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