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