mikro-orm-markdown 0.1.0-alpha.1 → 0.1.0-alpha.3
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 +53 -0
- package/CODE_OF_CONDUCT.md +25 -0
- package/LICENSE +1 -1
- package/README.ko.md +268 -57
- package/README.md +267 -56
- package/SECURITY.md +28 -0
- package/dist/cli.cjs +745 -161
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +51 -0
- package/dist/cli.d.ts +51 -0
- package/dist/cli.js +725 -163
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +577 -143
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -17
- package/dist/index.d.ts +21 -17
- package/dist/index.js +566 -143
- package/dist/index.js.map +1 -1
- package/package.json +44 -16
package/dist/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// src/docs/jsdoc.ts
|
|
2
2
|
import { Project } from "ts-morph";
|
|
3
|
-
function loadJsDoc(
|
|
3
|
+
function loadJsDoc(filePaths) {
|
|
4
4
|
const entities = /* @__PURE__ */ new Map();
|
|
5
5
|
const props = /* @__PURE__ */ new Map();
|
|
6
|
-
|
|
6
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
7
|
+
if (filePaths.length === 0) {
|
|
8
|
+
return { entities, props, sourceFileCount: 0, classNames };
|
|
9
|
+
}
|
|
7
10
|
const project = new Project({
|
|
8
11
|
skipAddingFilesFromTsConfig: true,
|
|
9
12
|
skipLoadingLibFiles: true,
|
|
@@ -12,30 +15,44 @@ function loadJsDoc(srcGlobs) {
|
|
|
12
15
|
skipLibCheck: true
|
|
13
16
|
}
|
|
14
17
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
for (const filePath of filePaths) {
|
|
19
|
+
try {
|
|
20
|
+
project.addSourceFilesAtPaths(filePath);
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const sourceFiles = project.getSourceFiles();
|
|
25
|
+
for (const sourceFile of sourceFiles) {
|
|
26
|
+
try {
|
|
27
|
+
for (const cls of sourceFile.getClasses()) {
|
|
28
|
+
const className = cls.getName();
|
|
29
|
+
if (!className) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
classNames.add(className);
|
|
33
|
+
const classDocs = cls.getJsDocs();
|
|
34
|
+
if (classDocs.length > 0) {
|
|
35
|
+
entities.set(className, parseEntityJsDoc(classDocs));
|
|
36
|
+
}
|
|
37
|
+
const propMap = /* @__PURE__ */ new Map();
|
|
38
|
+
for (const prop of cls.getProperties()) {
|
|
39
|
+
const propDocs = prop.getJsDocs();
|
|
40
|
+
if (propDocs.length === 0) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const info = parsePropJsDoc(propDocs);
|
|
44
|
+
if (info.description !== void 0 || info.atLeastOne) {
|
|
45
|
+
propMap.set(prop.getName(), info);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (propMap.size > 0) {
|
|
49
|
+
props.set(className, propMap);
|
|
31
50
|
}
|
|
32
51
|
}
|
|
33
|
-
|
|
34
|
-
props.set(className, propMap);
|
|
35
|
-
}
|
|
52
|
+
} catch {
|
|
36
53
|
}
|
|
37
54
|
}
|
|
38
|
-
return { entities, props };
|
|
55
|
+
return { entities, props, sourceFileCount: sourceFiles.length, classNames };
|
|
39
56
|
}
|
|
40
57
|
function parseEntityJsDoc(jsDocs) {
|
|
41
58
|
const namespaces = [];
|
|
@@ -45,14 +62,21 @@ function parseEntityJsDoc(jsDocs) {
|
|
|
45
62
|
let description;
|
|
46
63
|
for (const doc of jsDocs) {
|
|
47
64
|
const desc = doc.getDescription().trim();
|
|
48
|
-
if (desc && description === void 0)
|
|
65
|
+
if (desc && description === void 0) {
|
|
66
|
+
description = desc;
|
|
67
|
+
}
|
|
49
68
|
for (const tag of doc.getTags()) {
|
|
50
69
|
const tagName = tag.getTagName();
|
|
51
70
|
const comment = tag.getCommentText()?.trim();
|
|
52
|
-
if (tagName === "namespace" && comment)
|
|
53
|
-
|
|
54
|
-
else if (tagName === "
|
|
55
|
-
|
|
71
|
+
if (tagName === "namespace" && comment) {
|
|
72
|
+
namespaces.push(comment);
|
|
73
|
+
} else if (tagName === "erd" && comment) {
|
|
74
|
+
erdNamespaces.push(comment);
|
|
75
|
+
} else if (tagName === "describe" && comment) {
|
|
76
|
+
describeNamespaces.push(comment);
|
|
77
|
+
} else if (tagName === "hidden") {
|
|
78
|
+
hidden = true;
|
|
79
|
+
}
|
|
56
80
|
}
|
|
57
81
|
}
|
|
58
82
|
return {
|
|
@@ -63,16 +87,26 @@ function parseEntityJsDoc(jsDocs) {
|
|
|
63
87
|
hidden
|
|
64
88
|
};
|
|
65
89
|
}
|
|
66
|
-
function
|
|
90
|
+
function parsePropJsDoc(jsDocs) {
|
|
91
|
+
let description;
|
|
92
|
+
let atLeastOne = false;
|
|
67
93
|
for (const doc of jsDocs) {
|
|
68
94
|
const desc = doc.getDescription().trim();
|
|
69
|
-
if (desc)
|
|
95
|
+
if (desc && description === void 0) {
|
|
96
|
+
description = desc;
|
|
97
|
+
}
|
|
98
|
+
for (const tag of doc.getTags()) {
|
|
99
|
+
if (tag.getTagName() === "atLeastOne") {
|
|
100
|
+
atLeastOne = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
70
103
|
}
|
|
71
|
-
return void 0;
|
|
104
|
+
return { ...description !== void 0 && { description }, atLeastOne };
|
|
72
105
|
}
|
|
73
106
|
|
|
74
107
|
// src/metadata/load.ts
|
|
75
|
-
import
|
|
108
|
+
import * as path from "path";
|
|
109
|
+
import { EntitySchema, MikroORM } from "@mikro-orm/core";
|
|
76
110
|
var MetadataLoadError = class extends Error {
|
|
77
111
|
constructor(message, cause) {
|
|
78
112
|
super(message);
|
|
@@ -80,12 +114,46 @@ var MetadataLoadError = class extends Error {
|
|
|
80
114
|
this.name = "MetadataLoadError";
|
|
81
115
|
}
|
|
82
116
|
};
|
|
117
|
+
async function closeDiscoveryResources(orm) {
|
|
118
|
+
await orm.config.getMetadataCacheAdapter()?.close?.();
|
|
119
|
+
await orm.config.getResultCacheAdapter()?.close?.();
|
|
120
|
+
}
|
|
121
|
+
function collectEntitySchemaNames(options) {
|
|
122
|
+
const configuredEntities = [...options.entities ?? [], ...options.entitiesTs ?? []];
|
|
123
|
+
const names = [];
|
|
124
|
+
for (const entity of configuredEntities) {
|
|
125
|
+
if (entity instanceof EntitySchema) {
|
|
126
|
+
names.push(entity.meta.className);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (entity !== null && typeof entity === "object" && "schema" in entity && entity.schema instanceof EntitySchema) {
|
|
130
|
+
names.push(entity.schema.meta.className);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return names;
|
|
134
|
+
}
|
|
135
|
+
function assertNoEntitySchemaEntities(options) {
|
|
136
|
+
const schemaNames = collectEntitySchemaNames(options);
|
|
137
|
+
if (schemaNames.length === 0) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
throw new MetadataLoadError(
|
|
141
|
+
`EntitySchema-defined entities are not currently supported: ${schemaNames.join(", ")}.
|
|
142
|
+
Use decorator-based @Entity() classes instead.`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
83
145
|
async function loadEntityMetadata(options) {
|
|
146
|
+
assertNoEntitySchemaEntities(options);
|
|
84
147
|
let orm;
|
|
85
148
|
try {
|
|
86
149
|
orm = await MikroORM.init({
|
|
87
150
|
...options,
|
|
88
|
-
debug: false
|
|
151
|
+
debug: false,
|
|
152
|
+
connect: false,
|
|
153
|
+
// Always disable the metadata cache for one-shot doc runs so the project
|
|
154
|
+
// is never littered with a temp/ folder, regardless of how metadataProvider
|
|
155
|
+
// was configured.
|
|
156
|
+
metadataCache: { ...options.metadataCache, enabled: false }
|
|
89
157
|
});
|
|
90
158
|
} catch (cause) {
|
|
91
159
|
throw new MetadataLoadError(
|
|
@@ -100,20 +168,73 @@ async function loadEntityMetadata(options) {
|
|
|
100
168
|
"No entities were discovered. Check that your config specifies at least one entity path or class."
|
|
101
169
|
);
|
|
102
170
|
}
|
|
103
|
-
|
|
171
|
+
const baseDir = orm.config.get("baseDir");
|
|
172
|
+
const sourcePaths = [...new Set(all.filter((m) => m.path).map((m) => path.resolve(baseDir, m.path)))];
|
|
173
|
+
return { metas: all, sourcePaths };
|
|
104
174
|
} finally {
|
|
105
|
-
await orm
|
|
175
|
+
await closeDiscoveryResources(orm);
|
|
106
176
|
}
|
|
107
177
|
}
|
|
108
178
|
|
|
179
|
+
// src/model/build.ts
|
|
180
|
+
import { ReferenceKind as ReferenceKind2 } from "@mikro-orm/core";
|
|
181
|
+
|
|
109
182
|
// src/render/mermaid.ts
|
|
110
183
|
import { ReferenceKind } from "@mikro-orm/core";
|
|
184
|
+
|
|
185
|
+
// src/render/escape.ts
|
|
186
|
+
var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#]/g;
|
|
187
|
+
var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
|
|
188
|
+
function normalizeInlineText(value) {
|
|
189
|
+
return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
190
|
+
}
|
|
191
|
+
function splitNormalizedLines(value) {
|
|
192
|
+
return value.replace(/\r\n?/g, "\n").split("\n");
|
|
193
|
+
}
|
|
194
|
+
function escapeHtmlText(value) {
|
|
195
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
196
|
+
}
|
|
197
|
+
function escapeMarkdownInline(value) {
|
|
198
|
+
return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&");
|
|
199
|
+
}
|
|
200
|
+
function escapeMarkdownTableCell(value) {
|
|
201
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
|
|
202
|
+
}
|
|
203
|
+
function escapeMarkdownParagraph(value) {
|
|
204
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
|
|
205
|
+
}
|
|
206
|
+
function renderMarkdownBlockQuote(value) {
|
|
207
|
+
return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
|
|
208
|
+
}
|
|
209
|
+
function renderMarkdownInlineCode(value) {
|
|
210
|
+
const normalized = normalizeInlineText(value);
|
|
211
|
+
const backtickRuns = normalized.match(/`+/g) ?? [];
|
|
212
|
+
const longestRun = Math.max(0, ...backtickRuns.map((run) => run.length));
|
|
213
|
+
const fence = "`".repeat(longestRun + 1);
|
|
214
|
+
const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
|
|
215
|
+
const content = needsPadding ? ` ${normalized} ` : normalized;
|
|
216
|
+
return `${fence}${content}${fence}`;
|
|
217
|
+
}
|
|
218
|
+
function toMermaidIdentifier(value) {
|
|
219
|
+
const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
|
|
220
|
+
const identifier = normalized === "" ? "_" : normalized;
|
|
221
|
+
return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
|
|
222
|
+
}
|
|
223
|
+
function escapeMermaidQuotedText(value) {
|
|
224
|
+
return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
225
|
+
}
|
|
226
|
+
function toMarkdownAnchor(value) {
|
|
227
|
+
return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/render/mermaid.ts
|
|
111
231
|
var FORMULA_DUMMY_TABLE = {
|
|
112
232
|
alias: "e0",
|
|
113
233
|
name: "",
|
|
114
234
|
qualifiedName: "",
|
|
115
235
|
toString: () => "e0"
|
|
116
236
|
};
|
|
237
|
+
var UNRESOLVED_FORMULA = "<unresolved>";
|
|
117
238
|
function buildDiagramModel(metas) {
|
|
118
239
|
const metaByClass = new Map(metas.map((m) => [m.className, m]));
|
|
119
240
|
const entities = metas.filter((meta) => !meta.pivotTable && !meta.embeddable).map((meta) => buildEntityModel(meta, metaByClass));
|
|
@@ -121,13 +242,14 @@ function buildDiagramModel(metas) {
|
|
|
121
242
|
return { entities, relations };
|
|
122
243
|
}
|
|
123
244
|
function buildEntityModel(meta, metaByClass) {
|
|
124
|
-
const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.
|
|
245
|
+
const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.extends;
|
|
125
246
|
const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== void 0;
|
|
126
247
|
const columns = [];
|
|
127
248
|
for (const prop of Object.values(meta.properties)) {
|
|
128
|
-
if (isStiRoot && prop.inherited === true)
|
|
129
|
-
|
|
130
|
-
|
|
249
|
+
if (isStiRoot && prop.inherited === true) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
columns.push(...buildColumns(prop, metaByClass, meta));
|
|
131
253
|
}
|
|
132
254
|
return {
|
|
133
255
|
className: meta.className,
|
|
@@ -137,61 +259,123 @@ function buildEntityModel(meta, metaByClass) {
|
|
|
137
259
|
isEmbeddable: meta.embeddable === true,
|
|
138
260
|
...isStiRoot && { discriminatorColumn: meta.discriminatorColumn },
|
|
139
261
|
...isStiChild && { extendsEntity: meta.extends },
|
|
262
|
+
...isStiChild && meta.discriminatorValue !== void 0 && { discriminatorValue: String(meta.discriminatorValue) },
|
|
140
263
|
constraints: buildConstraints(meta)
|
|
141
264
|
};
|
|
142
265
|
}
|
|
143
|
-
function
|
|
144
|
-
if (prop.kind === ReferenceKind.EMBEDDED)
|
|
266
|
+
function buildColumns(prop, metaByClass, owningMeta) {
|
|
267
|
+
if (prop.kind === ReferenceKind.EMBEDDED) {
|
|
268
|
+
if (prop.object === true || prop.array === true) {
|
|
269
|
+
return [
|
|
270
|
+
{
|
|
271
|
+
propName: prop.name,
|
|
272
|
+
fieldName: prop.fieldNames?.[0] ?? prop.name,
|
|
273
|
+
type: "json",
|
|
274
|
+
isPrimary: false,
|
|
275
|
+
isForeignKey: false,
|
|
276
|
+
isUnique: prop.unique === true,
|
|
277
|
+
isNullable: prop.nullable === true,
|
|
278
|
+
...prop.comment !== void 0 && { comment: prop.comment },
|
|
279
|
+
embeddedIn: prop.array === true ? `${prop.type}[]` : prop.type
|
|
280
|
+
}
|
|
281
|
+
];
|
|
282
|
+
}
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
145
285
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
286
|
+
if (prop.object === true && prop.embedded !== void 0) {
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
146
289
|
const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
|
|
147
290
|
let embeddedIn;
|
|
291
|
+
let embeddedPropName;
|
|
148
292
|
if (prop.embedded !== void 0) {
|
|
149
293
|
const parentPropName = prop.embedded[0];
|
|
150
294
|
embeddedIn = owningMeta.properties[parentPropName]?.type;
|
|
295
|
+
embeddedPropName = prop.embedded[1];
|
|
151
296
|
}
|
|
152
297
|
const isDiscriminator = owningMeta.discriminatorColumn !== void 0 && prop.name === owningMeta.discriminatorColumn;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
298
|
+
const enumItems = prop.enum === true && Array.isArray(prop.items) && prop.items.length > 0 ? prop.items.map((item) => String(item)) : void 0;
|
|
299
|
+
return [
|
|
300
|
+
{
|
|
301
|
+
propName: prop.name,
|
|
302
|
+
fieldName: prop.fieldNames?.[0] ?? prop.name,
|
|
303
|
+
// Store the original type (e.g. `varchar(255)`); the Mermaid renderer
|
|
304
|
+
// sanitizes it for diagram identifiers, while the markdown table shows
|
|
305
|
+
// it verbatim. Guard against a missing type so downstream string
|
|
306
|
+
// handling never sees undefined (matches the FK path's defaulting).
|
|
307
|
+
type: prop.type ?? "unknown",
|
|
308
|
+
isPrimary: prop.primary === true,
|
|
309
|
+
isForeignKey: false,
|
|
310
|
+
isUnique: prop.unique === true,
|
|
311
|
+
isNullable: prop.nullable === true,
|
|
312
|
+
...prop.comment !== void 0 && { comment: prop.comment },
|
|
313
|
+
...formulaExpr !== void 0 && { formula: formulaExpr },
|
|
314
|
+
...embeddedIn !== void 0 && { embeddedIn },
|
|
315
|
+
...embeddedPropName !== void 0 && { embeddedPropName },
|
|
316
|
+
...isDiscriminator && { isDiscriminator: true },
|
|
317
|
+
...enumItems !== void 0 && { enumItems }
|
|
318
|
+
}
|
|
319
|
+
];
|
|
165
320
|
}
|
|
166
321
|
if (prop.kind === ReferenceKind.MANY_TO_ONE || prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner === true) {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
isPrimary: false,
|
|
173
|
-
isForeignKey: true,
|
|
174
|
-
isUnique: prop.unique === true,
|
|
175
|
-
isNullable: prop.nullable === true
|
|
176
|
-
};
|
|
322
|
+
const cols = buildForeignKeyColumns(prop, metaByClass);
|
|
323
|
+
if (prop.type === owningMeta.className) {
|
|
324
|
+
return cols.map((col) => ({ ...col, isSelfReference: true }));
|
|
325
|
+
}
|
|
326
|
+
return cols;
|
|
177
327
|
}
|
|
178
|
-
return
|
|
328
|
+
return [];
|
|
179
329
|
}
|
|
180
330
|
function resolveFormulaExpr(cb) {
|
|
181
331
|
try {
|
|
182
|
-
const cols = new Proxy(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
332
|
+
const cols = new Proxy(
|
|
333
|
+
{},
|
|
334
|
+
{
|
|
335
|
+
get: (_target, key) => typeof key === "string" ? key : ""
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
const result = cb(FORMULA_DUMMY_TABLE, cols);
|
|
339
|
+
return typeof result === "string" ? result : String(result);
|
|
186
340
|
} catch {
|
|
187
|
-
return
|
|
341
|
+
return UNRESOLVED_FORMULA;
|
|
188
342
|
}
|
|
189
343
|
}
|
|
190
|
-
function
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
344
|
+
function buildForeignKeyColumns(prop, metaByClass) {
|
|
345
|
+
const fieldNames = prop.fieldNames && prop.fieldNames.length > 0 ? prop.fieldNames : [`${prop.name}_id`];
|
|
346
|
+
const fkTypes = resolveFkTypes(prop, metaByClass, fieldNames.length);
|
|
347
|
+
return fieldNames.map((fieldName, index) => ({
|
|
348
|
+
propName: prop.name,
|
|
349
|
+
fieldName,
|
|
350
|
+
type: fkTypes[index] ?? fkTypes[0] ?? "integer",
|
|
351
|
+
isPrimary: prop.primary === true,
|
|
352
|
+
isForeignKey: true,
|
|
353
|
+
isUnique: prop.unique === true,
|
|
354
|
+
isNullable: prop.nullable === true,
|
|
355
|
+
...prop.comment !== void 0 && { comment: prop.comment },
|
|
356
|
+
referencedEntity: prop.type
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
function resolveFkTypes(prop, metaByClass, fieldNameCount) {
|
|
360
|
+
const refMeta = metaByClass.get(prop.type);
|
|
361
|
+
if (!refMeta) {
|
|
362
|
+
return Array.from({ length: fieldNameCount }, () => "integer");
|
|
363
|
+
}
|
|
364
|
+
const primaryProps = getPrimaryProps(refMeta);
|
|
365
|
+
const referencedColumnNames = prop.referencedColumnNames ?? [];
|
|
366
|
+
return Array.from({ length: fieldNameCount }, (_value, index) => {
|
|
367
|
+
const referencedColumnName = referencedColumnNames[index];
|
|
368
|
+
const pkProp = referencedColumnName !== void 0 ? primaryProps.find((candidate) => candidate.fieldNames.includes(referencedColumnName)) : void 0;
|
|
369
|
+
return (pkProp ?? primaryProps[index] ?? primaryProps[0])?.type ?? "integer";
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function getPrimaryProps(meta) {
|
|
373
|
+
const primaryKeys = meta.primaryKeys ?? [];
|
|
374
|
+
const orderedPrimaryProps = primaryKeys.map((key) => meta.properties[String(key)]).filter((prop) => prop !== void 0);
|
|
375
|
+
if (orderedPrimaryProps.length > 0) {
|
|
376
|
+
return orderedPrimaryProps;
|
|
377
|
+
}
|
|
378
|
+
return Object.values(meta.properties).filter((prop) => prop.primary === true);
|
|
195
379
|
}
|
|
196
380
|
function buildConstraints(meta) {
|
|
197
381
|
const result = [];
|
|
@@ -199,7 +383,7 @@ function buildConstraints(meta) {
|
|
|
199
383
|
const props = idx.properties;
|
|
200
384
|
result.push({
|
|
201
385
|
type: "index",
|
|
202
|
-
properties:
|
|
386
|
+
properties: resolveConstraintProperties(meta, props),
|
|
203
387
|
...idx.name !== void 0 && { name: idx.name }
|
|
204
388
|
});
|
|
205
389
|
}
|
|
@@ -207,12 +391,14 @@ function buildConstraints(meta) {
|
|
|
207
391
|
const props = uniq.properties;
|
|
208
392
|
result.push({
|
|
209
393
|
type: "unique",
|
|
210
|
-
properties:
|
|
394
|
+
properties: resolveConstraintProperties(meta, props),
|
|
211
395
|
...uniq.name !== void 0 && { name: uniq.name }
|
|
212
396
|
});
|
|
213
397
|
}
|
|
214
398
|
for (const check of meta.checks ?? []) {
|
|
215
|
-
if (typeof check.expression !== "string")
|
|
399
|
+
if (typeof check.expression !== "string") {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
216
402
|
result.push({
|
|
217
403
|
type: "check",
|
|
218
404
|
properties: [],
|
|
@@ -222,13 +408,30 @@ function buildConstraints(meta) {
|
|
|
222
408
|
}
|
|
223
409
|
return result;
|
|
224
410
|
}
|
|
411
|
+
function resolveConstraintProperties(meta, props) {
|
|
412
|
+
const propNames = Array.isArray(props) ? props : props !== void 0 ? [props] : [];
|
|
413
|
+
return propNames.flatMap((propName) => {
|
|
414
|
+
const prop = meta.properties[String(propName)];
|
|
415
|
+
if (prop === void 0) {
|
|
416
|
+
return [String(propName)];
|
|
417
|
+
}
|
|
418
|
+
if (prop.fieldNames !== void 0 && prop.fieldNames.length > 0) {
|
|
419
|
+
return prop.fieldNames;
|
|
420
|
+
}
|
|
421
|
+
return [prop.name];
|
|
422
|
+
});
|
|
423
|
+
}
|
|
225
424
|
function buildRelationEdges(metas) {
|
|
226
425
|
const edges = [];
|
|
227
426
|
for (const meta of metas) {
|
|
228
|
-
if (meta.pivotTable || meta.embeddable)
|
|
427
|
+
if (meta.pivotTable || meta.embeddable) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
229
430
|
for (const prop of Object.values(meta.properties)) {
|
|
230
431
|
const edge = buildEdge(meta.className, prop);
|
|
231
|
-
if (edge !== null)
|
|
432
|
+
if (edge !== null) {
|
|
433
|
+
edges.push(edge);
|
|
434
|
+
}
|
|
232
435
|
}
|
|
233
436
|
}
|
|
234
437
|
return edges;
|
|
@@ -236,6 +439,9 @@ function buildRelationEdges(metas) {
|
|
|
236
439
|
function buildEdge(fromEntity, prop) {
|
|
237
440
|
const isNullable = prop.nullable === true;
|
|
238
441
|
if (prop.kind === ReferenceKind.MANY_TO_ONE) {
|
|
442
|
+
if (prop.type === fromEntity) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
239
445
|
return {
|
|
240
446
|
fromEntity,
|
|
241
447
|
toEntity: prop.type,
|
|
@@ -267,7 +473,7 @@ function buildEdge(fromEntity, prop) {
|
|
|
267
473
|
function renderErDiagram(model) {
|
|
268
474
|
const lines = ["erDiagram"];
|
|
269
475
|
for (const entity of model.entities) {
|
|
270
|
-
lines.push(` ${entity.className} {`);
|
|
476
|
+
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
271
477
|
for (const col of entity.columns) {
|
|
272
478
|
lines.push(` ${renderColumnLine(col)}`);
|
|
273
479
|
}
|
|
@@ -275,16 +481,18 @@ function renderErDiagram(model) {
|
|
|
275
481
|
}
|
|
276
482
|
for (const rel of model.relations) {
|
|
277
483
|
lines.push(
|
|
278
|
-
` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : "${rel.label}"`
|
|
484
|
+
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
279
485
|
);
|
|
280
486
|
}
|
|
281
487
|
return lines.join("\n");
|
|
282
488
|
}
|
|
283
489
|
function renderColumnLine(col) {
|
|
284
490
|
let qualifier = "";
|
|
285
|
-
if (col.isPrimary)
|
|
286
|
-
|
|
287
|
-
else if (col.isUnique)
|
|
491
|
+
if (col.isPrimary) {
|
|
492
|
+
qualifier = " PK";
|
|
493
|
+
} else if (col.isUnique) {
|
|
494
|
+
qualifier = " UK";
|
|
495
|
+
}
|
|
288
496
|
let comment;
|
|
289
497
|
if (col.formula !== void 0) {
|
|
290
498
|
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
@@ -292,41 +500,54 @@ function renderColumnLine(col) {
|
|
|
292
500
|
comment = "discriminator";
|
|
293
501
|
} else if (col.embeddedIn !== void 0) {
|
|
294
502
|
comment = `[${col.embeddedIn}]`;
|
|
295
|
-
} else if (col.
|
|
296
|
-
comment =
|
|
503
|
+
} else if (col.isSelfReference) {
|
|
504
|
+
comment = "self-ref";
|
|
297
505
|
}
|
|
298
|
-
const commentStr = comment !== void 0 ? ` "${comment}"` : "";
|
|
299
|
-
return `${col.type} ${col.fieldName}${qualifier}${commentStr}`;
|
|
300
|
-
}
|
|
301
|
-
function normalizeType(type) {
|
|
302
|
-
return type.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
506
|
+
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
507
|
+
return `${toMermaidIdentifier(col.type)} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
303
508
|
}
|
|
304
509
|
|
|
305
510
|
// src/model/build.ts
|
|
306
|
-
|
|
307
|
-
|
|
511
|
+
var FROM_ONE_OR_MORE = "}|";
|
|
512
|
+
var TO_ONE_OR_MORE = "|{";
|
|
513
|
+
function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
514
|
+
const { entities: diagramEntities, relations } = buildDiagramModel(metas);
|
|
515
|
+
const allRelations = applyAtLeastOne(relations, metas, jsDocResult.props, onWarn);
|
|
516
|
+
const hiddenClasses = /* @__PURE__ */ new Set();
|
|
517
|
+
for (const model of diagramEntities) {
|
|
518
|
+
if (jsDocResult.entities.get(model.className)?.hidden) {
|
|
519
|
+
hiddenClasses.add(model.className);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
308
522
|
const enrichedByClass = /* @__PURE__ */ new Map();
|
|
309
523
|
for (const model of diagramEntities) {
|
|
310
524
|
const jsDoc = jsDocResult.entities.get(model.className);
|
|
311
|
-
if (jsDoc?.hidden)
|
|
312
|
-
|
|
313
|
-
|
|
525
|
+
if (jsDoc?.hidden) {
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const columns = model.columns.filter(
|
|
529
|
+
(col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
|
|
530
|
+
);
|
|
531
|
+
const visibleModel = columns.length === model.columns.length ? model : { ...model, columns };
|
|
532
|
+
const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
|
|
533
|
+
const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
|
|
534
|
+
enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
|
|
314
535
|
}
|
|
315
536
|
const groupNames = /* @__PURE__ */ new Set();
|
|
316
537
|
let anyUntagged = false;
|
|
317
538
|
for (const { jsDoc } of enrichedByClass.values()) {
|
|
318
|
-
const allNs = [
|
|
319
|
-
...jsDoc?.namespaces ?? [],
|
|
320
|
-
...jsDoc?.erdNamespaces ?? [],
|
|
321
|
-
...jsDoc?.describeNamespaces ?? []
|
|
322
|
-
];
|
|
539
|
+
const allNs = [...jsDoc?.namespaces ?? [], ...jsDoc?.erdNamespaces ?? [], ...jsDoc?.describeNamespaces ?? []];
|
|
323
540
|
if (allNs.length === 0) {
|
|
324
541
|
anyUntagged = true;
|
|
325
542
|
} else {
|
|
326
|
-
for (const ns of allNs)
|
|
543
|
+
for (const ns of allNs) {
|
|
544
|
+
groupNames.add(ns);
|
|
545
|
+
}
|
|
327
546
|
}
|
|
328
547
|
}
|
|
329
|
-
if (anyUntagged)
|
|
548
|
+
if (anyUntagged) {
|
|
549
|
+
groupNames.add("default");
|
|
550
|
+
}
|
|
330
551
|
const groups = [];
|
|
331
552
|
for (const groupName of groupNames) {
|
|
332
553
|
const isDefault = groupName === "default";
|
|
@@ -337,46 +558,153 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
337
558
|
({ jsDoc }) => belongsToGroupForText(jsDoc, groupName, isDefault)
|
|
338
559
|
);
|
|
339
560
|
const erdClassNames = new Set(erdEntities.map((e) => e.model.className));
|
|
340
|
-
const erdRelations = allRelations.filter(
|
|
341
|
-
(r) => erdClassNames.has(r.fromEntity) && erdClassNames.has(r.toEntity)
|
|
342
|
-
);
|
|
561
|
+
const erdRelations = allRelations.filter((r) => erdClassNames.has(r.fromEntity) && erdClassNames.has(r.toEntity));
|
|
343
562
|
groups.push({ name: groupName, erdEntities, textEntities, erdRelations });
|
|
344
563
|
}
|
|
345
564
|
groups.sort((a, b) => {
|
|
346
|
-
if (a.name === "default")
|
|
347
|
-
|
|
565
|
+
if (a.name === "default") {
|
|
566
|
+
return 1;
|
|
567
|
+
}
|
|
568
|
+
if (b.name === "default") {
|
|
569
|
+
return -1;
|
|
570
|
+
}
|
|
348
571
|
return a.name.localeCompare(b.name);
|
|
349
572
|
});
|
|
350
573
|
return { title, groups, ...description !== void 0 && { description } };
|
|
351
574
|
}
|
|
575
|
+
function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
|
|
576
|
+
const merged = new Map(ownPropDocs);
|
|
577
|
+
for (const col of columns) {
|
|
578
|
+
if (merged.has(col.propName) || col.embeddedIn === void 0 || col.embeddedPropName === void 0) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const info = allPropDocs.get(col.embeddedIn)?.get(col.embeddedPropName);
|
|
582
|
+
if (info) {
|
|
583
|
+
merged.set(col.propName, info);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return merged;
|
|
587
|
+
}
|
|
588
|
+
function applyAtLeastOne(relations, metas, props, onWarn) {
|
|
589
|
+
const adjusted = relations.map((edge) => ({ ...edge }));
|
|
590
|
+
const metaByClass = new Map(metas.map((m) => [m.className, m]));
|
|
591
|
+
for (const [className, propMap] of props) {
|
|
592
|
+
const meta = metaByClass.get(className);
|
|
593
|
+
if (!meta) {
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
for (const [propName, info] of propMap) {
|
|
597
|
+
if (!info.atLeastOne) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
const prop = meta.properties[propName];
|
|
601
|
+
if (!prop) {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
let edge;
|
|
605
|
+
if (prop.kind === ReferenceKind2.ONE_TO_MANY && prop.mappedBy) {
|
|
606
|
+
edge = adjusted.find(
|
|
607
|
+
(e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
|
|
608
|
+
);
|
|
609
|
+
if (edge) {
|
|
610
|
+
edge.fromCardinality = FROM_ONE_OR_MORE;
|
|
611
|
+
}
|
|
612
|
+
} else if (prop.kind === ReferenceKind2.MANY_TO_MANY && prop.owner === true) {
|
|
613
|
+
edge = adjusted.find((e) => e.fromEntity === className && e.toEntity === prop.type && e.label === propName);
|
|
614
|
+
if (edge) {
|
|
615
|
+
edge.toCardinality = TO_ONE_OR_MORE;
|
|
616
|
+
}
|
|
617
|
+
} else if (prop.kind === ReferenceKind2.MANY_TO_MANY && prop.mappedBy) {
|
|
618
|
+
edge = adjusted.find(
|
|
619
|
+
(e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
|
|
620
|
+
);
|
|
621
|
+
if (edge) {
|
|
622
|
+
edge.fromCardinality = FROM_ONE_OR_MORE;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (!edge) {
|
|
626
|
+
onWarn?.(
|
|
627
|
+
`@atLeastOne on ${className}.${propName} had no effect: no matching relation edge was found. It applies only to collection relations that can be matched to a rendered edge: @OneToMany with mappedBy, or @ManyToMany on either the owning side or an inverse mappedBy side.`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return adjusted;
|
|
633
|
+
}
|
|
352
634
|
function hasNoNamespaceTags(jsDoc) {
|
|
353
|
-
if (!jsDoc)
|
|
635
|
+
if (!jsDoc) {
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
354
638
|
return jsDoc.namespaces.length === 0 && jsDoc.erdNamespaces.length === 0 && jsDoc.describeNamespaces.length === 0;
|
|
355
639
|
}
|
|
356
640
|
function belongsToGroupForErd(jsDoc, groupName, isDefault) {
|
|
357
|
-
if (isDefault)
|
|
358
|
-
|
|
641
|
+
if (isDefault) {
|
|
642
|
+
return hasNoNamespaceTags(jsDoc);
|
|
643
|
+
}
|
|
644
|
+
if (!jsDoc) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
359
647
|
return jsDoc.namespaces.includes(groupName) || jsDoc.erdNamespaces.includes(groupName);
|
|
360
648
|
}
|
|
361
649
|
function belongsToGroupForText(jsDoc, groupName, isDefault) {
|
|
362
|
-
if (isDefault)
|
|
363
|
-
|
|
650
|
+
if (isDefault) {
|
|
651
|
+
return hasNoNamespaceTags(jsDoc);
|
|
652
|
+
}
|
|
653
|
+
if (!jsDoc) {
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
364
656
|
return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
|
|
365
657
|
}
|
|
366
658
|
|
|
659
|
+
// src/provider.ts
|
|
660
|
+
async function withTsMorphMetadataProvider(options, onWarn) {
|
|
661
|
+
if (options.metadataProvider !== void 0) {
|
|
662
|
+
return options;
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
const { TsMorphMetadataProvider } = await import("@mikro-orm/reflection");
|
|
666
|
+
return { ...options, metadataProvider: TsMorphMetadataProvider };
|
|
667
|
+
} catch (err) {
|
|
668
|
+
const code = err !== null && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
669
|
+
const isNotInstalled = code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND";
|
|
670
|
+
if (!isNotInstalled && onWarn) {
|
|
671
|
+
onWarn(
|
|
672
|
+
`@mikro-orm/reflection is installed but failed to load: ${err instanceof Error ? err.message : String(err)}. Ensure all @mikro-orm/* packages are installed at the same version.`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
return options;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
367
679
|
// src/render/markdown.ts
|
|
368
680
|
function renderMarkdown(docModel) {
|
|
369
|
-
const sections = [`# ${docModel.title}`];
|
|
681
|
+
const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
|
|
370
682
|
if (docModel.description) {
|
|
371
|
-
sections.push(docModel.description);
|
|
683
|
+
sections.push(escapeMarkdownParagraph(docModel.description));
|
|
684
|
+
}
|
|
685
|
+
if (docModel.groups.length > 1) {
|
|
686
|
+
sections.push(renderTableOfContents(docModel.groups));
|
|
372
687
|
}
|
|
373
688
|
for (const group of docModel.groups) {
|
|
374
689
|
sections.push(renderGroupSection(group));
|
|
375
690
|
}
|
|
376
691
|
return sections.join("\n\n");
|
|
377
692
|
}
|
|
693
|
+
function renderBulletSection(header, items, renderItem) {
|
|
694
|
+
const lines = [header, ""];
|
|
695
|
+
for (const item of items) {
|
|
696
|
+
lines.push(renderItem(item));
|
|
697
|
+
}
|
|
698
|
+
return lines.join("\n");
|
|
699
|
+
}
|
|
700
|
+
function renderTableOfContents(groups) {
|
|
701
|
+
return renderBulletSection("## Contents", groups, (group) => {
|
|
702
|
+
const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
|
|
703
|
+
return `- [${label}](#${toMarkdownAnchor(group.name)})`;
|
|
704
|
+
});
|
|
705
|
+
}
|
|
378
706
|
function renderGroupSection(group) {
|
|
379
|
-
const parts = [`## ${group.name}`];
|
|
707
|
+
const parts = [`## ${escapeMarkdownInline(group.name)}`];
|
|
380
708
|
if (group.erdEntities.length > 0) {
|
|
381
709
|
const diagramModel = {
|
|
382
710
|
entities: group.erdEntities.map((e) => e.model),
|
|
@@ -390,16 +718,18 @@ function renderGroupSection(group) {
|
|
|
390
718
|
return parts.join("\n\n");
|
|
391
719
|
}
|
|
392
720
|
function renderEntitySection(entity) {
|
|
393
|
-
const parts = [`### ${entity.model.className}`];
|
|
721
|
+
const parts = [`### ${escapeMarkdownInline(entity.model.className)}`];
|
|
722
|
+
parts.push(`*Table: ${renderMarkdownInlineCode(entity.model.tableName)}*`);
|
|
394
723
|
if (entity.jsDoc?.description) {
|
|
395
|
-
parts.push(
|
|
724
|
+
parts.push(renderMarkdownBlockQuote(entity.jsDoc.description));
|
|
396
725
|
}
|
|
397
726
|
if (entity.model.discriminatorColumn) {
|
|
727
|
+
parts.push(`*STI root \u2014 discriminator column: ${renderMarkdownInlineCode(entity.model.discriminatorColumn)}*`);
|
|
728
|
+
} else if (entity.model.extendsEntity) {
|
|
729
|
+
const discValue = entity.model.discriminatorValue !== void 0 ? `, discriminator value: ${renderMarkdownInlineCode(entity.model.discriminatorValue)}` : "";
|
|
398
730
|
parts.push(
|
|
399
|
-
`*
|
|
731
|
+
`*Extends ${renderMarkdownInlineCode(entity.model.extendsEntity)} (Single Table Inheritance${discValue})*`
|
|
400
732
|
);
|
|
401
|
-
} else if (entity.model.extendsEntity) {
|
|
402
|
-
parts.push(`*Extends \`${entity.model.extendsEntity}\` (Single Table Inheritance)*`);
|
|
403
733
|
}
|
|
404
734
|
if (entity.model.columns.length > 0) {
|
|
405
735
|
parts.push(renderColumnTable(entity));
|
|
@@ -407,6 +737,10 @@ function renderEntitySection(entity) {
|
|
|
407
737
|
if (entity.model.constraints.length > 0) {
|
|
408
738
|
parts.push(renderConstraints(entity.model.constraints));
|
|
409
739
|
}
|
|
740
|
+
const computedColumns = entity.model.columns.filter((col) => col.formula !== void 0);
|
|
741
|
+
if (computedColumns.length > 0) {
|
|
742
|
+
parts.push(renderComputedColumns(computedColumns));
|
|
743
|
+
}
|
|
410
744
|
return parts.join("\n\n");
|
|
411
745
|
}
|
|
412
746
|
function renderColumnTable(entity) {
|
|
@@ -415,47 +749,136 @@ function renderColumnTable(entity) {
|
|
|
415
749
|
const rows = entity.model.columns.map((col) => {
|
|
416
750
|
const key = resolveColumnKey(col);
|
|
417
751
|
const nullable = col.isNullable && !col.isPrimary ? "Y" : "";
|
|
418
|
-
const
|
|
419
|
-
|
|
752
|
+
const docDesc = entity.propDocs.get(col.propName)?.description ?? col.comment ?? "";
|
|
753
|
+
const enumDesc = col.enumItems !== void 0 ? `One of: ${col.enumItems.join(", ")}` : "";
|
|
754
|
+
const desc = [docDesc, enumDesc].filter((part) => part !== "").join("\n");
|
|
755
|
+
return `| ${escapeMarkdownTableCell(col.fieldName)} | ${escapeMarkdownTableCell(col.type)} | ${escapeMarkdownTableCell(key)} | ${nullable} | ${escapeMarkdownTableCell(desc)} |`;
|
|
420
756
|
});
|
|
421
757
|
return [header, sep, ...rows].join("\n");
|
|
422
758
|
}
|
|
423
759
|
function resolveColumnKey(col) {
|
|
424
|
-
|
|
760
|
+
const fkKey = col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
|
|
761
|
+
if (col.isPrimary && col.isForeignKey) {
|
|
762
|
+
return `PK, ${fkKey}`;
|
|
763
|
+
}
|
|
764
|
+
if (col.isPrimary) {
|
|
765
|
+
return "PK";
|
|
766
|
+
}
|
|
425
767
|
if (col.isForeignKey) {
|
|
426
|
-
return
|
|
768
|
+
return fkKey;
|
|
769
|
+
}
|
|
770
|
+
if (col.isUnique) {
|
|
771
|
+
return "UK";
|
|
772
|
+
}
|
|
773
|
+
if (col.isDiscriminator) {
|
|
774
|
+
return "discriminator";
|
|
775
|
+
}
|
|
776
|
+
if (col.embeddedIn !== void 0) {
|
|
777
|
+
return `[${col.embeddedIn}]`;
|
|
427
778
|
}
|
|
428
|
-
if (col.isUnique) return "UK";
|
|
429
|
-
if (col.formula !== void 0) return `formula: ${col.formula}`;
|
|
430
|
-
if (col.isDiscriminator) return "discriminator";
|
|
431
|
-
if (col.embeddedIn !== void 0) return `[${col.embeddedIn}]`;
|
|
432
779
|
return "";
|
|
433
780
|
}
|
|
781
|
+
function renderComputedColumns(columns) {
|
|
782
|
+
return renderBulletSection("**Computed columns:**", columns, (col) => {
|
|
783
|
+
const expr = col.formula ? `: ${renderMarkdownInlineCode(col.formula)}` : "";
|
|
784
|
+
return `- ${renderMarkdownInlineCode(col.fieldName)}${expr}`;
|
|
785
|
+
});
|
|
786
|
+
}
|
|
434
787
|
function renderConstraints(constraints) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const
|
|
788
|
+
return renderBulletSection("**Constraints:**", constraints, (c) => {
|
|
789
|
+
const name = c.name ? ` ${renderMarkdownInlineCode(c.name)}` : "";
|
|
790
|
+
const properties = c.properties.map(escapeMarkdownInline).join(", ");
|
|
438
791
|
if (c.type === "index") {
|
|
439
|
-
|
|
440
|
-
} else if (c.type === "unique") {
|
|
441
|
-
lines.push(`- Unique${name}: (${c.properties.join(", ")})`);
|
|
442
|
-
} else if (c.type === "check") {
|
|
443
|
-
lines.push(`- Check${name}: \`${c.expression ?? ""}\``);
|
|
792
|
+
return `- Index${name}: (${properties})`;
|
|
444
793
|
}
|
|
445
|
-
|
|
446
|
-
|
|
794
|
+
if (c.type === "unique") {
|
|
795
|
+
return `- Unique${name}: (${properties})`;
|
|
796
|
+
}
|
|
797
|
+
return `- Check${name}: ${renderMarkdownInlineCode(c.expression ?? "")}`;
|
|
798
|
+
});
|
|
447
799
|
}
|
|
448
800
|
|
|
449
801
|
// src/index.ts
|
|
802
|
+
var COMPILED_JS = /\.(c|m)?js$/i;
|
|
803
|
+
function resolveJsDocSources(sourcePaths, src, onWarn) {
|
|
804
|
+
if (src !== void 0 && src.length > 0) {
|
|
805
|
+
return src;
|
|
806
|
+
}
|
|
807
|
+
if (sourcePaths.some((p) => COMPILED_JS.test(p)) && onWarn) {
|
|
808
|
+
onWarn(
|
|
809
|
+
'Entities were discovered from compiled JavaScript, so JSDoc descriptions and @namespace/@hidden tags cannot be read (build tools strip comments). Hidden entities may be exposed. Pass --src "<glob to your .ts sources>" (or the `src` option) to read JSDoc from the original TypeScript files.'
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
return sourcePaths;
|
|
813
|
+
}
|
|
814
|
+
function assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn) {
|
|
815
|
+
if (jsDocResult.sourceFileCount === 0) {
|
|
816
|
+
throw new Error(
|
|
817
|
+
`No source files matched the explicit src paths: ${src.join(", ")}
|
|
818
|
+
Check the --src glob/path (or the \`src\` option). Without matching TypeScript sources, JSDoc tags such as @namespace and @hidden cannot be read.`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
const isRenderable = (meta) => !meta.pivotTable && !meta.embeddable;
|
|
822
|
+
const missingConcrete = metas.filter((meta) => isRenderable(meta) && !meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
|
|
823
|
+
if (missingConcrete.length > 0) {
|
|
824
|
+
throw new Error(
|
|
825
|
+
`Explicit src paths did not include source declarations for discovered entities: ${missingConcrete.join(", ")}
|
|
826
|
+
Check that --src (or the \`src\` option) points at all TypeScript entity files. JSDoc tags such as @namespace and @hidden for missing entities cannot be read.`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
if (onWarn) {
|
|
830
|
+
const missingAbstract = metas.filter((meta) => isRenderable(meta) && meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
|
|
831
|
+
if (missingAbstract.length > 0) {
|
|
832
|
+
onWarn(
|
|
833
|
+
`Abstract STI parent entities were not found in the explicit src paths: ${missingAbstract.join(", ")}
|
|
834
|
+
@hidden and @namespace tags for these entities will not be applied. Include their source files in --src to enable JSDoc tags for them.`
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function errorMessages(err) {
|
|
840
|
+
const messages = [];
|
|
841
|
+
const seen = /* @__PURE__ */ new Set();
|
|
842
|
+
let current = err;
|
|
843
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
844
|
+
seen.add(current);
|
|
845
|
+
messages.push(current.message);
|
|
846
|
+
current = current.cause;
|
|
847
|
+
}
|
|
848
|
+
return messages;
|
|
849
|
+
}
|
|
850
|
+
function isMissingTsMorphSourceFile(err) {
|
|
851
|
+
return errorMessages(err).some((message) => message.includes("Source file") && message.includes("not found"));
|
|
852
|
+
}
|
|
853
|
+
async function loadEntityMetadataWithTsMorphFallback(originalOrm, effectiveOrm) {
|
|
854
|
+
try {
|
|
855
|
+
return await loadEntityMetadata(effectiveOrm);
|
|
856
|
+
} catch (err) {
|
|
857
|
+
const wasAutoInjected = originalOrm.metadataProvider === void 0 && effectiveOrm.metadataProvider !== void 0;
|
|
858
|
+
if (!wasAutoInjected || !isMissingTsMorphSourceFile(err)) {
|
|
859
|
+
throw err;
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
return await loadEntityMetadata(originalOrm);
|
|
863
|
+
} catch {
|
|
864
|
+
throw err;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
450
868
|
async function generateMarkdown(options) {
|
|
451
|
-
const { orm, title = "Database Schema", description, src
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
const
|
|
869
|
+
const { orm, title = "Database Schema", description, src, onWarn } = options;
|
|
870
|
+
const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
|
|
871
|
+
const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
|
|
872
|
+
const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
|
|
873
|
+
if (src !== void 0 && src.length > 0) {
|
|
874
|
+
assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
|
|
875
|
+
}
|
|
876
|
+
const docModel = buildDocumentModel(metas, jsDocResult, title, description, onWarn);
|
|
455
877
|
return renderMarkdown(docModel);
|
|
456
878
|
}
|
|
457
879
|
export {
|
|
458
880
|
MetadataLoadError,
|
|
459
|
-
generateMarkdown
|
|
881
|
+
generateMarkdown,
|
|
882
|
+
resolveJsDocSources
|
|
460
883
|
};
|
|
461
884
|
//# sourceMappingURL=index.js.map
|