mikro-orm-markdown 0.1.0-alpha.2 → 0.1.0-alpha.4
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 +66 -0
- package/CODE_OF_CONDUCT.md +25 -0
- package/LICENSE +1 -1
- package/README.ko.md +248 -41
- package/README.md +248 -41
- package/SECURITY.md +28 -0
- package/dist/cli.cjs +729 -136
- 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 +709 -138
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +559 -121
- 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 +548 -121
- package/dist/index.js.map +1 -1
- package/package.json +30 -6
package/dist/cli.cjs
CHANGED
|
@@ -6,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
9
13
|
var __copyProps = (to, from, except, desc) => {
|
|
10
14
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
15
|
for (let key of __getOwnPropNames(from))
|
|
@@ -22,19 +26,39 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
26
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
27
|
mod
|
|
24
28
|
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
30
|
|
|
26
31
|
// src/cli.ts
|
|
32
|
+
var cli_exports = {};
|
|
33
|
+
__export(cli_exports, {
|
|
34
|
+
findNearestTsconfig: () => findNearestTsconfig,
|
|
35
|
+
formatDiscoveryError: () => formatDiscoveryError,
|
|
36
|
+
formatErrorChain: () => formatErrorChain,
|
|
37
|
+
loadOrmOptions: () => loadOrmOptions,
|
|
38
|
+
toConfigImportSpecifier: () => toConfigImportSpecifier,
|
|
39
|
+
writeMarkdownFile: () => writeMarkdownFile
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(cli_exports);
|
|
42
|
+
|
|
43
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
44
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
45
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
46
|
+
|
|
47
|
+
// src/cli.ts
|
|
48
|
+
var syncFs = __toESM(require("fs"), 1);
|
|
27
49
|
var fs = __toESM(require("fs/promises"), 1);
|
|
28
|
-
var
|
|
50
|
+
var path2 = __toESM(require("path"), 1);
|
|
51
|
+
var import_node_url = require("url");
|
|
29
52
|
var import_commander = require("commander");
|
|
30
53
|
|
|
31
54
|
// src/docs/jsdoc.ts
|
|
32
55
|
var import_ts_morph = require("ts-morph");
|
|
33
|
-
function loadJsDoc(
|
|
56
|
+
function loadJsDoc(filePaths) {
|
|
34
57
|
const entities = /* @__PURE__ */ new Map();
|
|
35
58
|
const props = /* @__PURE__ */ new Map();
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
60
|
+
if (filePaths.length === 0) {
|
|
61
|
+
return { entities, props, sourceFileCount: 0, classNames };
|
|
38
62
|
}
|
|
39
63
|
const project = new import_ts_morph.Project({
|
|
40
64
|
skipAddingFilesFromTsConfig: true,
|
|
@@ -44,34 +68,44 @@ function loadJsDoc(srcGlobs) {
|
|
|
44
68
|
skipLibCheck: true
|
|
45
69
|
}
|
|
46
70
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
for (const prop of cls.getProperties()) {
|
|
60
|
-
const propDocs = prop.getJsDocs();
|
|
61
|
-
if (propDocs.length === 0) {
|
|
71
|
+
for (const filePath of filePaths) {
|
|
72
|
+
try {
|
|
73
|
+
project.addSourceFilesAtPaths(filePath);
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const sourceFiles = project.getSourceFiles();
|
|
78
|
+
for (const sourceFile of sourceFiles) {
|
|
79
|
+
try {
|
|
80
|
+
for (const cls of sourceFile.getClasses()) {
|
|
81
|
+
const className = cls.getName();
|
|
82
|
+
if (!className) {
|
|
62
83
|
continue;
|
|
63
84
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
classNames.add(className);
|
|
86
|
+
const classDocs = cls.getJsDocs();
|
|
87
|
+
if (classDocs.length > 0) {
|
|
88
|
+
entities.set(className, parseEntityJsDoc(classDocs));
|
|
89
|
+
}
|
|
90
|
+
const propMap = /* @__PURE__ */ new Map();
|
|
91
|
+
for (const prop of cls.getProperties()) {
|
|
92
|
+
const propDocs = prop.getJsDocs();
|
|
93
|
+
if (propDocs.length === 0) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const info = parsePropJsDoc(propDocs);
|
|
97
|
+
if (info.description !== void 0 || info.atLeastOne) {
|
|
98
|
+
propMap.set(prop.getName(), info);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (propMap.size > 0) {
|
|
102
|
+
props.set(className, propMap);
|
|
67
103
|
}
|
|
68
104
|
}
|
|
69
|
-
|
|
70
|
-
props.set(className, propMap);
|
|
71
|
-
}
|
|
105
|
+
} catch {
|
|
72
106
|
}
|
|
73
107
|
}
|
|
74
|
-
return { entities, props };
|
|
108
|
+
return { entities, props, sourceFileCount: sourceFiles.length, classNames };
|
|
75
109
|
}
|
|
76
110
|
function parseEntityJsDoc(jsDocs) {
|
|
77
111
|
const namespaces = [];
|
|
@@ -106,17 +140,25 @@ function parseEntityJsDoc(jsDocs) {
|
|
|
106
140
|
hidden
|
|
107
141
|
};
|
|
108
142
|
}
|
|
109
|
-
function
|
|
143
|
+
function parsePropJsDoc(jsDocs) {
|
|
144
|
+
let description;
|
|
145
|
+
let atLeastOne = false;
|
|
110
146
|
for (const doc of jsDocs) {
|
|
111
147
|
const desc = doc.getDescription().trim();
|
|
112
|
-
if (desc) {
|
|
113
|
-
|
|
148
|
+
if (desc && description === void 0) {
|
|
149
|
+
description = desc;
|
|
150
|
+
}
|
|
151
|
+
for (const tag of doc.getTags()) {
|
|
152
|
+
if (tag.getTagName() === "atLeastOne") {
|
|
153
|
+
atLeastOne = true;
|
|
154
|
+
}
|
|
114
155
|
}
|
|
115
156
|
}
|
|
116
|
-
return void 0;
|
|
157
|
+
return { ...description !== void 0 && { description }, atLeastOne };
|
|
117
158
|
}
|
|
118
159
|
|
|
119
160
|
// src/metadata/load.ts
|
|
161
|
+
var path = __toESM(require("path"), 1);
|
|
120
162
|
var import_core = require("@mikro-orm/core");
|
|
121
163
|
var MetadataLoadError = class extends Error {
|
|
122
164
|
constructor(message, cause) {
|
|
@@ -125,13 +167,46 @@ var MetadataLoadError = class extends Error {
|
|
|
125
167
|
this.name = "MetadataLoadError";
|
|
126
168
|
}
|
|
127
169
|
};
|
|
170
|
+
async function closeDiscoveryResources(orm) {
|
|
171
|
+
await orm.config.getMetadataCacheAdapter()?.close?.();
|
|
172
|
+
await orm.config.getResultCacheAdapter()?.close?.();
|
|
173
|
+
}
|
|
174
|
+
function collectEntitySchemaNames(options) {
|
|
175
|
+
const configuredEntities = [...options.entities ?? [], ...options.entitiesTs ?? []];
|
|
176
|
+
const names = [];
|
|
177
|
+
for (const entity of configuredEntities) {
|
|
178
|
+
if (entity instanceof import_core.EntitySchema) {
|
|
179
|
+
names.push(entity.meta.className);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (entity !== null && typeof entity === "object" && "schema" in entity && entity.schema instanceof import_core.EntitySchema) {
|
|
183
|
+
names.push(entity.schema.meta.className);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return names;
|
|
187
|
+
}
|
|
188
|
+
function assertNoEntitySchemaEntities(options) {
|
|
189
|
+
const schemaNames = collectEntitySchemaNames(options);
|
|
190
|
+
if (schemaNames.length === 0) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
throw new MetadataLoadError(
|
|
194
|
+
`EntitySchema-defined entities are not currently supported: ${schemaNames.join(", ")}.
|
|
195
|
+
Use decorator-based @Entity() classes instead.`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
128
198
|
async function loadEntityMetadata(options) {
|
|
199
|
+
assertNoEntitySchemaEntities(options);
|
|
129
200
|
let orm;
|
|
130
201
|
try {
|
|
131
202
|
orm = await import_core.MikroORM.init({
|
|
132
203
|
...options,
|
|
133
204
|
debug: false,
|
|
134
|
-
connect: false
|
|
205
|
+
connect: false,
|
|
206
|
+
// Always disable the metadata cache for one-shot doc runs so the project
|
|
207
|
+
// is never littered with a temp/ folder, regardless of how metadataProvider
|
|
208
|
+
// was configured.
|
|
209
|
+
metadataCache: { ...options.metadataCache, enabled: false }
|
|
135
210
|
});
|
|
136
211
|
} catch (cause) {
|
|
137
212
|
throw new MetadataLoadError(
|
|
@@ -139,23 +214,80 @@ async function loadEntityMetadata(options) {
|
|
|
139
214
|
cause
|
|
140
215
|
);
|
|
141
216
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
217
|
+
try {
|
|
218
|
+
const all = Object.values(orm.getMetadata().getAll());
|
|
219
|
+
if (all.length === 0) {
|
|
220
|
+
throw new MetadataLoadError(
|
|
221
|
+
"No entities were discovered. Check that your config specifies at least one entity path or class."
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const baseDir = orm.config.get("baseDir");
|
|
225
|
+
const sourcePaths = [...new Set(all.filter((m) => m.path).map((m) => path.resolve(baseDir, m.path)))];
|
|
226
|
+
return { metas: all, sourcePaths };
|
|
227
|
+
} finally {
|
|
228
|
+
await closeDiscoveryResources(orm);
|
|
147
229
|
}
|
|
148
|
-
return all;
|
|
149
230
|
}
|
|
150
231
|
|
|
232
|
+
// src/model/build.ts
|
|
233
|
+
var import_core3 = require("@mikro-orm/core");
|
|
234
|
+
|
|
151
235
|
// src/render/mermaid.ts
|
|
152
236
|
var import_core2 = require("@mikro-orm/core");
|
|
237
|
+
|
|
238
|
+
// src/render/escape.ts
|
|
239
|
+
var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#]/g;
|
|
240
|
+
var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
|
|
241
|
+
function normalizeInlineText(value) {
|
|
242
|
+
return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
243
|
+
}
|
|
244
|
+
function splitNormalizedLines(value) {
|
|
245
|
+
return value.replace(/\r\n?/g, "\n").split("\n");
|
|
246
|
+
}
|
|
247
|
+
function escapeHtmlText(value) {
|
|
248
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
249
|
+
}
|
|
250
|
+
function escapeMarkdownInline(value) {
|
|
251
|
+
return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&");
|
|
252
|
+
}
|
|
253
|
+
function escapeMarkdownTableCell(value) {
|
|
254
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
|
|
255
|
+
}
|
|
256
|
+
function escapeMarkdownParagraph(value) {
|
|
257
|
+
return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
|
|
258
|
+
}
|
|
259
|
+
function renderMarkdownBlockQuote(value) {
|
|
260
|
+
return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
|
|
261
|
+
}
|
|
262
|
+
function renderMarkdownInlineCode(value) {
|
|
263
|
+
const normalized = normalizeInlineText(value);
|
|
264
|
+
const backtickRuns = normalized.match(/`+/g) ?? [];
|
|
265
|
+
const longestRun = Math.max(0, ...backtickRuns.map((run2) => run2.length));
|
|
266
|
+
const fence = "`".repeat(longestRun + 1);
|
|
267
|
+
const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
|
|
268
|
+
const content = needsPadding ? ` ${normalized} ` : normalized;
|
|
269
|
+
return `${fence}${content}${fence}`;
|
|
270
|
+
}
|
|
271
|
+
function toMermaidIdentifier(value) {
|
|
272
|
+
const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
|
|
273
|
+
const identifier = normalized === "" ? "_" : normalized;
|
|
274
|
+
return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
|
|
275
|
+
}
|
|
276
|
+
function escapeMermaidQuotedText(value) {
|
|
277
|
+
return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
278
|
+
}
|
|
279
|
+
function toMarkdownAnchor(value) {
|
|
280
|
+
return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/render/mermaid.ts
|
|
153
284
|
var FORMULA_DUMMY_TABLE = {
|
|
154
285
|
alias: "e0",
|
|
155
286
|
name: "",
|
|
156
287
|
qualifiedName: "",
|
|
157
288
|
toString: () => "e0"
|
|
158
289
|
};
|
|
290
|
+
var UNRESOLVED_FORMULA = "<unresolved>";
|
|
159
291
|
function buildDiagramModel(metas) {
|
|
160
292
|
const metaByClass = new Map(metas.map((m) => [m.className, m]));
|
|
161
293
|
const entities = metas.filter((meta) => !meta.pivotTable && !meta.embeddable).map((meta) => buildEntityModel(meta, metaByClass));
|
|
@@ -163,17 +295,14 @@ function buildDiagramModel(metas) {
|
|
|
163
295
|
return { entities, relations };
|
|
164
296
|
}
|
|
165
297
|
function buildEntityModel(meta, metaByClass) {
|
|
166
|
-
const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.
|
|
298
|
+
const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.extends;
|
|
167
299
|
const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== void 0;
|
|
168
300
|
const columns = [];
|
|
169
301
|
for (const prop of Object.values(meta.properties)) {
|
|
170
302
|
if (isStiRoot && prop.inherited === true) {
|
|
171
303
|
continue;
|
|
172
304
|
}
|
|
173
|
-
|
|
174
|
-
if (col !== null) {
|
|
175
|
-
columns.push(col);
|
|
176
|
-
}
|
|
305
|
+
columns.push(...buildColumns(prop, metaByClass, meta));
|
|
177
306
|
}
|
|
178
307
|
return {
|
|
179
308
|
className: meta.className,
|
|
@@ -183,47 +312,73 @@ function buildEntityModel(meta, metaByClass) {
|
|
|
183
312
|
isEmbeddable: meta.embeddable === true,
|
|
184
313
|
...isStiRoot && { discriminatorColumn: meta.discriminatorColumn },
|
|
185
314
|
...isStiChild && { extendsEntity: meta.extends },
|
|
315
|
+
...isStiChild && meta.discriminatorValue !== void 0 && { discriminatorValue: String(meta.discriminatorValue) },
|
|
186
316
|
constraints: buildConstraints(meta)
|
|
187
317
|
};
|
|
188
318
|
}
|
|
189
|
-
function
|
|
319
|
+
function buildColumns(prop, metaByClass, owningMeta) {
|
|
190
320
|
if (prop.kind === import_core2.ReferenceKind.EMBEDDED) {
|
|
191
|
-
|
|
321
|
+
if (prop.object === true || prop.array === true) {
|
|
322
|
+
return [
|
|
323
|
+
{
|
|
324
|
+
propName: prop.name,
|
|
325
|
+
fieldName: prop.fieldNames?.[0] ?? prop.name,
|
|
326
|
+
type: "json",
|
|
327
|
+
isPrimary: false,
|
|
328
|
+
isForeignKey: false,
|
|
329
|
+
isUnique: prop.unique === true,
|
|
330
|
+
isNullable: prop.nullable === true,
|
|
331
|
+
...prop.comment !== void 0 && { comment: prop.comment },
|
|
332
|
+
embeddedIn: prop.array === true ? `${prop.type}[]` : prop.type
|
|
333
|
+
}
|
|
334
|
+
];
|
|
335
|
+
}
|
|
336
|
+
return [];
|
|
192
337
|
}
|
|
193
338
|
if (prop.kind === import_core2.ReferenceKind.SCALAR) {
|
|
339
|
+
if (prop.object === true && prop.embedded !== void 0) {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
194
342
|
const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
|
|
195
343
|
let embeddedIn;
|
|
344
|
+
let embeddedPropName;
|
|
196
345
|
if (prop.embedded !== void 0) {
|
|
197
346
|
const parentPropName = prop.embedded[0];
|
|
198
347
|
embeddedIn = owningMeta.properties[parentPropName]?.type;
|
|
348
|
+
embeddedPropName = prop.embedded[1];
|
|
199
349
|
}
|
|
200
350
|
const isDiscriminator = owningMeta.discriminatorColumn !== void 0 && prop.name === owningMeta.discriminatorColumn;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
351
|
+
const enumItems = prop.enum === true && Array.isArray(prop.items) && prop.items.length > 0 ? prop.items.map((item) => String(item)) : void 0;
|
|
352
|
+
return [
|
|
353
|
+
{
|
|
354
|
+
propName: prop.name,
|
|
355
|
+
fieldName: prop.fieldNames?.[0] ?? prop.name,
|
|
356
|
+
// Store the original type (e.g. `varchar(255)`); the Mermaid renderer
|
|
357
|
+
// sanitizes it for diagram identifiers, while the markdown table shows
|
|
358
|
+
// it verbatim. Guard against a missing type so downstream string
|
|
359
|
+
// handling never sees undefined (matches the FK path's defaulting).
|
|
360
|
+
type: prop.type ?? "unknown",
|
|
361
|
+
isPrimary: prop.primary === true,
|
|
362
|
+
isForeignKey: false,
|
|
363
|
+
isUnique: prop.unique === true,
|
|
364
|
+
isNullable: prop.nullable === true,
|
|
365
|
+
...prop.comment !== void 0 && { comment: prop.comment },
|
|
366
|
+
...formulaExpr !== void 0 && { formula: formulaExpr },
|
|
367
|
+
...embeddedIn !== void 0 && { embeddedIn },
|
|
368
|
+
...embeddedPropName !== void 0 && { embeddedPropName },
|
|
369
|
+
...isDiscriminator && { isDiscriminator: true },
|
|
370
|
+
...enumItems !== void 0 && { enumItems }
|
|
371
|
+
}
|
|
372
|
+
];
|
|
213
373
|
}
|
|
214
374
|
if (prop.kind === import_core2.ReferenceKind.MANY_TO_ONE || prop.kind === import_core2.ReferenceKind.ONE_TO_ONE && prop.owner === true) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
isPrimary: false,
|
|
221
|
-
isForeignKey: true,
|
|
222
|
-
isUnique: prop.unique === true,
|
|
223
|
-
isNullable: prop.nullable === true
|
|
224
|
-
};
|
|
375
|
+
const cols = buildForeignKeyColumns(prop, metaByClass);
|
|
376
|
+
if (prop.type === owningMeta.className) {
|
|
377
|
+
return cols.map((col) => ({ ...col, isSelfReference: true }));
|
|
378
|
+
}
|
|
379
|
+
return cols;
|
|
225
380
|
}
|
|
226
|
-
return
|
|
381
|
+
return [];
|
|
227
382
|
}
|
|
228
383
|
function resolveFormulaExpr(cb) {
|
|
229
384
|
try {
|
|
@@ -233,18 +388,69 @@ function resolveFormulaExpr(cb) {
|
|
|
233
388
|
get: (_target, key) => typeof key === "string" ? key : ""
|
|
234
389
|
}
|
|
235
390
|
);
|
|
236
|
-
|
|
391
|
+
const result = cb(FORMULA_DUMMY_TABLE, cols);
|
|
392
|
+
return typeof result === "string" ? result : String(result);
|
|
237
393
|
} catch {
|
|
238
|
-
return
|
|
394
|
+
return UNRESOLVED_FORMULA;
|
|
239
395
|
}
|
|
240
396
|
}
|
|
241
|
-
function
|
|
242
|
-
const
|
|
397
|
+
function buildForeignKeyColumns(prop, metaByClass) {
|
|
398
|
+
const fieldNames = prop.fieldNames && prop.fieldNames.length > 0 ? prop.fieldNames : [`${prop.name}_id`];
|
|
399
|
+
const fkTypes = resolveFkTypes(prop, metaByClass, fieldNames.length);
|
|
400
|
+
return fieldNames.map((fieldName, index) => ({
|
|
401
|
+
propName: prop.name,
|
|
402
|
+
fieldName,
|
|
403
|
+
type: fkTypes[index] ?? fkTypes[0] ?? "integer",
|
|
404
|
+
isPrimary: prop.primary === true,
|
|
405
|
+
isForeignKey: true,
|
|
406
|
+
isUnique: prop.unique === true,
|
|
407
|
+
isNullable: prop.nullable === true,
|
|
408
|
+
...prop.comment !== void 0 && { comment: prop.comment },
|
|
409
|
+
referencedEntity: prop.type
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
function resolveFkTypes(prop, metaByClass, fieldNameCount) {
|
|
413
|
+
const refMeta = metaByClass.get(prop.type);
|
|
243
414
|
if (!refMeta) {
|
|
415
|
+
return Array.from({ length: fieldNameCount }, () => "integer");
|
|
416
|
+
}
|
|
417
|
+
const primaryProps = getPrimaryProps(refMeta);
|
|
418
|
+
const referencedColumnNames = prop.referencedColumnNames ?? [];
|
|
419
|
+
return Array.from({ length: fieldNameCount }, (_value, index) => {
|
|
420
|
+
const referencedColumnName = referencedColumnNames[index];
|
|
421
|
+
const pkProp = referencedColumnName !== void 0 ? primaryProps.find((candidate) => candidate.fieldNames.includes(referencedColumnName)) : void 0;
|
|
422
|
+
const resolvedProp = pkProp ?? primaryProps[index] ?? primaryProps[0];
|
|
423
|
+
const rawType = resolvedProp?.type ?? "integer";
|
|
424
|
+
const pkIndex = resolvedProp !== void 0 ? primaryProps.indexOf(resolvedProp) : 0;
|
|
425
|
+
return resolveScalarType(rawType, metaByClass, pkIndex);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function resolveScalarType(type, metaByClass, pkIndex = 0, depth = 0) {
|
|
429
|
+
if (depth >= 5) {
|
|
244
430
|
return "integer";
|
|
245
431
|
}
|
|
246
|
-
const
|
|
247
|
-
|
|
432
|
+
const refMeta = metaByClass.get(type);
|
|
433
|
+
if (!refMeta) {
|
|
434
|
+
return type;
|
|
435
|
+
}
|
|
436
|
+
const primaryProps = getPrimaryProps(refMeta);
|
|
437
|
+
if (primaryProps.length === 0) {
|
|
438
|
+
return type;
|
|
439
|
+
}
|
|
440
|
+
const targetProp = primaryProps[pkIndex] ?? primaryProps[0];
|
|
441
|
+
const nextType = targetProp?.type ?? type;
|
|
442
|
+
if (nextType === type) {
|
|
443
|
+
return type;
|
|
444
|
+
}
|
|
445
|
+
return resolveScalarType(nextType, metaByClass, pkIndex, depth + 1);
|
|
446
|
+
}
|
|
447
|
+
function getPrimaryProps(meta) {
|
|
448
|
+
const primaryKeys = meta.primaryKeys ?? [];
|
|
449
|
+
const orderedPrimaryProps = primaryKeys.map((key) => meta.properties[String(key)]).filter((prop) => prop !== void 0);
|
|
450
|
+
if (orderedPrimaryProps.length > 0) {
|
|
451
|
+
return orderedPrimaryProps;
|
|
452
|
+
}
|
|
453
|
+
return Object.values(meta.properties).filter((prop) => prop.primary === true);
|
|
248
454
|
}
|
|
249
455
|
function buildConstraints(meta) {
|
|
250
456
|
const result = [];
|
|
@@ -252,7 +458,7 @@ function buildConstraints(meta) {
|
|
|
252
458
|
const props = idx.properties;
|
|
253
459
|
result.push({
|
|
254
460
|
type: "index",
|
|
255
|
-
properties:
|
|
461
|
+
properties: resolveConstraintProperties(meta, props),
|
|
256
462
|
...idx.name !== void 0 && { name: idx.name }
|
|
257
463
|
});
|
|
258
464
|
}
|
|
@@ -260,7 +466,7 @@ function buildConstraints(meta) {
|
|
|
260
466
|
const props = uniq.properties;
|
|
261
467
|
result.push({
|
|
262
468
|
type: "unique",
|
|
263
|
-
properties:
|
|
469
|
+
properties: resolveConstraintProperties(meta, props),
|
|
264
470
|
...uniq.name !== void 0 && { name: uniq.name }
|
|
265
471
|
});
|
|
266
472
|
}
|
|
@@ -277,6 +483,19 @@ function buildConstraints(meta) {
|
|
|
277
483
|
}
|
|
278
484
|
return result;
|
|
279
485
|
}
|
|
486
|
+
function resolveConstraintProperties(meta, props) {
|
|
487
|
+
const propNames = Array.isArray(props) ? props : props !== void 0 ? [props] : [];
|
|
488
|
+
return propNames.flatMap((propName) => {
|
|
489
|
+
const prop = meta.properties[String(propName)];
|
|
490
|
+
if (prop === void 0) {
|
|
491
|
+
return [String(propName)];
|
|
492
|
+
}
|
|
493
|
+
if (prop.fieldNames !== void 0 && prop.fieldNames.length > 0) {
|
|
494
|
+
return prop.fieldNames;
|
|
495
|
+
}
|
|
496
|
+
return [prop.name];
|
|
497
|
+
});
|
|
498
|
+
}
|
|
280
499
|
function buildRelationEdges(metas) {
|
|
281
500
|
const edges = [];
|
|
282
501
|
for (const meta of metas) {
|
|
@@ -295,6 +514,9 @@ function buildRelationEdges(metas) {
|
|
|
295
514
|
function buildEdge(fromEntity, prop) {
|
|
296
515
|
const isNullable = prop.nullable === true;
|
|
297
516
|
if (prop.kind === import_core2.ReferenceKind.MANY_TO_ONE) {
|
|
517
|
+
if (prop.type === fromEntity) {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
298
520
|
return {
|
|
299
521
|
fromEntity,
|
|
300
522
|
toEntity: prop.type,
|
|
@@ -323,17 +545,41 @@ function buildEdge(fromEntity, prop) {
|
|
|
323
545
|
}
|
|
324
546
|
return null;
|
|
325
547
|
}
|
|
548
|
+
function normalizeType(type) {
|
|
549
|
+
const t = type.toLowerCase().trim();
|
|
550
|
+
if (t === "uuid" || t === "text" || t === "string" || t.startsWith("varchar")) {
|
|
551
|
+
return "string";
|
|
552
|
+
}
|
|
553
|
+
if (t === "timestamptz" || t === "timestamp" || t === "datetime") {
|
|
554
|
+
return "datetime";
|
|
555
|
+
}
|
|
556
|
+
if (t === "integer" || t === "int" || t === "bigint" || t === "smallint") {
|
|
557
|
+
return "integer";
|
|
558
|
+
}
|
|
559
|
+
if (t === "doubletype" || t === "double precision" || t === "double" || t === "float" || t === "decimal") {
|
|
560
|
+
return "float";
|
|
561
|
+
}
|
|
562
|
+
if (t === "boolean" || t === "bool") {
|
|
563
|
+
return "boolean";
|
|
564
|
+
}
|
|
565
|
+
if (t === "jsonb") {
|
|
566
|
+
return "json";
|
|
567
|
+
}
|
|
568
|
+
return type;
|
|
569
|
+
}
|
|
326
570
|
function renderErDiagram(model) {
|
|
327
571
|
const lines = ["erDiagram"];
|
|
328
572
|
for (const entity of model.entities) {
|
|
329
|
-
lines.push(` ${entity.className} {`);
|
|
573
|
+
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
330
574
|
for (const col of entity.columns) {
|
|
331
575
|
lines.push(` ${renderColumnLine(col)}`);
|
|
332
576
|
}
|
|
333
577
|
lines.push(" }");
|
|
334
578
|
}
|
|
335
579
|
for (const rel of model.relations) {
|
|
336
|
-
lines.push(
|
|
580
|
+
lines.push(
|
|
581
|
+
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
582
|
+
);
|
|
337
583
|
}
|
|
338
584
|
return lines.join("\n");
|
|
339
585
|
}
|
|
@@ -341,8 +587,6 @@ function renderColumnLine(col) {
|
|
|
341
587
|
let qualifier = "";
|
|
342
588
|
if (col.isPrimary) {
|
|
343
589
|
qualifier = " PK";
|
|
344
|
-
} else if (col.isForeignKey) {
|
|
345
|
-
qualifier = " FK";
|
|
346
590
|
} else if (col.isUnique) {
|
|
347
591
|
qualifier = " UK";
|
|
348
592
|
}
|
|
@@ -353,27 +597,38 @@ function renderColumnLine(col) {
|
|
|
353
597
|
comment = "discriminator";
|
|
354
598
|
} else if (col.embeddedIn !== void 0) {
|
|
355
599
|
comment = `[${col.embeddedIn}]`;
|
|
356
|
-
} else if (col.
|
|
357
|
-
comment =
|
|
600
|
+
} else if (col.isSelfReference) {
|
|
601
|
+
comment = "self-ref";
|
|
358
602
|
}
|
|
359
|
-
const commentStr = comment !== void 0 ? ` "${comment}"` : "";
|
|
360
|
-
return `${col.type} ${col.fieldName}${qualifier}${commentStr}`;
|
|
361
|
-
}
|
|
362
|
-
function normalizeType(type) {
|
|
363
|
-
return type.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
603
|
+
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
604
|
+
return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
364
605
|
}
|
|
365
606
|
|
|
366
607
|
// src/model/build.ts
|
|
367
|
-
|
|
368
|
-
|
|
608
|
+
var FROM_ONE_OR_MORE = "}|";
|
|
609
|
+
var TO_ONE_OR_MORE = "|{";
|
|
610
|
+
function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
611
|
+
const { entities: diagramEntities, relations } = buildDiagramModel(metas);
|
|
612
|
+
const allRelations = applyAtLeastOne(relations, metas, jsDocResult.props, onWarn);
|
|
613
|
+
const hiddenClasses = /* @__PURE__ */ new Set();
|
|
614
|
+
for (const model of diagramEntities) {
|
|
615
|
+
if (jsDocResult.entities.get(model.className)?.hidden) {
|
|
616
|
+
hiddenClasses.add(model.className);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
369
619
|
const enrichedByClass = /* @__PURE__ */ new Map();
|
|
370
620
|
for (const model of diagramEntities) {
|
|
371
621
|
const jsDoc = jsDocResult.entities.get(model.className);
|
|
372
622
|
if (jsDoc?.hidden) {
|
|
373
623
|
continue;
|
|
374
624
|
}
|
|
375
|
-
const
|
|
376
|
-
|
|
625
|
+
const columns = model.columns.filter(
|
|
626
|
+
(col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
|
|
627
|
+
);
|
|
628
|
+
const visibleModel = columns.length === model.columns.length ? model : { ...model, columns };
|
|
629
|
+
const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
|
|
630
|
+
const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
|
|
631
|
+
enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
|
|
377
632
|
}
|
|
378
633
|
const groupNames = /* @__PURE__ */ new Set();
|
|
379
634
|
let anyUntagged = false;
|
|
@@ -393,9 +648,16 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
393
648
|
const groups = [];
|
|
394
649
|
for (const groupName of groupNames) {
|
|
395
650
|
const isDefault = groupName === "default";
|
|
396
|
-
const erdEntities = [...enrichedByClass.values()].filter(
|
|
397
|
-
|
|
398
|
-
|
|
651
|
+
const erdEntities = [...enrichedByClass.values()].filter(({ jsDoc }) => belongsToGroupForErd(jsDoc, groupName, isDefault)).map((entity) => {
|
|
652
|
+
if (isCrossNamespaceInGroup(entity.jsDoc, groupName, isDefault)) {
|
|
653
|
+
const pkColumns = entity.model.columns.filter((col) => col.isPrimary);
|
|
654
|
+
if (pkColumns.length === 0) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
return { ...entity, model: { ...entity.model, columns: pkColumns } };
|
|
658
|
+
}
|
|
659
|
+
return entity;
|
|
660
|
+
}).filter((entity) => entity !== null);
|
|
399
661
|
const textEntities = [...enrichedByClass.values()].filter(
|
|
400
662
|
({ jsDoc }) => belongsToGroupForText(jsDoc, groupName, isDefault)
|
|
401
663
|
);
|
|
@@ -414,6 +676,65 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
414
676
|
});
|
|
415
677
|
return { title, groups, ...description !== void 0 && { description } };
|
|
416
678
|
}
|
|
679
|
+
function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
|
|
680
|
+
const merged = new Map(ownPropDocs);
|
|
681
|
+
for (const col of columns) {
|
|
682
|
+
if (merged.has(col.propName) || col.embeddedIn === void 0 || col.embeddedPropName === void 0) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const info = allPropDocs.get(col.embeddedIn)?.get(col.embeddedPropName);
|
|
686
|
+
if (info) {
|
|
687
|
+
merged.set(col.propName, info);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return merged;
|
|
691
|
+
}
|
|
692
|
+
function applyAtLeastOne(relations, metas, props, onWarn) {
|
|
693
|
+
const adjusted = relations.map((edge) => ({ ...edge }));
|
|
694
|
+
const metaByClass = new Map(metas.map((m) => [m.className, m]));
|
|
695
|
+
for (const [className, propMap] of props) {
|
|
696
|
+
const meta = metaByClass.get(className);
|
|
697
|
+
if (!meta) {
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
for (const [propName, info] of propMap) {
|
|
701
|
+
if (!info.atLeastOne) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
const prop = meta.properties[propName];
|
|
705
|
+
if (!prop) {
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
let edge;
|
|
709
|
+
if (prop.kind === import_core3.ReferenceKind.ONE_TO_MANY && prop.mappedBy) {
|
|
710
|
+
edge = adjusted.find(
|
|
711
|
+
(e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
|
|
712
|
+
);
|
|
713
|
+
if (edge) {
|
|
714
|
+
edge.fromCardinality = FROM_ONE_OR_MORE;
|
|
715
|
+
}
|
|
716
|
+
} else if (prop.kind === import_core3.ReferenceKind.MANY_TO_MANY && prop.owner === true) {
|
|
717
|
+
edge = adjusted.find((e) => e.fromEntity === className && e.toEntity === prop.type && e.label === propName);
|
|
718
|
+
if (edge) {
|
|
719
|
+
edge.toCardinality = TO_ONE_OR_MORE;
|
|
720
|
+
}
|
|
721
|
+
} else if (prop.kind === import_core3.ReferenceKind.MANY_TO_MANY && prop.mappedBy) {
|
|
722
|
+
edge = adjusted.find(
|
|
723
|
+
(e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
|
|
724
|
+
);
|
|
725
|
+
if (edge) {
|
|
726
|
+
edge.fromCardinality = FROM_ONE_OR_MORE;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (!edge) {
|
|
730
|
+
onWarn?.(
|
|
731
|
+
`@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.`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return adjusted;
|
|
737
|
+
}
|
|
417
738
|
function hasNoNamespaceTags(jsDoc) {
|
|
418
739
|
if (!jsDoc) {
|
|
419
740
|
return true;
|
|
@@ -438,20 +759,62 @@ function belongsToGroupForText(jsDoc, groupName, isDefault) {
|
|
|
438
759
|
}
|
|
439
760
|
return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
|
|
440
761
|
}
|
|
762
|
+
function isCrossNamespaceInGroup(jsDoc, groupName, isDefault) {
|
|
763
|
+
if (isDefault || !jsDoc) {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
return jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/provider.ts
|
|
770
|
+
async function withTsMorphMetadataProvider(options, onWarn) {
|
|
771
|
+
if (options.metadataProvider !== void 0) {
|
|
772
|
+
return options;
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
const { TsMorphMetadataProvider } = await import("@mikro-orm/reflection");
|
|
776
|
+
return { ...options, metadataProvider: TsMorphMetadataProvider };
|
|
777
|
+
} catch (err) {
|
|
778
|
+
const code = err !== null && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
779
|
+
const isNotInstalled = code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND";
|
|
780
|
+
if (!isNotInstalled && onWarn) {
|
|
781
|
+
onWarn(
|
|
782
|
+
`@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.`
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
return options;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
441
788
|
|
|
442
789
|
// src/render/markdown.ts
|
|
443
790
|
function renderMarkdown(docModel) {
|
|
444
|
-
const sections = [`# ${docModel.title}`];
|
|
791
|
+
const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
|
|
445
792
|
if (docModel.description) {
|
|
446
|
-
sections.push(docModel.description);
|
|
793
|
+
sections.push(escapeMarkdownParagraph(docModel.description));
|
|
794
|
+
}
|
|
795
|
+
if (docModel.groups.length > 1) {
|
|
796
|
+
sections.push(renderTableOfContents(docModel.groups));
|
|
447
797
|
}
|
|
448
798
|
for (const group of docModel.groups) {
|
|
449
799
|
sections.push(renderGroupSection(group));
|
|
450
800
|
}
|
|
451
801
|
return sections.join("\n\n");
|
|
452
802
|
}
|
|
803
|
+
function renderBulletSection(header, items, renderItem) {
|
|
804
|
+
const lines = [header, ""];
|
|
805
|
+
for (const item of items) {
|
|
806
|
+
lines.push(renderItem(item));
|
|
807
|
+
}
|
|
808
|
+
return lines.join("\n");
|
|
809
|
+
}
|
|
810
|
+
function renderTableOfContents(groups) {
|
|
811
|
+
return renderBulletSection("## Contents", groups, (group) => {
|
|
812
|
+
const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
|
|
813
|
+
return `- [${label}](#${toMarkdownAnchor(group.name)})`;
|
|
814
|
+
});
|
|
815
|
+
}
|
|
453
816
|
function renderGroupSection(group) {
|
|
454
|
-
const parts = [`## ${group.name}`];
|
|
817
|
+
const parts = [`## ${escapeMarkdownInline(group.name)}`];
|
|
455
818
|
if (group.erdEntities.length > 0) {
|
|
456
819
|
const diagramModel = {
|
|
457
820
|
entities: group.erdEntities.map((e) => e.model),
|
|
@@ -465,14 +828,18 @@ function renderGroupSection(group) {
|
|
|
465
828
|
return parts.join("\n\n");
|
|
466
829
|
}
|
|
467
830
|
function renderEntitySection(entity) {
|
|
468
|
-
const parts = [`### ${entity.model.className}`];
|
|
831
|
+
const parts = [`### ${escapeMarkdownInline(entity.model.className)}`];
|
|
832
|
+
parts.push(`*Table: ${renderMarkdownInlineCode(entity.model.tableName)}*`);
|
|
469
833
|
if (entity.jsDoc?.description) {
|
|
470
|
-
parts.push(
|
|
834
|
+
parts.push(renderMarkdownBlockQuote(entity.jsDoc.description));
|
|
471
835
|
}
|
|
472
836
|
if (entity.model.discriminatorColumn) {
|
|
473
|
-
parts.push(`*STI root \u2014 discriminator column:
|
|
837
|
+
parts.push(`*STI root \u2014 discriminator column: ${renderMarkdownInlineCode(entity.model.discriminatorColumn)}*`);
|
|
474
838
|
} else if (entity.model.extendsEntity) {
|
|
475
|
-
|
|
839
|
+
const discValue = entity.model.discriminatorValue !== void 0 ? `, discriminator value: ${renderMarkdownInlineCode(entity.model.discriminatorValue)}` : "";
|
|
840
|
+
parts.push(
|
|
841
|
+
`*Extends ${renderMarkdownInlineCode(entity.model.extendsEntity)} (Single Table Inheritance${discValue})*`
|
|
842
|
+
);
|
|
476
843
|
}
|
|
477
844
|
if (entity.model.columns.length > 0) {
|
|
478
845
|
parts.push(renderColumnTable(entity));
|
|
@@ -480,6 +847,10 @@ function renderEntitySection(entity) {
|
|
|
480
847
|
if (entity.model.constraints.length > 0) {
|
|
481
848
|
parts.push(renderConstraints(entity.model.constraints));
|
|
482
849
|
}
|
|
850
|
+
const computedColumns = entity.model.columns.filter((col) => col.formula !== void 0);
|
|
851
|
+
if (computedColumns.length > 0) {
|
|
852
|
+
parts.push(renderComputedColumns(computedColumns));
|
|
853
|
+
}
|
|
483
854
|
return parts.join("\n\n");
|
|
484
855
|
}
|
|
485
856
|
function renderColumnTable(entity) {
|
|
@@ -488,24 +859,27 @@ function renderColumnTable(entity) {
|
|
|
488
859
|
const rows = entity.model.columns.map((col) => {
|
|
489
860
|
const key = resolveColumnKey(col);
|
|
490
861
|
const nullable = col.isNullable && !col.isPrimary ? "Y" : "";
|
|
491
|
-
const
|
|
492
|
-
|
|
862
|
+
const docDesc = entity.propDocs.get(col.propName)?.description ?? col.comment ?? "";
|
|
863
|
+
const enumDesc = col.enumItems !== void 0 ? `One of: ${col.enumItems.join(", ")}` : "";
|
|
864
|
+
const desc = [docDesc, enumDesc].filter((part) => part !== "").join("\n");
|
|
865
|
+
return `| ${escapeMarkdownTableCell(col.fieldName)} | ${escapeMarkdownTableCell(normalizeType(col.type))} | ${escapeMarkdownTableCell(key)} | ${nullable} | ${escapeMarkdownTableCell(desc)} |`;
|
|
493
866
|
});
|
|
494
867
|
return [header, sep, ...rows].join("\n");
|
|
495
868
|
}
|
|
496
869
|
function resolveColumnKey(col) {
|
|
870
|
+
const fkKey = col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
|
|
871
|
+
if (col.isPrimary && col.isForeignKey) {
|
|
872
|
+
return `PK, ${fkKey}`;
|
|
873
|
+
}
|
|
497
874
|
if (col.isPrimary) {
|
|
498
875
|
return "PK";
|
|
499
876
|
}
|
|
500
877
|
if (col.isForeignKey) {
|
|
501
|
-
return
|
|
878
|
+
return fkKey;
|
|
502
879
|
}
|
|
503
880
|
if (col.isUnique) {
|
|
504
881
|
return "UK";
|
|
505
882
|
}
|
|
506
|
-
if (col.formula !== void 0) {
|
|
507
|
-
return `formula: ${col.formula}`;
|
|
508
|
-
}
|
|
509
883
|
if (col.isDiscriminator) {
|
|
510
884
|
return "discriminator";
|
|
511
885
|
}
|
|
@@ -514,43 +888,227 @@ function resolveColumnKey(col) {
|
|
|
514
888
|
}
|
|
515
889
|
return "";
|
|
516
890
|
}
|
|
891
|
+
function renderComputedColumns(columns) {
|
|
892
|
+
return renderBulletSection("**Computed columns:**", columns, (col) => {
|
|
893
|
+
const expr = col.formula ? `: ${renderMarkdownInlineCode(col.formula)}` : "";
|
|
894
|
+
return `- ${renderMarkdownInlineCode(col.fieldName)}${expr}`;
|
|
895
|
+
});
|
|
896
|
+
}
|
|
517
897
|
function renderConstraints(constraints) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const
|
|
898
|
+
return renderBulletSection("**Constraints:**", constraints, (c) => {
|
|
899
|
+
const name = c.name ? ` ${renderMarkdownInlineCode(c.name)}` : "";
|
|
900
|
+
const properties = c.properties.map(escapeMarkdownInline).join(", ");
|
|
521
901
|
if (c.type === "index") {
|
|
522
|
-
|
|
523
|
-
} else if (c.type === "unique") {
|
|
524
|
-
lines.push(`- Unique${name}: (${c.properties.join(", ")})`);
|
|
525
|
-
} else if (c.type === "check") {
|
|
526
|
-
lines.push(`- Check${name}: \`${c.expression ?? ""}\``);
|
|
902
|
+
return `- Index${name}: (${properties})`;
|
|
527
903
|
}
|
|
528
|
-
|
|
529
|
-
|
|
904
|
+
if (c.type === "unique") {
|
|
905
|
+
return `- Unique${name}: (${properties})`;
|
|
906
|
+
}
|
|
907
|
+
return `- Check${name}: ${renderMarkdownInlineCode(c.expression ?? "")}`;
|
|
908
|
+
});
|
|
530
909
|
}
|
|
531
910
|
|
|
532
911
|
// src/index.ts
|
|
912
|
+
var COMPILED_JS = /\.(c|m)?js$/i;
|
|
913
|
+
function resolveJsDocSources(sourcePaths, src, onWarn) {
|
|
914
|
+
if (src !== void 0 && src.length > 0) {
|
|
915
|
+
return src;
|
|
916
|
+
}
|
|
917
|
+
if (sourcePaths.some((p) => COMPILED_JS.test(p)) && onWarn) {
|
|
918
|
+
onWarn(
|
|
919
|
+
'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.'
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
return sourcePaths;
|
|
923
|
+
}
|
|
924
|
+
function assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn) {
|
|
925
|
+
if (jsDocResult.sourceFileCount === 0) {
|
|
926
|
+
throw new Error(
|
|
927
|
+
`No source files matched the explicit src paths: ${src.join(", ")}
|
|
928
|
+
Check the --src glob/path (or the \`src\` option). Without matching TypeScript sources, JSDoc tags such as @namespace and @hidden cannot be read.`
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
const isRenderable = (meta) => !meta.pivotTable && !meta.embeddable;
|
|
932
|
+
const missingConcrete = metas.filter((meta) => isRenderable(meta) && !meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
|
|
933
|
+
if (missingConcrete.length > 0) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`Explicit src paths did not include source declarations for discovered entities: ${missingConcrete.join(", ")}
|
|
936
|
+
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.`
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
if (onWarn) {
|
|
940
|
+
const missingAbstract = metas.filter((meta) => isRenderable(meta) && meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
|
|
941
|
+
if (missingAbstract.length > 0) {
|
|
942
|
+
onWarn(
|
|
943
|
+
`Abstract STI parent entities were not found in the explicit src paths: ${missingAbstract.join(", ")}
|
|
944
|
+
@hidden and @namespace tags for these entities will not be applied. Include their source files in --src to enable JSDoc tags for them.`
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function errorMessages(err) {
|
|
950
|
+
const messages = [];
|
|
951
|
+
const seen = /* @__PURE__ */ new Set();
|
|
952
|
+
let current = err;
|
|
953
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
954
|
+
seen.add(current);
|
|
955
|
+
messages.push(current.message);
|
|
956
|
+
current = current.cause;
|
|
957
|
+
}
|
|
958
|
+
return messages;
|
|
959
|
+
}
|
|
960
|
+
function isMissingTsMorphSourceFile(err) {
|
|
961
|
+
return errorMessages(err).some((message) => message.includes("Source file") && message.includes("not found"));
|
|
962
|
+
}
|
|
963
|
+
async function loadEntityMetadataWithTsMorphFallback(originalOrm, effectiveOrm) {
|
|
964
|
+
try {
|
|
965
|
+
return await loadEntityMetadata(effectiveOrm);
|
|
966
|
+
} catch (err) {
|
|
967
|
+
const wasAutoInjected = originalOrm.metadataProvider === void 0 && effectiveOrm.metadataProvider !== void 0;
|
|
968
|
+
if (!wasAutoInjected || !isMissingTsMorphSourceFile(err)) {
|
|
969
|
+
throw err;
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
return await loadEntityMetadata(originalOrm);
|
|
973
|
+
} catch {
|
|
974
|
+
throw err;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
533
978
|
async function generateMarkdown(options) {
|
|
534
|
-
const { orm, title = "Database Schema", description, src
|
|
535
|
-
const
|
|
536
|
-
const
|
|
537
|
-
const
|
|
979
|
+
const { orm, title = "Database Schema", description, src, onWarn } = options;
|
|
980
|
+
const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
|
|
981
|
+
const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
|
|
982
|
+
const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
|
|
983
|
+
if (src !== void 0 && src.length > 0) {
|
|
984
|
+
assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
|
|
985
|
+
}
|
|
986
|
+
const docModel = buildDocumentModel(metas, jsDocResult, title, description, onWarn);
|
|
538
987
|
return renderMarkdown(docModel);
|
|
539
988
|
}
|
|
540
989
|
|
|
541
990
|
// src/cli.ts
|
|
991
|
+
function toConfigImportSpecifier(configPath) {
|
|
992
|
+
return (0, import_node_url.pathToFileURL)(path2.resolve(configPath)).href;
|
|
993
|
+
}
|
|
994
|
+
function findNearestTsconfig(fromPath) {
|
|
995
|
+
let dir = path2.dirname(path2.resolve(fromPath));
|
|
996
|
+
for (; ; ) {
|
|
997
|
+
const candidate = path2.join(dir, "tsconfig.json");
|
|
998
|
+
if (syncFs.existsSync(candidate)) {
|
|
999
|
+
return candidate;
|
|
1000
|
+
}
|
|
1001
|
+
const parent = path2.dirname(dir);
|
|
1002
|
+
if (parent === dir) {
|
|
1003
|
+
return void 0;
|
|
1004
|
+
}
|
|
1005
|
+
dir = parent;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
async function loadOrmOptions(configPath, tsconfigPath) {
|
|
1009
|
+
const isTypeScriptConfig = configPath.endsWith(".ts");
|
|
1010
|
+
if (isTypeScriptConfig) {
|
|
1011
|
+
let register;
|
|
1012
|
+
try {
|
|
1013
|
+
({ register } = await import("tsx/esm/api"));
|
|
1014
|
+
} catch {
|
|
1015
|
+
throw new Error('TypeScript config files require the "tsx" package.\nInstall it with: npm install -D tsx');
|
|
1016
|
+
}
|
|
1017
|
+
let tsconfig;
|
|
1018
|
+
if (tsconfigPath !== void 0) {
|
|
1019
|
+
tsconfig = path2.resolve(tsconfigPath);
|
|
1020
|
+
if (!syncFs.existsSync(tsconfig)) {
|
|
1021
|
+
throw new Error(`--tsconfig file not found: ${tsconfig}`);
|
|
1022
|
+
}
|
|
1023
|
+
} else {
|
|
1024
|
+
tsconfig = findNearestTsconfig(configPath);
|
|
1025
|
+
}
|
|
1026
|
+
register(tsconfig !== void 0 ? { tsconfig } : {});
|
|
1027
|
+
}
|
|
1028
|
+
const configUrl = toConfigImportSpecifier(configPath);
|
|
1029
|
+
let mod;
|
|
1030
|
+
try {
|
|
1031
|
+
mod = await import(
|
|
1032
|
+
/* @vite-ignore */
|
|
1033
|
+
configUrl
|
|
1034
|
+
);
|
|
1035
|
+
} catch (cause) {
|
|
1036
|
+
if (isTypeScriptConfig) {
|
|
1037
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
1038
|
+
throw new Error(
|
|
1039
|
+
`Failed to load TypeScript config.
|
|
1040
|
+
${detail}
|
|
1041
|
+
|
|
1042
|
+
If this looks like a decorator/metadata error, the tsconfig applied to your entity files is likely missing "experimentalDecorators" / "emitDecoratorMetadata".
|
|
1043
|
+
Make sure a tsconfig.json with those options sits next to your config file, or pass one explicitly with --tsconfig <path>.`,
|
|
1044
|
+
{ cause }
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
throw cause;
|
|
1048
|
+
}
|
|
1049
|
+
if (mod.default === void 0) {
|
|
1050
|
+
throw new Error("Config file must use a default export, e.g. `export default defineConfig({ ... })`.");
|
|
1051
|
+
}
|
|
1052
|
+
const config = mod.default;
|
|
1053
|
+
if (typeof config === "function" || config instanceof Promise) {
|
|
1054
|
+
throw new Error(
|
|
1055
|
+
"Config file default export must be a configuration object, not a function or Promise.\nResolve it first, or use the programmatic API instead (see README)."
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
1059
|
+
throw new Error(
|
|
1060
|
+
"Config file default export must be a configuration object, not a primitive value or array.\nExport a plain MikroORM options object instead."
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
const options = config;
|
|
1064
|
+
const withPreferTs = isTypeScriptConfig && options.preferTs === void 0 ? { ...options, preferTs: true } : options;
|
|
1065
|
+
return withPreferTs;
|
|
1066
|
+
}
|
|
1067
|
+
function formatErrorChain(err) {
|
|
1068
|
+
const lines = [];
|
|
1069
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1070
|
+
let current = err;
|
|
1071
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
1072
|
+
seen.add(current);
|
|
1073
|
+
lines.push(current.message);
|
|
1074
|
+
current = current.cause;
|
|
1075
|
+
}
|
|
1076
|
+
if (current !== void 0 && !(current instanceof Error)) {
|
|
1077
|
+
lines.push(String(current));
|
|
1078
|
+
}
|
|
1079
|
+
return lines.map((line, i) => i === 0 ? line : ` \u21B3 caused by: ${line}`).join("\n");
|
|
1080
|
+
}
|
|
1081
|
+
var REFLECTION_METADATA_HINT = "\n\nNote: this tool loads configs via tsx (esbuild), which does not emit 'emitDecoratorMetadata' reflection data, so enabling it will not help here.\nEither give each entity property an explicit 'type:'/'entity:' attribute, or install '@mikro-orm/reflection' \u2014 this tool then reads types from your TypeScript sources automatically.";
|
|
1082
|
+
function formatDiscoveryError(err) {
|
|
1083
|
+
const chain = formatErrorChain(err);
|
|
1084
|
+
return chain.includes("emitDecoratorMetadata") ? chain + REFLECTION_METADATA_HINT : chain;
|
|
1085
|
+
}
|
|
1086
|
+
function formatFileSystemError(cause) {
|
|
1087
|
+
if (cause instanceof Error) {
|
|
1088
|
+
const code = "code" in cause && typeof cause.code === "string" ? ` (${cause.code})` : "";
|
|
1089
|
+
return `${cause.message}${code}`;
|
|
1090
|
+
}
|
|
1091
|
+
return String(cause);
|
|
1092
|
+
}
|
|
1093
|
+
async function writeMarkdownFile(outPath, markdown) {
|
|
1094
|
+
try {
|
|
1095
|
+
await fs.mkdir(path2.dirname(outPath), { recursive: true });
|
|
1096
|
+
await fs.writeFile(outPath, markdown, "utf-8");
|
|
1097
|
+
} catch (cause) {
|
|
1098
|
+
throw new Error(`Cannot write output file: ${outPath}
|
|
1099
|
+
${formatFileSystemError(cause)}`, { cause });
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
542
1102
|
async function run(opts) {
|
|
543
|
-
const configPath =
|
|
544
|
-
const outPath =
|
|
1103
|
+
const configPath = path2.resolve(opts.config);
|
|
1104
|
+
const outPath = path2.resolve(opts.out);
|
|
545
1105
|
let ormOptions;
|
|
546
1106
|
try {
|
|
547
|
-
|
|
548
|
-
ormOptions = mod.default ?? mod;
|
|
1107
|
+
ormOptions = await loadOrmOptions(configPath, opts.tsconfig);
|
|
549
1108
|
} catch (err) {
|
|
550
|
-
const hint = configPath.endsWith(".ts") ? "\nHint: TypeScript configs require tsx or ts-node:\n npx tsx ./node_modules/.bin/mikro-orm-markdown ..." : "";
|
|
551
1109
|
process.stderr.write(
|
|
552
1110
|
`Error: Cannot load config: ${configPath}
|
|
553
|
-
${err instanceof Error ? err.message : String(err)}
|
|
1111
|
+
${err instanceof Error ? err.message : String(err)}
|
|
554
1112
|
`
|
|
555
1113
|
);
|
|
556
1114
|
process.exit(1);
|
|
@@ -560,23 +1118,58 @@ ${err instanceof Error ? err.message : String(err)}${hint}
|
|
|
560
1118
|
markdown = await generateMarkdown({
|
|
561
1119
|
orm: ormOptions,
|
|
562
1120
|
title: opts.title,
|
|
563
|
-
|
|
564
|
-
...opts.
|
|
1121
|
+
...opts.description !== void 0 && { description: opts.description },
|
|
1122
|
+
...opts.src !== void 0 && { src: opts.src },
|
|
1123
|
+
onWarn: (message) => void process.stderr.write(`Warning: ${message}
|
|
1124
|
+
`)
|
|
565
1125
|
});
|
|
566
1126
|
} catch (err) {
|
|
567
|
-
|
|
568
|
-
|
|
1127
|
+
process.stderr.write(`Error: ${formatDiscoveryError(err)}
|
|
1128
|
+
`);
|
|
1129
|
+
process.exit(1);
|
|
1130
|
+
}
|
|
1131
|
+
try {
|
|
1132
|
+
await writeMarkdownFile(outPath, markdown);
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
569
1135
|
`);
|
|
570
1136
|
process.exit(1);
|
|
571
1137
|
}
|
|
572
|
-
|
|
573
|
-
process.stdout.write(`\u2713 Written to ${path.relative(process.cwd(), outPath)}
|
|
1138
|
+
process.stdout.write(`\u2713 Written to ${path2.relative(process.cwd(), outPath)}
|
|
574
1139
|
`);
|
|
575
1140
|
}
|
|
576
|
-
var program = new import_commander.Command().name("mikro-orm-markdown").description("Generate Mermaid ERD + markdown docs from MikroORM entities").requiredOption("-c, --config <path>", "MikroORM config file path").option("-o, --out <path>", "Output markdown file path", "./ERD.md").option("-t, --title <string>", "Document title", "Database Schema").option("-d, --description <string>", "Optional description paragraph shown below the title").option(
|
|
577
|
-
|
|
578
|
-
|
|
1141
|
+
var program = new import_commander.Command().name("mikro-orm-markdown").description("Generate Mermaid ERD + markdown docs from MikroORM entities").requiredOption("-c, --config <path>", "MikroORM config file path").option("-o, --out <path>", "Output markdown file path", "./ERD.md").option("-t, --title <string>", "Document title", "Database Schema").option("-d, --description <string>", "Optional description paragraph shown below the title").option(
|
|
1142
|
+
"--tsconfig <path>",
|
|
1143
|
+
"tsconfig.json to use when loading a .ts config (defaults to the nearest one beside the config file)"
|
|
1144
|
+
).option(
|
|
1145
|
+
"--src <paths...>",
|
|
1146
|
+
"Source .ts file globs to read JSDoc from when entities run from compiled .js (comments are stripped at build time)"
|
|
1147
|
+
).action(run);
|
|
1148
|
+
function isDirectCliExecution() {
|
|
1149
|
+
const entryPoint = process.argv[1];
|
|
1150
|
+
if (entryPoint === void 0) {
|
|
1151
|
+
return false;
|
|
1152
|
+
}
|
|
1153
|
+
try {
|
|
1154
|
+
return syncFs.realpathSync(path2.resolve(entryPoint)) === syncFs.realpathSync((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
1155
|
+
} catch {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (isDirectCliExecution()) {
|
|
1160
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
1161
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
579
1162
|
`);
|
|
580
|
-
|
|
1163
|
+
process.exit(1);
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1167
|
+
0 && (module.exports = {
|
|
1168
|
+
findNearestTsconfig,
|
|
1169
|
+
formatDiscoveryError,
|
|
1170
|
+
formatErrorChain,
|
|
1171
|
+
loadOrmOptions,
|
|
1172
|
+
toConfigImportSpecifier,
|
|
1173
|
+
writeMarkdownFile
|
|
581
1174
|
});
|
|
582
1175
|
//# sourceMappingURL=cli.cjs.map
|