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/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,23 +17,33 @@ 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
|
-
|
|
34
|
-
|
|
44
|
+
const classNames = /* @__PURE__ */ new Set();
|
|
45
|
+
if (filePaths.length === 0) {
|
|
46
|
+
return { entities, props, sourceFileCount: 0, classNames };
|
|
35
47
|
}
|
|
36
48
|
const project = new import_ts_morph.Project({
|
|
37
49
|
skipAddingFilesFromTsConfig: true,
|
|
@@ -41,34 +53,44 @@ function loadJsDoc(srcGlobs) {
|
|
|
41
53
|
skipLibCheck: true
|
|
42
54
|
}
|
|
43
55
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
for (const prop of cls.getProperties()) {
|
|
57
|
-
const propDocs = prop.getJsDocs();
|
|
58
|
-
if (propDocs.length === 0) {
|
|
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) {
|
|
59
68
|
continue;
|
|
60
69
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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);
|
|
64
88
|
}
|
|
65
89
|
}
|
|
66
|
-
|
|
67
|
-
props.set(className, propMap);
|
|
68
|
-
}
|
|
90
|
+
} catch {
|
|
69
91
|
}
|
|
70
92
|
}
|
|
71
|
-
return { entities, props };
|
|
93
|
+
return { entities, props, sourceFileCount: sourceFiles.length, classNames };
|
|
72
94
|
}
|
|
73
95
|
function parseEntityJsDoc(jsDocs) {
|
|
74
96
|
const namespaces = [];
|
|
@@ -103,17 +125,25 @@ function parseEntityJsDoc(jsDocs) {
|
|
|
103
125
|
hidden
|
|
104
126
|
};
|
|
105
127
|
}
|
|
106
|
-
function
|
|
128
|
+
function parsePropJsDoc(jsDocs) {
|
|
129
|
+
let description;
|
|
130
|
+
let atLeastOne = false;
|
|
107
131
|
for (const doc of jsDocs) {
|
|
108
132
|
const desc = doc.getDescription().trim();
|
|
109
|
-
if (desc) {
|
|
110
|
-
|
|
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
|
+
}
|
|
111
140
|
}
|
|
112
141
|
}
|
|
113
|
-
return void 0;
|
|
142
|
+
return { ...description !== void 0 && { description }, atLeastOne };
|
|
114
143
|
}
|
|
115
144
|
|
|
116
145
|
// src/metadata/load.ts
|
|
146
|
+
var path = __toESM(require("path"), 1);
|
|
117
147
|
var import_core = require("@mikro-orm/core");
|
|
118
148
|
var MetadataLoadError = class extends Error {
|
|
119
149
|
constructor(message, cause) {
|
|
@@ -122,13 +152,46 @@ var MetadataLoadError = class extends Error {
|
|
|
122
152
|
this.name = "MetadataLoadError";
|
|
123
153
|
}
|
|
124
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
|
+
}
|
|
125
183
|
async function loadEntityMetadata(options) {
|
|
184
|
+
assertNoEntitySchemaEntities(options);
|
|
126
185
|
let orm;
|
|
127
186
|
try {
|
|
128
187
|
orm = await import_core.MikroORM.init({
|
|
129
188
|
...options,
|
|
130
189
|
debug: false,
|
|
131
|
-
connect: 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 }
|
|
132
195
|
});
|
|
133
196
|
} catch (cause) {
|
|
134
197
|
throw new MetadataLoadError(
|
|
@@ -136,23 +199,80 @@ async function loadEntityMetadata(options) {
|
|
|
136
199
|
cause
|
|
137
200
|
);
|
|
138
201
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
202
|
+
try {
|
|
203
|
+
const all = Object.values(orm.getMetadata().getAll());
|
|
204
|
+
if (all.length === 0) {
|
|
205
|
+
throw new MetadataLoadError(
|
|
206
|
+
"No entities were discovered. Check that your config specifies at least one entity path or class."
|
|
207
|
+
);
|
|
208
|
+
}
|
|
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 };
|
|
212
|
+
} finally {
|
|
213
|
+
await closeDiscoveryResources(orm);
|
|
144
214
|
}
|
|
145
|
-
return all;
|
|
146
215
|
}
|
|
147
216
|
|
|
217
|
+
// src/model/build.ts
|
|
218
|
+
var import_core3 = require("@mikro-orm/core");
|
|
219
|
+
|
|
148
220
|
// src/render/mermaid.ts
|
|
149
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
|
|
150
269
|
var FORMULA_DUMMY_TABLE = {
|
|
151
270
|
alias: "e0",
|
|
152
271
|
name: "",
|
|
153
272
|
qualifiedName: "",
|
|
154
273
|
toString: () => "e0"
|
|
155
274
|
};
|
|
275
|
+
var UNRESOLVED_FORMULA = "<unresolved>";
|
|
156
276
|
function buildDiagramModel(metas) {
|
|
157
277
|
const metaByClass = new Map(metas.map((m) => [m.className, m]));
|
|
158
278
|
const entities = metas.filter((meta) => !meta.pivotTable && !meta.embeddable).map((meta) => buildEntityModel(meta, metaByClass));
|
|
@@ -160,17 +280,14 @@ function buildDiagramModel(metas) {
|
|
|
160
280
|
return { entities, relations };
|
|
161
281
|
}
|
|
162
282
|
function buildEntityModel(meta, metaByClass) {
|
|
163
|
-
const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.
|
|
283
|
+
const isStiRoot = meta.discriminatorColumn !== void 0 && !meta.extends;
|
|
164
284
|
const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== void 0;
|
|
165
285
|
const columns = [];
|
|
166
286
|
for (const prop of Object.values(meta.properties)) {
|
|
167
287
|
if (isStiRoot && prop.inherited === true) {
|
|
168
288
|
continue;
|
|
169
289
|
}
|
|
170
|
-
|
|
171
|
-
if (col !== null) {
|
|
172
|
-
columns.push(col);
|
|
173
|
-
}
|
|
290
|
+
columns.push(...buildColumns(prop, metaByClass, meta));
|
|
174
291
|
}
|
|
175
292
|
return {
|
|
176
293
|
className: meta.className,
|
|
@@ -180,47 +297,73 @@ function buildEntityModel(meta, metaByClass) {
|
|
|
180
297
|
isEmbeddable: meta.embeddable === true,
|
|
181
298
|
...isStiRoot && { discriminatorColumn: meta.discriminatorColumn },
|
|
182
299
|
...isStiChild && { extendsEntity: meta.extends },
|
|
300
|
+
...isStiChild && meta.discriminatorValue !== void 0 && { discriminatorValue: String(meta.discriminatorValue) },
|
|
183
301
|
constraints: buildConstraints(meta)
|
|
184
302
|
};
|
|
185
303
|
}
|
|
186
|
-
function
|
|
304
|
+
function buildColumns(prop, metaByClass, owningMeta) {
|
|
187
305
|
if (prop.kind === import_core2.ReferenceKind.EMBEDDED) {
|
|
188
|
-
|
|
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 [];
|
|
189
322
|
}
|
|
190
323
|
if (prop.kind === import_core2.ReferenceKind.SCALAR) {
|
|
324
|
+
if (prop.object === true && prop.embedded !== void 0) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
191
327
|
const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
|
|
192
328
|
let embeddedIn;
|
|
329
|
+
let embeddedPropName;
|
|
193
330
|
if (prop.embedded !== void 0) {
|
|
194
331
|
const parentPropName = prop.embedded[0];
|
|
195
332
|
embeddedIn = owningMeta.properties[parentPropName]?.type;
|
|
333
|
+
embeddedPropName = prop.embedded[1];
|
|
196
334
|
}
|
|
197
335
|
const isDiscriminator = owningMeta.discriminatorColumn !== void 0 && prop.name === owningMeta.discriminatorColumn;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
+
];
|
|
210
358
|
}
|
|
211
359
|
if (prop.kind === import_core2.ReferenceKind.MANY_TO_ONE || prop.kind === import_core2.ReferenceKind.ONE_TO_ONE && prop.owner === true) {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
isPrimary: false,
|
|
218
|
-
isForeignKey: true,
|
|
219
|
-
isUnique: prop.unique === true,
|
|
220
|
-
isNullable: prop.nullable === true
|
|
221
|
-
};
|
|
360
|
+
const cols = buildForeignKeyColumns(prop, metaByClass);
|
|
361
|
+
if (prop.type === owningMeta.className) {
|
|
362
|
+
return cols.map((col) => ({ ...col, isSelfReference: true }));
|
|
363
|
+
}
|
|
364
|
+
return cols;
|
|
222
365
|
}
|
|
223
|
-
return
|
|
366
|
+
return [];
|
|
224
367
|
}
|
|
225
368
|
function resolveFormulaExpr(cb) {
|
|
226
369
|
try {
|
|
@@ -230,18 +373,69 @@ function resolveFormulaExpr(cb) {
|
|
|
230
373
|
get: (_target, key) => typeof key === "string" ? key : ""
|
|
231
374
|
}
|
|
232
375
|
);
|
|
233
|
-
|
|
376
|
+
const result = cb(FORMULA_DUMMY_TABLE, cols);
|
|
377
|
+
return typeof result === "string" ? result : String(result);
|
|
234
378
|
} catch {
|
|
235
|
-
return
|
|
379
|
+
return UNRESOLVED_FORMULA;
|
|
236
380
|
}
|
|
237
381
|
}
|
|
238
|
-
function
|
|
239
|
-
const
|
|
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);
|
|
240
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
|
+
const resolvedProp = pkProp ?? primaryProps[index] ?? primaryProps[0];
|
|
408
|
+
const rawType = resolvedProp?.type ?? "integer";
|
|
409
|
+
const pkIndex = resolvedProp !== void 0 ? primaryProps.indexOf(resolvedProp) : 0;
|
|
410
|
+
return resolveScalarType(rawType, metaByClass, pkIndex);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
function resolveScalarType(type, metaByClass, pkIndex = 0, depth = 0) {
|
|
414
|
+
if (depth >= 5) {
|
|
241
415
|
return "integer";
|
|
242
416
|
}
|
|
243
|
-
const
|
|
244
|
-
|
|
417
|
+
const refMeta = metaByClass.get(type);
|
|
418
|
+
if (!refMeta) {
|
|
419
|
+
return type;
|
|
420
|
+
}
|
|
421
|
+
const primaryProps = getPrimaryProps(refMeta);
|
|
422
|
+
if (primaryProps.length === 0) {
|
|
423
|
+
return type;
|
|
424
|
+
}
|
|
425
|
+
const targetProp = primaryProps[pkIndex] ?? primaryProps[0];
|
|
426
|
+
const nextType = targetProp?.type ?? type;
|
|
427
|
+
if (nextType === type) {
|
|
428
|
+
return type;
|
|
429
|
+
}
|
|
430
|
+
return resolveScalarType(nextType, metaByClass, pkIndex, depth + 1);
|
|
431
|
+
}
|
|
432
|
+
function getPrimaryProps(meta) {
|
|
433
|
+
const primaryKeys = meta.primaryKeys ?? [];
|
|
434
|
+
const orderedPrimaryProps = primaryKeys.map((key) => meta.properties[String(key)]).filter((prop) => prop !== void 0);
|
|
435
|
+
if (orderedPrimaryProps.length > 0) {
|
|
436
|
+
return orderedPrimaryProps;
|
|
437
|
+
}
|
|
438
|
+
return Object.values(meta.properties).filter((prop) => prop.primary === true);
|
|
245
439
|
}
|
|
246
440
|
function buildConstraints(meta) {
|
|
247
441
|
const result = [];
|
|
@@ -249,7 +443,7 @@ function buildConstraints(meta) {
|
|
|
249
443
|
const props = idx.properties;
|
|
250
444
|
result.push({
|
|
251
445
|
type: "index",
|
|
252
|
-
properties:
|
|
446
|
+
properties: resolveConstraintProperties(meta, props),
|
|
253
447
|
...idx.name !== void 0 && { name: idx.name }
|
|
254
448
|
});
|
|
255
449
|
}
|
|
@@ -257,7 +451,7 @@ function buildConstraints(meta) {
|
|
|
257
451
|
const props = uniq.properties;
|
|
258
452
|
result.push({
|
|
259
453
|
type: "unique",
|
|
260
|
-
properties:
|
|
454
|
+
properties: resolveConstraintProperties(meta, props),
|
|
261
455
|
...uniq.name !== void 0 && { name: uniq.name }
|
|
262
456
|
});
|
|
263
457
|
}
|
|
@@ -274,6 +468,19 @@ function buildConstraints(meta) {
|
|
|
274
468
|
}
|
|
275
469
|
return result;
|
|
276
470
|
}
|
|
471
|
+
function resolveConstraintProperties(meta, props) {
|
|
472
|
+
const propNames = Array.isArray(props) ? props : props !== void 0 ? [props] : [];
|
|
473
|
+
return propNames.flatMap((propName) => {
|
|
474
|
+
const prop = meta.properties[String(propName)];
|
|
475
|
+
if (prop === void 0) {
|
|
476
|
+
return [String(propName)];
|
|
477
|
+
}
|
|
478
|
+
if (prop.fieldNames !== void 0 && prop.fieldNames.length > 0) {
|
|
479
|
+
return prop.fieldNames;
|
|
480
|
+
}
|
|
481
|
+
return [prop.name];
|
|
482
|
+
});
|
|
483
|
+
}
|
|
277
484
|
function buildRelationEdges(metas) {
|
|
278
485
|
const edges = [];
|
|
279
486
|
for (const meta of metas) {
|
|
@@ -292,6 +499,9 @@ function buildRelationEdges(metas) {
|
|
|
292
499
|
function buildEdge(fromEntity, prop) {
|
|
293
500
|
const isNullable = prop.nullable === true;
|
|
294
501
|
if (prop.kind === import_core2.ReferenceKind.MANY_TO_ONE) {
|
|
502
|
+
if (prop.type === fromEntity) {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
295
505
|
return {
|
|
296
506
|
fromEntity,
|
|
297
507
|
toEntity: prop.type,
|
|
@@ -320,17 +530,41 @@ function buildEdge(fromEntity, prop) {
|
|
|
320
530
|
}
|
|
321
531
|
return null;
|
|
322
532
|
}
|
|
533
|
+
function normalizeType(type) {
|
|
534
|
+
const t = type.toLowerCase().trim();
|
|
535
|
+
if (t === "uuid" || t === "text" || t === "string" || t.startsWith("varchar")) {
|
|
536
|
+
return "string";
|
|
537
|
+
}
|
|
538
|
+
if (t === "timestamptz" || t === "timestamp" || t === "datetime") {
|
|
539
|
+
return "datetime";
|
|
540
|
+
}
|
|
541
|
+
if (t === "integer" || t === "int" || t === "bigint" || t === "smallint") {
|
|
542
|
+
return "integer";
|
|
543
|
+
}
|
|
544
|
+
if (t === "doubletype" || t === "double precision" || t === "double" || t === "float" || t === "decimal") {
|
|
545
|
+
return "float";
|
|
546
|
+
}
|
|
547
|
+
if (t === "boolean" || t === "bool") {
|
|
548
|
+
return "boolean";
|
|
549
|
+
}
|
|
550
|
+
if (t === "jsonb") {
|
|
551
|
+
return "json";
|
|
552
|
+
}
|
|
553
|
+
return type;
|
|
554
|
+
}
|
|
323
555
|
function renderErDiagram(model) {
|
|
324
556
|
const lines = ["erDiagram"];
|
|
325
557
|
for (const entity of model.entities) {
|
|
326
|
-
lines.push(` ${entity.className} {`);
|
|
558
|
+
lines.push(` ${toMermaidIdentifier(entity.className)} {`);
|
|
327
559
|
for (const col of entity.columns) {
|
|
328
560
|
lines.push(` ${renderColumnLine(col)}`);
|
|
329
561
|
}
|
|
330
562
|
lines.push(" }");
|
|
331
563
|
}
|
|
332
564
|
for (const rel of model.relations) {
|
|
333
|
-
lines.push(
|
|
565
|
+
lines.push(
|
|
566
|
+
` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
|
|
567
|
+
);
|
|
334
568
|
}
|
|
335
569
|
return lines.join("\n");
|
|
336
570
|
}
|
|
@@ -338,8 +572,6 @@ function renderColumnLine(col) {
|
|
|
338
572
|
let qualifier = "";
|
|
339
573
|
if (col.isPrimary) {
|
|
340
574
|
qualifier = " PK";
|
|
341
|
-
} else if (col.isForeignKey) {
|
|
342
|
-
qualifier = " FK";
|
|
343
575
|
} else if (col.isUnique) {
|
|
344
576
|
qualifier = " UK";
|
|
345
577
|
}
|
|
@@ -350,27 +582,38 @@ function renderColumnLine(col) {
|
|
|
350
582
|
comment = "discriminator";
|
|
351
583
|
} else if (col.embeddedIn !== void 0) {
|
|
352
584
|
comment = `[${col.embeddedIn}]`;
|
|
353
|
-
} else if (col.
|
|
354
|
-
comment =
|
|
585
|
+
} else if (col.isSelfReference) {
|
|
586
|
+
comment = "self-ref";
|
|
355
587
|
}
|
|
356
|
-
const commentStr = comment !== void 0 ? ` "${comment}"` : "";
|
|
357
|
-
return `${col.type} ${col.fieldName}${qualifier}${commentStr}`;
|
|
358
|
-
}
|
|
359
|
-
function normalizeType(type) {
|
|
360
|
-
return type.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
588
|
+
const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
|
|
589
|
+
return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
|
|
361
590
|
}
|
|
362
591
|
|
|
363
592
|
// src/model/build.ts
|
|
364
|
-
|
|
365
|
-
|
|
593
|
+
var FROM_ONE_OR_MORE = "}|";
|
|
594
|
+
var TO_ONE_OR_MORE = "|{";
|
|
595
|
+
function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
|
|
596
|
+
const { entities: diagramEntities, relations } = buildDiagramModel(metas);
|
|
597
|
+
const allRelations = applyAtLeastOne(relations, metas, jsDocResult.props, onWarn);
|
|
598
|
+
const hiddenClasses = /* @__PURE__ */ new Set();
|
|
599
|
+
for (const model of diagramEntities) {
|
|
600
|
+
if (jsDocResult.entities.get(model.className)?.hidden) {
|
|
601
|
+
hiddenClasses.add(model.className);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
366
604
|
const enrichedByClass = /* @__PURE__ */ new Map();
|
|
367
605
|
for (const model of diagramEntities) {
|
|
368
606
|
const jsDoc = jsDocResult.entities.get(model.className);
|
|
369
607
|
if (jsDoc?.hidden) {
|
|
370
608
|
continue;
|
|
371
609
|
}
|
|
372
|
-
const
|
|
373
|
-
|
|
610
|
+
const columns = model.columns.filter(
|
|
611
|
+
(col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
|
|
612
|
+
);
|
|
613
|
+
const visibleModel = columns.length === model.columns.length ? model : { ...model, columns };
|
|
614
|
+
const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
|
|
615
|
+
const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
|
|
616
|
+
enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
|
|
374
617
|
}
|
|
375
618
|
const groupNames = /* @__PURE__ */ new Set();
|
|
376
619
|
let anyUntagged = false;
|
|
@@ -390,9 +633,16 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
390
633
|
const groups = [];
|
|
391
634
|
for (const groupName of groupNames) {
|
|
392
635
|
const isDefault = groupName === "default";
|
|
393
|
-
const erdEntities = [...enrichedByClass.values()].filter(
|
|
394
|
-
|
|
395
|
-
|
|
636
|
+
const erdEntities = [...enrichedByClass.values()].filter(({ jsDoc }) => belongsToGroupForErd(jsDoc, groupName, isDefault)).map((entity) => {
|
|
637
|
+
if (isCrossNamespaceInGroup(entity.jsDoc, groupName, isDefault)) {
|
|
638
|
+
const pkColumns = entity.model.columns.filter((col) => col.isPrimary);
|
|
639
|
+
if (pkColumns.length === 0) {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
return { ...entity, model: { ...entity.model, columns: pkColumns } };
|
|
643
|
+
}
|
|
644
|
+
return entity;
|
|
645
|
+
}).filter((entity) => entity !== null);
|
|
396
646
|
const textEntities = [...enrichedByClass.values()].filter(
|
|
397
647
|
({ jsDoc }) => belongsToGroupForText(jsDoc, groupName, isDefault)
|
|
398
648
|
);
|
|
@@ -411,6 +661,65 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
411
661
|
});
|
|
412
662
|
return { title, groups, ...description !== void 0 && { description } };
|
|
413
663
|
}
|
|
664
|
+
function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
|
|
665
|
+
const merged = new Map(ownPropDocs);
|
|
666
|
+
for (const col of columns) {
|
|
667
|
+
if (merged.has(col.propName) || col.embeddedIn === void 0 || col.embeddedPropName === void 0) {
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const info = allPropDocs.get(col.embeddedIn)?.get(col.embeddedPropName);
|
|
671
|
+
if (info) {
|
|
672
|
+
merged.set(col.propName, info);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return merged;
|
|
676
|
+
}
|
|
677
|
+
function applyAtLeastOne(relations, metas, props, onWarn) {
|
|
678
|
+
const adjusted = relations.map((edge) => ({ ...edge }));
|
|
679
|
+
const metaByClass = new Map(metas.map((m) => [m.className, m]));
|
|
680
|
+
for (const [className, propMap] of props) {
|
|
681
|
+
const meta = metaByClass.get(className);
|
|
682
|
+
if (!meta) {
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
for (const [propName, info] of propMap) {
|
|
686
|
+
if (!info.atLeastOne) {
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
const prop = meta.properties[propName];
|
|
690
|
+
if (!prop) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
let edge;
|
|
694
|
+
if (prop.kind === import_core3.ReferenceKind.ONE_TO_MANY && prop.mappedBy) {
|
|
695
|
+
edge = adjusted.find(
|
|
696
|
+
(e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
|
|
697
|
+
);
|
|
698
|
+
if (edge) {
|
|
699
|
+
edge.fromCardinality = FROM_ONE_OR_MORE;
|
|
700
|
+
}
|
|
701
|
+
} else if (prop.kind === import_core3.ReferenceKind.MANY_TO_MANY && prop.owner === true) {
|
|
702
|
+
edge = adjusted.find((e) => e.fromEntity === className && e.toEntity === prop.type && e.label === propName);
|
|
703
|
+
if (edge) {
|
|
704
|
+
edge.toCardinality = TO_ONE_OR_MORE;
|
|
705
|
+
}
|
|
706
|
+
} else if (prop.kind === import_core3.ReferenceKind.MANY_TO_MANY && prop.mappedBy) {
|
|
707
|
+
edge = adjusted.find(
|
|
708
|
+
(e) => e.fromEntity === prop.type && e.toEntity === className && e.label === prop.mappedBy
|
|
709
|
+
);
|
|
710
|
+
if (edge) {
|
|
711
|
+
edge.fromCardinality = FROM_ONE_OR_MORE;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (!edge) {
|
|
715
|
+
onWarn?.(
|
|
716
|
+
`@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.`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return adjusted;
|
|
722
|
+
}
|
|
414
723
|
function hasNoNamespaceTags(jsDoc) {
|
|
415
724
|
if (!jsDoc) {
|
|
416
725
|
return true;
|
|
@@ -435,20 +744,62 @@ function belongsToGroupForText(jsDoc, groupName, isDefault) {
|
|
|
435
744
|
}
|
|
436
745
|
return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
|
|
437
746
|
}
|
|
747
|
+
function isCrossNamespaceInGroup(jsDoc, groupName, isDefault) {
|
|
748
|
+
if (isDefault || !jsDoc) {
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
return jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/provider.ts
|
|
755
|
+
async function withTsMorphMetadataProvider(options, onWarn) {
|
|
756
|
+
if (options.metadataProvider !== void 0) {
|
|
757
|
+
return options;
|
|
758
|
+
}
|
|
759
|
+
try {
|
|
760
|
+
const { TsMorphMetadataProvider } = await import("@mikro-orm/reflection");
|
|
761
|
+
return { ...options, metadataProvider: TsMorphMetadataProvider };
|
|
762
|
+
} catch (err) {
|
|
763
|
+
const code = err !== null && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
764
|
+
const isNotInstalled = code === "MODULE_NOT_FOUND" || code === "ERR_MODULE_NOT_FOUND";
|
|
765
|
+
if (!isNotInstalled && onWarn) {
|
|
766
|
+
onWarn(
|
|
767
|
+
`@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.`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
return options;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
438
773
|
|
|
439
774
|
// src/render/markdown.ts
|
|
440
775
|
function renderMarkdown(docModel) {
|
|
441
|
-
const sections = [`# ${docModel.title}`];
|
|
776
|
+
const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
|
|
442
777
|
if (docModel.description) {
|
|
443
|
-
sections.push(docModel.description);
|
|
778
|
+
sections.push(escapeMarkdownParagraph(docModel.description));
|
|
779
|
+
}
|
|
780
|
+
if (docModel.groups.length > 1) {
|
|
781
|
+
sections.push(renderTableOfContents(docModel.groups));
|
|
444
782
|
}
|
|
445
783
|
for (const group of docModel.groups) {
|
|
446
784
|
sections.push(renderGroupSection(group));
|
|
447
785
|
}
|
|
448
786
|
return sections.join("\n\n");
|
|
449
787
|
}
|
|
788
|
+
function renderBulletSection(header, items, renderItem) {
|
|
789
|
+
const lines = [header, ""];
|
|
790
|
+
for (const item of items) {
|
|
791
|
+
lines.push(renderItem(item));
|
|
792
|
+
}
|
|
793
|
+
return lines.join("\n");
|
|
794
|
+
}
|
|
795
|
+
function renderTableOfContents(groups) {
|
|
796
|
+
return renderBulletSection("## Contents", groups, (group) => {
|
|
797
|
+
const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
|
|
798
|
+
return `- [${label}](#${toMarkdownAnchor(group.name)})`;
|
|
799
|
+
});
|
|
800
|
+
}
|
|
450
801
|
function renderGroupSection(group) {
|
|
451
|
-
const parts = [`## ${group.name}`];
|
|
802
|
+
const parts = [`## ${escapeMarkdownInline(group.name)}`];
|
|
452
803
|
if (group.erdEntities.length > 0) {
|
|
453
804
|
const diagramModel = {
|
|
454
805
|
entities: group.erdEntities.map((e) => e.model),
|
|
@@ -462,14 +813,18 @@ function renderGroupSection(group) {
|
|
|
462
813
|
return parts.join("\n\n");
|
|
463
814
|
}
|
|
464
815
|
function renderEntitySection(entity) {
|
|
465
|
-
const parts = [`### ${entity.model.className}`];
|
|
816
|
+
const parts = [`### ${escapeMarkdownInline(entity.model.className)}`];
|
|
817
|
+
parts.push(`*Table: ${renderMarkdownInlineCode(entity.model.tableName)}*`);
|
|
466
818
|
if (entity.jsDoc?.description) {
|
|
467
|
-
parts.push(
|
|
819
|
+
parts.push(renderMarkdownBlockQuote(entity.jsDoc.description));
|
|
468
820
|
}
|
|
469
821
|
if (entity.model.discriminatorColumn) {
|
|
470
|
-
parts.push(`*STI root \u2014 discriminator column:
|
|
822
|
+
parts.push(`*STI root \u2014 discriminator column: ${renderMarkdownInlineCode(entity.model.discriminatorColumn)}*`);
|
|
471
823
|
} else if (entity.model.extendsEntity) {
|
|
472
|
-
|
|
824
|
+
const discValue = entity.model.discriminatorValue !== void 0 ? `, discriminator value: ${renderMarkdownInlineCode(entity.model.discriminatorValue)}` : "";
|
|
825
|
+
parts.push(
|
|
826
|
+
`*Extends ${renderMarkdownInlineCode(entity.model.extendsEntity)} (Single Table Inheritance${discValue})*`
|
|
827
|
+
);
|
|
473
828
|
}
|
|
474
829
|
if (entity.model.columns.length > 0) {
|
|
475
830
|
parts.push(renderColumnTable(entity));
|
|
@@ -477,6 +832,10 @@ function renderEntitySection(entity) {
|
|
|
477
832
|
if (entity.model.constraints.length > 0) {
|
|
478
833
|
parts.push(renderConstraints(entity.model.constraints));
|
|
479
834
|
}
|
|
835
|
+
const computedColumns = entity.model.columns.filter((col) => col.formula !== void 0);
|
|
836
|
+
if (computedColumns.length > 0) {
|
|
837
|
+
parts.push(renderComputedColumns(computedColumns));
|
|
838
|
+
}
|
|
480
839
|
return parts.join("\n\n");
|
|
481
840
|
}
|
|
482
841
|
function renderColumnTable(entity) {
|
|
@@ -485,24 +844,27 @@ function renderColumnTable(entity) {
|
|
|
485
844
|
const rows = entity.model.columns.map((col) => {
|
|
486
845
|
const key = resolveColumnKey(col);
|
|
487
846
|
const nullable = col.isNullable && !col.isPrimary ? "Y" : "";
|
|
488
|
-
const
|
|
489
|
-
|
|
847
|
+
const docDesc = entity.propDocs.get(col.propName)?.description ?? col.comment ?? "";
|
|
848
|
+
const enumDesc = col.enumItems !== void 0 ? `One of: ${col.enumItems.join(", ")}` : "";
|
|
849
|
+
const desc = [docDesc, enumDesc].filter((part) => part !== "").join("\n");
|
|
850
|
+
return `| ${escapeMarkdownTableCell(col.fieldName)} | ${escapeMarkdownTableCell(normalizeType(col.type))} | ${escapeMarkdownTableCell(key)} | ${nullable} | ${escapeMarkdownTableCell(desc)} |`;
|
|
490
851
|
});
|
|
491
852
|
return [header, sep, ...rows].join("\n");
|
|
492
853
|
}
|
|
493
854
|
function resolveColumnKey(col) {
|
|
855
|
+
const fkKey = col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
|
|
856
|
+
if (col.isPrimary && col.isForeignKey) {
|
|
857
|
+
return `PK, ${fkKey}`;
|
|
858
|
+
}
|
|
494
859
|
if (col.isPrimary) {
|
|
495
860
|
return "PK";
|
|
496
861
|
}
|
|
497
862
|
if (col.isForeignKey) {
|
|
498
|
-
return
|
|
863
|
+
return fkKey;
|
|
499
864
|
}
|
|
500
865
|
if (col.isUnique) {
|
|
501
866
|
return "UK";
|
|
502
867
|
}
|
|
503
|
-
if (col.formula !== void 0) {
|
|
504
|
-
return `formula: ${col.formula}`;
|
|
505
|
-
}
|
|
506
868
|
if (col.isDiscriminator) {
|
|
507
869
|
return "discriminator";
|
|
508
870
|
}
|
|
@@ -511,32 +873,108 @@ function resolveColumnKey(col) {
|
|
|
511
873
|
}
|
|
512
874
|
return "";
|
|
513
875
|
}
|
|
876
|
+
function renderComputedColumns(columns) {
|
|
877
|
+
return renderBulletSection("**Computed columns:**", columns, (col) => {
|
|
878
|
+
const expr = col.formula ? `: ${renderMarkdownInlineCode(col.formula)}` : "";
|
|
879
|
+
return `- ${renderMarkdownInlineCode(col.fieldName)}${expr}`;
|
|
880
|
+
});
|
|
881
|
+
}
|
|
514
882
|
function renderConstraints(constraints) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const
|
|
883
|
+
return renderBulletSection("**Constraints:**", constraints, (c) => {
|
|
884
|
+
const name = c.name ? ` ${renderMarkdownInlineCode(c.name)}` : "";
|
|
885
|
+
const properties = c.properties.map(escapeMarkdownInline).join(", ");
|
|
518
886
|
if (c.type === "index") {
|
|
519
|
-
|
|
520
|
-
} else if (c.type === "unique") {
|
|
521
|
-
lines.push(`- Unique${name}: (${c.properties.join(", ")})`);
|
|
522
|
-
} else if (c.type === "check") {
|
|
523
|
-
lines.push(`- Check${name}: \`${c.expression ?? ""}\``);
|
|
887
|
+
return `- Index${name}: (${properties})`;
|
|
524
888
|
}
|
|
525
|
-
|
|
526
|
-
|
|
889
|
+
if (c.type === "unique") {
|
|
890
|
+
return `- Unique${name}: (${properties})`;
|
|
891
|
+
}
|
|
892
|
+
return `- Check${name}: ${renderMarkdownInlineCode(c.expression ?? "")}`;
|
|
893
|
+
});
|
|
527
894
|
}
|
|
528
895
|
|
|
529
896
|
// src/index.ts
|
|
897
|
+
var COMPILED_JS = /\.(c|m)?js$/i;
|
|
898
|
+
function resolveJsDocSources(sourcePaths, src, onWarn) {
|
|
899
|
+
if (src !== void 0 && src.length > 0) {
|
|
900
|
+
return src;
|
|
901
|
+
}
|
|
902
|
+
if (sourcePaths.some((p) => COMPILED_JS.test(p)) && onWarn) {
|
|
903
|
+
onWarn(
|
|
904
|
+
'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.'
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
return sourcePaths;
|
|
908
|
+
}
|
|
909
|
+
function assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn) {
|
|
910
|
+
if (jsDocResult.sourceFileCount === 0) {
|
|
911
|
+
throw new Error(
|
|
912
|
+
`No source files matched the explicit src paths: ${src.join(", ")}
|
|
913
|
+
Check the --src glob/path (or the \`src\` option). Without matching TypeScript sources, JSDoc tags such as @namespace and @hidden cannot be read.`
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
const isRenderable = (meta) => !meta.pivotTable && !meta.embeddable;
|
|
917
|
+
const missingConcrete = metas.filter((meta) => isRenderable(meta) && !meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
|
|
918
|
+
if (missingConcrete.length > 0) {
|
|
919
|
+
throw new Error(
|
|
920
|
+
`Explicit src paths did not include source declarations for discovered entities: ${missingConcrete.join(", ")}
|
|
921
|
+
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.`
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
if (onWarn) {
|
|
925
|
+
const missingAbstract = metas.filter((meta) => isRenderable(meta) && meta.abstract).map((meta) => meta.className).filter((className) => !jsDocResult.classNames.has(className));
|
|
926
|
+
if (missingAbstract.length > 0) {
|
|
927
|
+
onWarn(
|
|
928
|
+
`Abstract STI parent entities were not found in the explicit src paths: ${missingAbstract.join(", ")}
|
|
929
|
+
@hidden and @namespace tags for these entities will not be applied. Include their source files in --src to enable JSDoc tags for them.`
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function errorMessages(err) {
|
|
935
|
+
const messages = [];
|
|
936
|
+
const seen = /* @__PURE__ */ new Set();
|
|
937
|
+
let current = err;
|
|
938
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
939
|
+
seen.add(current);
|
|
940
|
+
messages.push(current.message);
|
|
941
|
+
current = current.cause;
|
|
942
|
+
}
|
|
943
|
+
return messages;
|
|
944
|
+
}
|
|
945
|
+
function isMissingTsMorphSourceFile(err) {
|
|
946
|
+
return errorMessages(err).some((message) => message.includes("Source file") && message.includes("not found"));
|
|
947
|
+
}
|
|
948
|
+
async function loadEntityMetadataWithTsMorphFallback(originalOrm, effectiveOrm) {
|
|
949
|
+
try {
|
|
950
|
+
return await loadEntityMetadata(effectiveOrm);
|
|
951
|
+
} catch (err) {
|
|
952
|
+
const wasAutoInjected = originalOrm.metadataProvider === void 0 && effectiveOrm.metadataProvider !== void 0;
|
|
953
|
+
if (!wasAutoInjected || !isMissingTsMorphSourceFile(err)) {
|
|
954
|
+
throw err;
|
|
955
|
+
}
|
|
956
|
+
try {
|
|
957
|
+
return await loadEntityMetadata(originalOrm);
|
|
958
|
+
} catch {
|
|
959
|
+
throw err;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
530
963
|
async function generateMarkdown(options) {
|
|
531
|
-
const { orm, title = "Database Schema", description, src
|
|
532
|
-
const
|
|
533
|
-
const
|
|
534
|
-
const
|
|
964
|
+
const { orm, title = "Database Schema", description, src, onWarn } = options;
|
|
965
|
+
const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
|
|
966
|
+
const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
|
|
967
|
+
const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
|
|
968
|
+
if (src !== void 0 && src.length > 0) {
|
|
969
|
+
assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
|
|
970
|
+
}
|
|
971
|
+
const docModel = buildDocumentModel(metas, jsDocResult, title, description, onWarn);
|
|
535
972
|
return renderMarkdown(docModel);
|
|
536
973
|
}
|
|
537
974
|
// Annotate the CommonJS export names for ESM import in node:
|
|
538
975
|
0 && (module.exports = {
|
|
539
976
|
MetadataLoadError,
|
|
540
|
-
generateMarkdown
|
|
977
|
+
generateMarkdown,
|
|
978
|
+
resolveJsDocSources
|
|
541
979
|
});
|
|
542
980
|
//# sourceMappingURL=index.cjs.map
|