mikro-orm-markdown 0.1.0-alpha.1 → 0.1.0-alpha.2
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/README.ko.md +27 -23
- package/README.md +26 -22
- package/dist/cli.cjs +117 -69
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +117 -69
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +115 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +115 -62
- package/dist/index.js.map +1 -1
- package/package.json +16 -12
package/README.ko.md
CHANGED
|
@@ -15,15 +15,19 @@ MikroORM에서도 동일한 ERD + Markdown 경험을 제공하며, Prisma로는
|
|
|
15
15
|
- **Embeddable** — 별도 테이블 없이 소유 엔티티의 테이블 안에 컬럼을 펼쳐서 저장하는 값 객체입니다. 예를 들어 `Address` 값 객체는 `address_street`, `address_city` 등의 컬럼으로 저장됩니다. DDD의 Value Object와 같은 개념입니다.
|
|
16
16
|
- **Single Table Inheritance (STI)** — `Dog`, `Cat` 같은 자식 클래스가 `animals` 테이블 하나를 공유합니다. `type` 같은 discriminator 컬럼으로 어떤 자식 클래스인지 구분합니다.
|
|
17
17
|
- **@Formula** — 실제 DB 컬럼 없이 SELECT 시 SQL 식으로 값을 계산하는 가상 컬럼입니다. 예를 들어 `LENGTH(name)`은 DB에 컬럼이 없지만 조회 시 이름의 길이를 반환합니다.
|
|
18
|
+
|
|
19
|
+
> 이 기능들은 MikroORM에서 완전히 지원하지만 실무에서는 자주 쓰이지 않을 수 있습니다. 특히 Embeddable은 MikroORM Entity를 Domain Entity로 함께 사용하는 구조에서 주로 활용되며, ORM 레이어와 도메인 모델을 분리해서 관리한다면 사용할 일이 거의 없습니다.
|
|
20
|
+
|
|
18
21
|
- NamingStrategy가 적용된 **실제 DB 컬럼명**
|
|
19
22
|
- **인덱스 및 제약 조건**
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
PostgreSQL, MySQL/MariaDB, SQLite, MSSQL 같은 MikroORM SQL 드라이버에서 동작합니다. 기존 DB 스키마를 직접 introspection하지 않고 MikroORM 설정에서 엔티티 메타데이터를 읽기 때문에 실행 중인 DB 연결이 필요하지 않습니다.
|
|
22
25
|
|
|
23
26
|
## 요구사항
|
|
24
27
|
|
|
25
28
|
- Node.js >= 18
|
|
26
29
|
- `@mikro-orm/core` >= 6 (peer dependency)
|
|
30
|
+
- 사용할 DB에 맞는 MikroORM 드라이버 패키지가 설치된 MikroORM 설정 파일
|
|
27
31
|
- TypeScript 설정 파일 사용 시 `tsx` 또는 `ts-node` 필요
|
|
28
32
|
|
|
29
33
|
## 설치
|
|
@@ -64,12 +68,12 @@ npm run erd
|
|
|
64
68
|
|
|
65
69
|
## CLI 옵션
|
|
66
70
|
|
|
67
|
-
| 옵션
|
|
68
|
-
|
|
69
|
-
| `-c, --config <path>`
|
|
70
|
-
| `-o, --out <path>`
|
|
71
|
-
| `-t, --title <string>` | `Database Schema` | 문서 H1 제목
|
|
72
|
-
| `-s, --src <glob>`
|
|
71
|
+
| 옵션 | 기본값 | 설명 |
|
|
72
|
+
| ---------------------- | ----------------- | --------------------------------------------------------------------- |
|
|
73
|
+
| `-c, --config <path>` | _(필수)_ | MikroORM 설정 파일 경로 |
|
|
74
|
+
| `-o, --out <path>` | `./ERD.md` | 출력 Markdown 파일 경로 |
|
|
75
|
+
| `-t, --title <string>` | `Database Schema` | 문서 H1 제목 |
|
|
76
|
+
| `-s, --src <glob>` | — | 엔티티 소스 파일 glob 패턴 (반복 가능). JSDoc 태그 추출에 사용됩니다. |
|
|
73
77
|
|
|
74
78
|
## JSDoc 태그
|
|
75
79
|
|
|
@@ -88,12 +92,12 @@ export class Post {
|
|
|
88
92
|
}
|
|
89
93
|
```
|
|
90
94
|
|
|
91
|
-
| 태그
|
|
92
|
-
|
|
93
|
-
| `@namespace <Name>` | `Name` 섹션에 포함 (ERD + 본문 표)
|
|
94
|
-
| `@erd <Name>`
|
|
95
|
-
| `@describe <Name>`
|
|
96
|
-
| `@hidden`
|
|
95
|
+
| 태그 | 설명 |
|
|
96
|
+
| ------------------- | ------------------------------------- |
|
|
97
|
+
| `@namespace <Name>` | `Name` 섹션에 포함 (ERD + 본문 표) |
|
|
98
|
+
| `@erd <Name>` | `Name` 섹션의 ERD 다이어그램에만 포함 |
|
|
99
|
+
| `@describe <Name>` | `Name` 섹션의 본문 표에만 포함 |
|
|
100
|
+
| `@hidden` | 문서 전체에서 제외 |
|
|
97
101
|
|
|
98
102
|
태그가 없는 엔티티는 `default` 섹션에 들어갑니다.
|
|
99
103
|
하나의 엔티티에 여러 태그를 지정할 수 있습니다.
|
|
@@ -127,21 +131,21 @@ erDiagram
|
|
|
127
131
|
|
|
128
132
|
> 등록된 사용자가 작성한 블로그 게시글입니다.
|
|
129
133
|
|
|
130
|
-
| Column
|
|
131
|
-
|
|
132
|
-
| id
|
|
133
|
-
| title
|
|
134
|
-
| body
|
|
135
|
-
| author_id | integer | FK (author) |
|
|
134
|
+
| Column | Type | Key | Nullable | Description |
|
|
135
|
+
| --------- | ------- | ----------- | -------- | ----------- |
|
|
136
|
+
| id | integer | PK | | |
|
|
137
|
+
| title | string | | | 게시글 제목 |
|
|
138
|
+
| body | text | | Y | |
|
|
139
|
+
| author_id | integer | FK (author) | | |
|
|
136
140
|
````
|
|
137
141
|
|
|
138
142
|
**Key 컬럼 주석 의미:**
|
|
139
143
|
|
|
140
|
-
| 표기
|
|
141
|
-
|
|
142
|
-
| `formula: <expr>`
|
|
144
|
+
| 표기 | 의미 |
|
|
145
|
+
| ------------------ | -------------------------------------------- |
|
|
146
|
+
| `formula: <expr>` | `@Formula` 계산 컬럼 — 실제 DB 컬럼 없음 |
|
|
143
147
|
| `[EmbeddableType]` | `@Embedded` 값 객체에서 flat으로 저장된 컬럼 |
|
|
144
|
-
| `discriminator`
|
|
148
|
+
| `discriminator` | STI 구분자 컬럼 |
|
|
145
149
|
|
|
146
150
|
## 참고 사항
|
|
147
151
|
|
package/README.md
CHANGED
|
@@ -15,15 +15,19 @@ This tool brings the same ERD + Markdown experience to MikroORM, with additional
|
|
|
15
15
|
- **Embeddable** — a value object whose fields are stored as flat columns inside the owning entity's table (e.g. `address_street`, `address_city`). No separate table is created.
|
|
16
16
|
- **Single Table Inheritance (STI)** — subclasses like `Dog` and `Cat` share one `animals` table. A discriminator column (e.g. `type`) distinguishes which subclass each row belongs to.
|
|
17
17
|
- **@Formula** — a virtual column with no physical DB column. Its value is computed by a SQL expression at SELECT time (e.g. `LENGTH(name)`).
|
|
18
|
+
|
|
19
|
+
> These features are fully supported by MikroORM but are less commonly used in practice. In particular, Embeddable is most relevant when MikroORM entities double as domain objects — if you separate your ORM layer from your domain model, you likely won't need it.
|
|
20
|
+
|
|
18
21
|
- **Actual DB column names** derived from your NamingStrategy
|
|
19
22
|
- **Indexes and constraints**
|
|
20
23
|
|
|
21
|
-
Works with
|
|
24
|
+
Works with MikroORM SQL drivers such as PostgreSQL, MySQL/MariaDB, SQLite, and MSSQL. It reads entity metadata from your MikroORM config, so no live database connection is required.
|
|
22
25
|
|
|
23
26
|
## Requirements
|
|
24
27
|
|
|
25
28
|
- Node.js >= 18
|
|
26
29
|
- `@mikro-orm/core` >= 6 (peer dependency)
|
|
30
|
+
- A MikroORM config with the matching database driver package installed
|
|
27
31
|
- TypeScript config files require `tsx` or `ts-node`
|
|
28
32
|
|
|
29
33
|
## Installation
|
|
@@ -64,12 +68,12 @@ npm run erd
|
|
|
64
68
|
|
|
65
69
|
## CLI Options
|
|
66
70
|
|
|
67
|
-
| Option
|
|
68
|
-
|
|
69
|
-
| `-c, --config <path>`
|
|
70
|
-
| `-o, --out <path>`
|
|
71
|
-
| `-t, --title <string>` | `Database Schema` | H1 heading of the generated document
|
|
72
|
-
| `-s, --src <glob>`
|
|
71
|
+
| Option | Default | Description |
|
|
72
|
+
| ---------------------- | ----------------- | -------------------------------------------------------------------------------- |
|
|
73
|
+
| `-c, --config <path>` | _(required)_ | Path to MikroORM config file |
|
|
74
|
+
| `-o, --out <path>` | `./ERD.md` | Output Markdown file path |
|
|
75
|
+
| `-t, --title <string>` | `Database Schema` | H1 heading of the generated document |
|
|
76
|
+
| `-s, --src <glob>` | — | Glob pattern for entity source files (repeatable). Enables JSDoc tag extraction. |
|
|
73
77
|
|
|
74
78
|
## JSDoc Tags
|
|
75
79
|
|
|
@@ -88,12 +92,12 @@ export class Post {
|
|
|
88
92
|
}
|
|
89
93
|
```
|
|
90
94
|
|
|
91
|
-
| Tag
|
|
92
|
-
|
|
95
|
+
| Tag | Description |
|
|
96
|
+
| ------------------- | --------------------------------------------------- |
|
|
93
97
|
| `@namespace <Name>` | Include entity in section `Name` (ERD + text table) |
|
|
94
|
-
| `@erd <Name>`
|
|
95
|
-
| `@describe <Name>`
|
|
96
|
-
| `@hidden`
|
|
98
|
+
| `@erd <Name>` | Include in section `Name`'s ERD diagram only |
|
|
99
|
+
| `@describe <Name>` | Include in section `Name`'s text table only |
|
|
100
|
+
| `@hidden` | Exclude entity from the entire document |
|
|
97
101
|
|
|
98
102
|
Entities with no tag are placed in the `default` section.
|
|
99
103
|
An entity can carry multiple tags to appear in more than one section.
|
|
@@ -127,21 +131,21 @@ erDiagram
|
|
|
127
131
|
|
|
128
132
|
> Blog post authored by a registered user.
|
|
129
133
|
|
|
130
|
-
| Column
|
|
131
|
-
|
|
132
|
-
| id
|
|
133
|
-
| title
|
|
134
|
-
| body
|
|
135
|
-
| author_id | integer | FK (author) |
|
|
134
|
+
| Column | Type | Key | Nullable | Description |
|
|
135
|
+
| --------- | ------- | ----------- | -------- | ----------- |
|
|
136
|
+
| id | integer | PK | | |
|
|
137
|
+
| title | string | | | Post title |
|
|
138
|
+
| body | text | | Y | |
|
|
139
|
+
| author_id | integer | FK (author) | | |
|
|
136
140
|
````
|
|
137
141
|
|
|
138
142
|
MikroORM-specific annotations in the **Key** column:
|
|
139
143
|
|
|
140
|
-
| Annotation
|
|
141
|
-
|
|
142
|
-
| `formula: <expr>`
|
|
144
|
+
| Annotation | Meaning |
|
|
145
|
+
| ------------------ | ---------------------------------------------------- |
|
|
146
|
+
| `formula: <expr>` | `@Formula` computed column — no physical DB column |
|
|
143
147
|
| `[EmbeddableType]` | Flat column inlined from an `@Embedded` value object |
|
|
144
|
-
| `discriminator`
|
|
148
|
+
| `discriminator` | STI discriminator column |
|
|
145
149
|
|
|
146
150
|
## Notes
|
|
147
151
|
|
package/dist/cli.cjs
CHANGED
|
@@ -33,7 +33,9 @@ var import_ts_morph = require("ts-morph");
|
|
|
33
33
|
function loadJsDoc(srcGlobs) {
|
|
34
34
|
const entities = /* @__PURE__ */ new Map();
|
|
35
35
|
const props = /* @__PURE__ */ new Map();
|
|
36
|
-
if (srcGlobs.length === 0)
|
|
36
|
+
if (srcGlobs.length === 0) {
|
|
37
|
+
return { entities, props };
|
|
38
|
+
}
|
|
37
39
|
const project = new import_ts_morph.Project({
|
|
38
40
|
skipAddingFilesFromTsConfig: true,
|
|
39
41
|
skipLoadingLibFiles: true,
|
|
@@ -46,7 +48,9 @@ function loadJsDoc(srcGlobs) {
|
|
|
46
48
|
for (const sourceFile of project.getSourceFiles()) {
|
|
47
49
|
for (const cls of sourceFile.getClasses()) {
|
|
48
50
|
const className = cls.getName();
|
|
49
|
-
if (!className)
|
|
51
|
+
if (!className) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
50
54
|
const classDocs = cls.getJsDocs();
|
|
51
55
|
if (classDocs.length > 0) {
|
|
52
56
|
entities.set(className, parseEntityJsDoc(classDocs));
|
|
@@ -54,7 +58,9 @@ function loadJsDoc(srcGlobs) {
|
|
|
54
58
|
const propMap = /* @__PURE__ */ new Map();
|
|
55
59
|
for (const prop of cls.getProperties()) {
|
|
56
60
|
const propDocs = prop.getJsDocs();
|
|
57
|
-
if (propDocs.length === 0)
|
|
61
|
+
if (propDocs.length === 0) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
58
64
|
const desc = extractDescription(propDocs);
|
|
59
65
|
if (desc !== void 0) {
|
|
60
66
|
propMap.set(prop.getName(), { description: desc });
|
|
@@ -75,14 +81,21 @@ function parseEntityJsDoc(jsDocs) {
|
|
|
75
81
|
let description;
|
|
76
82
|
for (const doc of jsDocs) {
|
|
77
83
|
const desc = doc.getDescription().trim();
|
|
78
|
-
if (desc && description === void 0)
|
|
84
|
+
if (desc && description === void 0) {
|
|
85
|
+
description = desc;
|
|
86
|
+
}
|
|
79
87
|
for (const tag of doc.getTags()) {
|
|
80
88
|
const tagName = tag.getTagName();
|
|
81
89
|
const comment = tag.getCommentText()?.trim();
|
|
82
|
-
if (tagName === "namespace" && comment)
|
|
83
|
-
|
|
84
|
-
else if (tagName === "
|
|
85
|
-
|
|
90
|
+
if (tagName === "namespace" && comment) {
|
|
91
|
+
namespaces.push(comment);
|
|
92
|
+
} else if (tagName === "erd" && comment) {
|
|
93
|
+
erdNamespaces.push(comment);
|
|
94
|
+
} else if (tagName === "describe" && comment) {
|
|
95
|
+
describeNamespaces.push(comment);
|
|
96
|
+
} else if (tagName === "hidden") {
|
|
97
|
+
hidden = true;
|
|
98
|
+
}
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
return {
|
|
@@ -96,7 +109,9 @@ function parseEntityJsDoc(jsDocs) {
|
|
|
96
109
|
function extractDescription(jsDocs) {
|
|
97
110
|
for (const doc of jsDocs) {
|
|
98
111
|
const desc = doc.getDescription().trim();
|
|
99
|
-
if (desc)
|
|
112
|
+
if (desc) {
|
|
113
|
+
return desc;
|
|
114
|
+
}
|
|
100
115
|
}
|
|
101
116
|
return void 0;
|
|
102
117
|
}
|
|
@@ -115,7 +130,8 @@ async function loadEntityMetadata(options) {
|
|
|
115
130
|
try {
|
|
116
131
|
orm = await import_core.MikroORM.init({
|
|
117
132
|
...options,
|
|
118
|
-
debug: false
|
|
133
|
+
debug: false,
|
|
134
|
+
connect: false
|
|
119
135
|
});
|
|
120
136
|
} catch (cause) {
|
|
121
137
|
throw new MetadataLoadError(
|
|
@@ -123,17 +139,13 @@ async function loadEntityMetadata(options) {
|
|
|
123
139
|
cause
|
|
124
140
|
);
|
|
125
141
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
return all;
|
|
134
|
-
} finally {
|
|
135
|
-
await orm.close(true);
|
|
142
|
+
const all = Object.values(orm.getMetadata().getAll());
|
|
143
|
+
if (all.length === 0) {
|
|
144
|
+
throw new MetadataLoadError(
|
|
145
|
+
"No entities were discovered. Check that your config specifies at least one entity path or class."
|
|
146
|
+
);
|
|
136
147
|
}
|
|
148
|
+
return all;
|
|
137
149
|
}
|
|
138
150
|
|
|
139
151
|
// src/render/mermaid.ts
|
|
@@ -155,9 +167,13 @@ function buildEntityModel(meta, metaByClass) {
|
|
|
155
167
|
const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== void 0;
|
|
156
168
|
const columns = [];
|
|
157
169
|
for (const prop of Object.values(meta.properties)) {
|
|
158
|
-
if (isStiRoot && prop.inherited === true)
|
|
170
|
+
if (isStiRoot && prop.inherited === true) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
159
173
|
const col = buildColumn(prop, metaByClass, meta);
|
|
160
|
-
if (col !== null)
|
|
174
|
+
if (col !== null) {
|
|
175
|
+
columns.push(col);
|
|
176
|
+
}
|
|
161
177
|
}
|
|
162
178
|
return {
|
|
163
179
|
className: meta.className,
|
|
@@ -171,7 +187,9 @@ function buildEntityModel(meta, metaByClass) {
|
|
|
171
187
|
};
|
|
172
188
|
}
|
|
173
189
|
function buildColumn(prop, metaByClass, owningMeta) {
|
|
174
|
-
if (prop.kind === import_core2.ReferenceKind.EMBEDDED)
|
|
190
|
+
if (prop.kind === import_core2.ReferenceKind.EMBEDDED) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
175
193
|
if (prop.kind === import_core2.ReferenceKind.SCALAR) {
|
|
176
194
|
const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
|
|
177
195
|
let embeddedIn;
|
|
@@ -209,9 +227,12 @@ function buildColumn(prop, metaByClass, owningMeta) {
|
|
|
209
227
|
}
|
|
210
228
|
function resolveFormulaExpr(cb) {
|
|
211
229
|
try {
|
|
212
|
-
const cols = new Proxy(
|
|
213
|
-
|
|
214
|
-
|
|
230
|
+
const cols = new Proxy(
|
|
231
|
+
{},
|
|
232
|
+
{
|
|
233
|
+
get: (_target, key) => typeof key === "string" ? key : ""
|
|
234
|
+
}
|
|
235
|
+
);
|
|
215
236
|
return cb(FORMULA_DUMMY_TABLE, cols);
|
|
216
237
|
} catch {
|
|
217
238
|
return "";
|
|
@@ -219,7 +240,9 @@ function resolveFormulaExpr(cb) {
|
|
|
219
240
|
}
|
|
220
241
|
function resolveFkType(referencedClassName, metaByClass) {
|
|
221
242
|
const refMeta = metaByClass.get(referencedClassName);
|
|
222
|
-
if (!refMeta)
|
|
243
|
+
if (!refMeta) {
|
|
244
|
+
return "integer";
|
|
245
|
+
}
|
|
223
246
|
const pkProp = Object.values(refMeta.properties).find((p) => p.primary === true);
|
|
224
247
|
return pkProp ? normalizeType(pkProp.type) : "integer";
|
|
225
248
|
}
|
|
@@ -242,7 +265,9 @@ function buildConstraints(meta) {
|
|
|
242
265
|
});
|
|
243
266
|
}
|
|
244
267
|
for (const check of meta.checks ?? []) {
|
|
245
|
-
if (typeof check.expression !== "string")
|
|
268
|
+
if (typeof check.expression !== "string") {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
246
271
|
result.push({
|
|
247
272
|
type: "check",
|
|
248
273
|
properties: [],
|
|
@@ -255,10 +280,14 @@ function buildConstraints(meta) {
|
|
|
255
280
|
function buildRelationEdges(metas) {
|
|
256
281
|
const edges = [];
|
|
257
282
|
for (const meta of metas) {
|
|
258
|
-
if (meta.pivotTable || meta.embeddable)
|
|
283
|
+
if (meta.pivotTable || meta.embeddable) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
259
286
|
for (const prop of Object.values(meta.properties)) {
|
|
260
287
|
const edge = buildEdge(meta.className, prop);
|
|
261
|
-
if (edge !== null)
|
|
288
|
+
if (edge !== null) {
|
|
289
|
+
edges.push(edge);
|
|
290
|
+
}
|
|
262
291
|
}
|
|
263
292
|
}
|
|
264
293
|
return edges;
|
|
@@ -304,17 +333,19 @@ function renderErDiagram(model) {
|
|
|
304
333
|
lines.push(" }");
|
|
305
334
|
}
|
|
306
335
|
for (const rel of model.relations) {
|
|
307
|
-
lines.push(
|
|
308
|
-
` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : "${rel.label}"`
|
|
309
|
-
);
|
|
336
|
+
lines.push(` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : "${rel.label}"`);
|
|
310
337
|
}
|
|
311
338
|
return lines.join("\n");
|
|
312
339
|
}
|
|
313
340
|
function renderColumnLine(col) {
|
|
314
341
|
let qualifier = "";
|
|
315
|
-
if (col.isPrimary)
|
|
316
|
-
|
|
317
|
-
else if (col.
|
|
342
|
+
if (col.isPrimary) {
|
|
343
|
+
qualifier = " PK";
|
|
344
|
+
} else if (col.isForeignKey) {
|
|
345
|
+
qualifier = " FK";
|
|
346
|
+
} else if (col.isUnique) {
|
|
347
|
+
qualifier = " UK";
|
|
348
|
+
}
|
|
318
349
|
let comment;
|
|
319
350
|
if (col.formula !== void 0) {
|
|
320
351
|
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
@@ -338,25 +369,27 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
338
369
|
const enrichedByClass = /* @__PURE__ */ new Map();
|
|
339
370
|
for (const model of diagramEntities) {
|
|
340
371
|
const jsDoc = jsDocResult.entities.get(model.className);
|
|
341
|
-
if (jsDoc?.hidden)
|
|
372
|
+
if (jsDoc?.hidden) {
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
342
375
|
const propDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
|
|
343
376
|
enrichedByClass.set(model.className, { model, jsDoc, propDocs });
|
|
344
377
|
}
|
|
345
378
|
const groupNames = /* @__PURE__ */ new Set();
|
|
346
379
|
let anyUntagged = false;
|
|
347
380
|
for (const { jsDoc } of enrichedByClass.values()) {
|
|
348
|
-
const allNs = [
|
|
349
|
-
...jsDoc?.namespaces ?? [],
|
|
350
|
-
...jsDoc?.erdNamespaces ?? [],
|
|
351
|
-
...jsDoc?.describeNamespaces ?? []
|
|
352
|
-
];
|
|
381
|
+
const allNs = [...jsDoc?.namespaces ?? [], ...jsDoc?.erdNamespaces ?? [], ...jsDoc?.describeNamespaces ?? []];
|
|
353
382
|
if (allNs.length === 0) {
|
|
354
383
|
anyUntagged = true;
|
|
355
384
|
} else {
|
|
356
|
-
for (const ns of allNs)
|
|
385
|
+
for (const ns of allNs) {
|
|
386
|
+
groupNames.add(ns);
|
|
387
|
+
}
|
|
357
388
|
}
|
|
358
389
|
}
|
|
359
|
-
if (anyUntagged)
|
|
390
|
+
if (anyUntagged) {
|
|
391
|
+
groupNames.add("default");
|
|
392
|
+
}
|
|
360
393
|
const groups = [];
|
|
361
394
|
for (const groupName of groupNames) {
|
|
362
395
|
const isDefault = groupName === "default";
|
|
@@ -367,30 +400,42 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
367
400
|
({ jsDoc }) => belongsToGroupForText(jsDoc, groupName, isDefault)
|
|
368
401
|
);
|
|
369
402
|
const erdClassNames = new Set(erdEntities.map((e) => e.model.className));
|
|
370
|
-
const erdRelations = allRelations.filter(
|
|
371
|
-
(r) => erdClassNames.has(r.fromEntity) && erdClassNames.has(r.toEntity)
|
|
372
|
-
);
|
|
403
|
+
const erdRelations = allRelations.filter((r) => erdClassNames.has(r.fromEntity) && erdClassNames.has(r.toEntity));
|
|
373
404
|
groups.push({ name: groupName, erdEntities, textEntities, erdRelations });
|
|
374
405
|
}
|
|
375
406
|
groups.sort((a, b) => {
|
|
376
|
-
if (a.name === "default")
|
|
377
|
-
|
|
407
|
+
if (a.name === "default") {
|
|
408
|
+
return 1;
|
|
409
|
+
}
|
|
410
|
+
if (b.name === "default") {
|
|
411
|
+
return -1;
|
|
412
|
+
}
|
|
378
413
|
return a.name.localeCompare(b.name);
|
|
379
414
|
});
|
|
380
415
|
return { title, groups, ...description !== void 0 && { description } };
|
|
381
416
|
}
|
|
382
417
|
function hasNoNamespaceTags(jsDoc) {
|
|
383
|
-
if (!jsDoc)
|
|
418
|
+
if (!jsDoc) {
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
384
421
|
return jsDoc.namespaces.length === 0 && jsDoc.erdNamespaces.length === 0 && jsDoc.describeNamespaces.length === 0;
|
|
385
422
|
}
|
|
386
423
|
function belongsToGroupForErd(jsDoc, groupName, isDefault) {
|
|
387
|
-
if (isDefault)
|
|
388
|
-
|
|
424
|
+
if (isDefault) {
|
|
425
|
+
return hasNoNamespaceTags(jsDoc);
|
|
426
|
+
}
|
|
427
|
+
if (!jsDoc) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
389
430
|
return jsDoc.namespaces.includes(groupName) || jsDoc.erdNamespaces.includes(groupName);
|
|
390
431
|
}
|
|
391
432
|
function belongsToGroupForText(jsDoc, groupName, isDefault) {
|
|
392
|
-
if (isDefault)
|
|
393
|
-
|
|
433
|
+
if (isDefault) {
|
|
434
|
+
return hasNoNamespaceTags(jsDoc);
|
|
435
|
+
}
|
|
436
|
+
if (!jsDoc) {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
394
439
|
return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
|
|
395
440
|
}
|
|
396
441
|
|
|
@@ -425,9 +470,7 @@ function renderEntitySection(entity) {
|
|
|
425
470
|
parts.push(`> ${entity.jsDoc.description}`);
|
|
426
471
|
}
|
|
427
472
|
if (entity.model.discriminatorColumn) {
|
|
428
|
-
parts.push(
|
|
429
|
-
`*STI root \u2014 discriminator column: \`${entity.model.discriminatorColumn}\`*`
|
|
430
|
-
);
|
|
473
|
+
parts.push(`*STI root \u2014 discriminator column: \`${entity.model.discriminatorColumn}\`*`);
|
|
431
474
|
} else if (entity.model.extendsEntity) {
|
|
432
475
|
parts.push(`*Extends \`${entity.model.extendsEntity}\` (Single Table Inheritance)*`);
|
|
433
476
|
}
|
|
@@ -451,14 +494,24 @@ function renderColumnTable(entity) {
|
|
|
451
494
|
return [header, sep, ...rows].join("\n");
|
|
452
495
|
}
|
|
453
496
|
function resolveColumnKey(col) {
|
|
454
|
-
if (col.isPrimary)
|
|
497
|
+
if (col.isPrimary) {
|
|
498
|
+
return "PK";
|
|
499
|
+
}
|
|
455
500
|
if (col.isForeignKey) {
|
|
456
501
|
return col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
|
|
457
502
|
}
|
|
458
|
-
if (col.isUnique)
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (col.
|
|
503
|
+
if (col.isUnique) {
|
|
504
|
+
return "UK";
|
|
505
|
+
}
|
|
506
|
+
if (col.formula !== void 0) {
|
|
507
|
+
return `formula: ${col.formula}`;
|
|
508
|
+
}
|
|
509
|
+
if (col.isDiscriminator) {
|
|
510
|
+
return "discriminator";
|
|
511
|
+
}
|
|
512
|
+
if (col.embeddedIn !== void 0) {
|
|
513
|
+
return `[${col.embeddedIn}]`;
|
|
514
|
+
}
|
|
462
515
|
return "";
|
|
463
516
|
}
|
|
464
517
|
function renderConstraints(constraints) {
|
|
@@ -507,7 +560,7 @@ ${err instanceof Error ? err.message : String(err)}${hint}
|
|
|
507
560
|
markdown = await generateMarkdown({
|
|
508
561
|
orm: ormOptions,
|
|
509
562
|
title: opts.title,
|
|
510
|
-
src: opts.src,
|
|
563
|
+
src: opts.src ?? [],
|
|
511
564
|
...opts.description !== void 0 && { description: opts.description }
|
|
512
565
|
});
|
|
513
566
|
} catch (err) {
|
|
@@ -520,12 +573,7 @@ ${err instanceof Error ? err.message : String(err)}${hint}
|
|
|
520
573
|
process.stdout.write(`\u2713 Written to ${path.relative(process.cwd(), outPath)}
|
|
521
574
|
`);
|
|
522
575
|
}
|
|
523
|
-
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(
|
|
524
|
-
"-s, --src <glob>",
|
|
525
|
-
"Glob pattern for entity source files, repeatable (for JSDoc extraction)",
|
|
526
|
-
(val, prev) => [...prev, val],
|
|
527
|
-
[]
|
|
528
|
-
).action(run);
|
|
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("-s, --src <glob>", "JSDoc source glob (repeatable)", (val, prev = []) => [...prev, val]).action(run);
|
|
529
577
|
program.parseAsync(process.argv).catch((err) => {
|
|
530
578
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
531
579
|
`);
|