mikro-orm-markdown 0.1.1 → 0.1.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/docs/jsdoc.ts
2
- import { Project } from "ts-morph";
2
+ import { Project, ts } from "ts-morph";
3
3
  function loadJsDoc(filePaths, onWarn) {
4
4
  const entities = /* @__PURE__ */ new Map();
5
5
  const props = /* @__PURE__ */ new Map();
@@ -38,17 +38,7 @@ function loadJsDoc(filePaths, onWarn) {
38
38
  if (classDocs.length > 0) {
39
39
  entities.set(className, parseEntityJsDoc(classDocs));
40
40
  }
41
- const propMap = /* @__PURE__ */ new Map();
42
- for (const prop of cls.getProperties()) {
43
- const propDocs = prop.getJsDocs();
44
- if (propDocs.length === 0) {
45
- continue;
46
- }
47
- const info = parsePropJsDoc(propDocs);
48
- if (info.description !== void 0 || info.atLeastOne) {
49
- propMap.set(prop.getName(), info);
50
- }
51
- }
41
+ const propMap = collectPropJsDocs(cls);
52
42
  if (propMap.size > 0) {
53
43
  props.set(className, propMap);
54
44
  }
@@ -65,6 +55,28 @@ function formatUnknownError(err) {
65
55
  function hasGlobPattern(filePath) {
66
56
  return /[*?[\]{}]/.test(filePath);
67
57
  }
58
+ function collectPropJsDocs(cls) {
59
+ const propMap = /* @__PURE__ */ new Map();
60
+ for (const prop of [...cls.getProperties(), ...cls.getGetAccessors()]) {
61
+ const info = parsePropJsDoc(prop.getJsDocs());
62
+ addPropInfo(propMap, prop.getName(), info);
63
+ }
64
+ for (const prop of getConstructorParameterProperties(cls)) {
65
+ const info = parseCompilerPropJsDoc(ts.getJSDocCommentsAndTags(prop.compilerNode));
66
+ addPropInfo(propMap, prop.getName(), info);
67
+ }
68
+ return propMap;
69
+ }
70
+ function getConstructorParameterProperties(cls) {
71
+ return cls.getConstructors().flatMap(
72
+ (constructorDeclaration) => constructorDeclaration.getParameters().filter((param) => param.isParameterProperty())
73
+ );
74
+ }
75
+ function addPropInfo(propMap, propName, info) {
76
+ if (info.description !== void 0 || info.atLeastOne) {
77
+ propMap.set(propName, info);
78
+ }
79
+ }
68
80
  function parseEntityJsDoc(jsDocs) {
69
81
  const namespaces = [];
70
82
  const erdNamespaces = [];
@@ -114,10 +126,39 @@ function parsePropJsDoc(jsDocs) {
114
126
  }
115
127
  return { ...description !== void 0 && { description }, atLeastOne };
116
128
  }
129
+ function parseCompilerPropJsDoc(jsDocs) {
130
+ let description;
131
+ let atLeastOne = false;
132
+ for (const doc of jsDocs) {
133
+ if (ts.isJSDoc(doc)) {
134
+ const desc = formatCompilerJsDocComment(doc.comment);
135
+ if (desc && description === void 0) {
136
+ description = desc;
137
+ }
138
+ for (const tag of doc.tags ?? []) {
139
+ if (tag.tagName.getText() === "atLeastOne") {
140
+ atLeastOne = true;
141
+ }
142
+ }
143
+ continue;
144
+ }
145
+ if (doc.tagName.getText() === "atLeastOne") {
146
+ atLeastOne = true;
147
+ }
148
+ }
149
+ return { ...description !== void 0 && { description }, atLeastOne };
150
+ }
151
+ function formatCompilerJsDocComment(comment) {
152
+ if (comment === void 0) {
153
+ return void 0;
154
+ }
155
+ const trimmed = ts.getTextOfJSDocComment(comment)?.trim();
156
+ return trimmed === void 0 || trimmed === "" ? void 0 : trimmed;
157
+ }
117
158
 
118
159
  // src/metadata/load.ts
119
160
  import * as path from "path";
120
- import { EntitySchema, MikroORM } from "@mikro-orm/core";
161
+ import { EntitySchema, MetadataStorage, MikroORM } from "@mikro-orm/core";
121
162
  var MetadataLoadError = class extends Error {
122
163
  constructor(message, cause) {
123
164
  super(message);
@@ -153,6 +194,44 @@ function assertNoEntitySchemaEntities(options) {
153
194
  Use decorator-based @Entity() classes instead.`
154
195
  );
155
196
  }
197
+ function hasDecoratorMarker(target) {
198
+ const pathSymbol = MetadataStorage.PATH_SYMBOL;
199
+ if (typeof pathSymbol === "symbol" && pathSymbol in target) {
200
+ return true;
201
+ }
202
+ return "__path" in target;
203
+ }
204
+ function isRenderableMeta(meta) {
205
+ return !meta.pivotTable && !meta.embeddable;
206
+ }
207
+ function assertDiscoveredEntitiesAreSupported(metas) {
208
+ const confirmed = [];
209
+ const unconfirmed = [];
210
+ for (const meta of metas) {
211
+ if (!isRenderableMeta(meta)) {
212
+ continue;
213
+ }
214
+ if (EntitySchema.REGISTRY.has(meta.class)) {
215
+ confirmed.push(meta.className);
216
+ } else if (!hasDecoratorMarker(meta.class)) {
217
+ unconfirmed.push(meta.className);
218
+ }
219
+ }
220
+ if (confirmed.length === 0 && unconfirmed.length === 0) {
221
+ return;
222
+ }
223
+ const lines = [];
224
+ if (confirmed.length > 0) {
225
+ lines.push(`EntitySchema-defined entities are not currently supported: ${confirmed.join(", ")}.`);
226
+ }
227
+ if (unconfirmed.length > 0) {
228
+ lines.push(
229
+ `Could not confirm these entities are decorator-based @Entity() classes: ${unconfirmed.join(", ")}. This usually means they are EntitySchema-defined entities (also not currently supported). If you're certain these are valid @Entity() classes, this may be a detection false positive in mikro-orm-markdown \u2014 please open an issue: https://github.com/iamkanguk97/mikro-orm-markdown/issues`
230
+ );
231
+ }
232
+ lines.push("Use decorator-based @Entity() classes instead.");
233
+ throw new MetadataLoadError(lines.join("\n"));
234
+ }
156
235
  async function loadEntityMetadata(options) {
157
236
  assertNoEntitySchemaEntities(options);
158
237
  let orm;
@@ -179,6 +258,7 @@ async function loadEntityMetadata(options) {
179
258
  "No entities were discovered. Check that your config specifies at least one entity path or class."
180
259
  );
181
260
  }
261
+ assertDiscoveredEntitiesAreSupported(all);
182
262
  const baseDir = orm.config.get("baseDir");
183
263
  const sourcePaths = [...new Set(all.filter((m) => m.path).map((m) => path.resolve(baseDir, m.path)))];
184
264
  return { metas: all, sourcePaths };
@@ -190,55 +270,8 @@ async function loadEntityMetadata(options) {
190
270
  // src/model/build.ts
191
271
  import { ReferenceKind as ReferenceKind2 } from "@mikro-orm/core";
192
272
 
193
- // src/render/mermaid.ts
273
+ // src/model/diagram.ts
194
274
  import { ReferenceKind } from "@mikro-orm/core";
195
-
196
- // src/render/escape.ts
197
- var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#]/g;
198
- var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
199
- function normalizeInlineText(value) {
200
- return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
201
- }
202
- function splitNormalizedLines(value) {
203
- return value.replace(/\r\n?/g, "\n").split("\n");
204
- }
205
- function escapeHtmlText(value) {
206
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
207
- }
208
- function escapeMarkdownInline(value) {
209
- return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&");
210
- }
211
- function escapeMarkdownTableCell(value) {
212
- return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
213
- }
214
- function escapeMarkdownParagraph(value) {
215
- return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
216
- }
217
- function renderMarkdownBlockQuote(value) {
218
- return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
219
- }
220
- function renderMarkdownInlineCode(value) {
221
- const normalized = normalizeInlineText(value);
222
- const backtickRuns = normalized.match(/`+/g) ?? [];
223
- const longestRun = Math.max(0, ...backtickRuns.map((run) => run.length));
224
- const fence = "`".repeat(longestRun + 1);
225
- const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
226
- const content = needsPadding ? ` ${normalized} ` : normalized;
227
- return `${fence}${content}${fence}`;
228
- }
229
- function toMermaidIdentifier(value) {
230
- const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
231
- const identifier = normalized === "" ? "_" : normalized;
232
- return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
233
- }
234
- function escapeMermaidQuotedText(value) {
235
- return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
236
- }
237
- function toMarkdownAnchor(value) {
238
- return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
239
- }
240
-
241
- // src/render/mermaid.ts
242
275
  var FORMULA_DUMMY_TABLE = {
243
276
  alias: "e0",
244
277
  name: "",
@@ -509,75 +542,6 @@ function buildEdge(fromEntity, prop) {
509
542
  }
510
543
  return null;
511
544
  }
512
- function normalizeType(type) {
513
- const t = type.toLowerCase().trim();
514
- if (t === "uuid" || t === "text" || t === "string" || t.startsWith("varchar")) {
515
- return "string";
516
- }
517
- if (t === "timestamptz" || t === "timestamp" || t === "datetime") {
518
- return "datetime";
519
- }
520
- if (t === "integer" || t === "int" || t === "bigint" || t === "smallint") {
521
- return "integer";
522
- }
523
- if (t === "doubletype" || t === "double precision" || t === "double" || t === "float" || t === "decimal") {
524
- return "float";
525
- }
526
- if (t === "boolean" || t === "bool") {
527
- return "boolean";
528
- }
529
- if (t === "jsonb") {
530
- return "json";
531
- }
532
- return type;
533
- }
534
- function renderErDiagram(model, mermaid) {
535
- const lines = [];
536
- if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
537
- lines.push("---", "config:");
538
- if (mermaid.layout !== void 0) {
539
- lines.push(` layout: ${mermaid.layout}`);
540
- }
541
- if (mermaid.theme !== void 0) {
542
- lines.push(` theme: ${mermaid.theme}`);
543
- }
544
- lines.push("---");
545
- }
546
- lines.push("erDiagram");
547
- for (const entity of model.entities) {
548
- lines.push(` ${toMermaidIdentifier(entity.className)} {`);
549
- for (const col of entity.columns) {
550
- lines.push(` ${renderColumnLine(col)}`);
551
- }
552
- lines.push(" }");
553
- }
554
- for (const rel of model.relations) {
555
- lines.push(
556
- ` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
557
- );
558
- }
559
- return lines.join("\n");
560
- }
561
- function renderColumnLine(col) {
562
- let qualifier = "";
563
- if (col.isPrimary) {
564
- qualifier = " PK";
565
- } else if (col.isUnique) {
566
- qualifier = " UK";
567
- }
568
- let comment;
569
- if (col.formula !== void 0) {
570
- comment = col.formula ? `formula: ${col.formula}` : "formula";
571
- } else if (col.isDiscriminator) {
572
- comment = "discriminator";
573
- } else if (col.embeddedIn !== void 0) {
574
- comment = `[${col.embeddedIn}]`;
575
- } else if (col.isSelfReference) {
576
- comment = "self-ref";
577
- }
578
- const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
579
- return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
580
- }
581
545
 
582
546
  // src/model/build.ts
583
547
  var FROM_ONE_OR_MORE = "}|";
@@ -600,7 +564,10 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
600
564
  const columns = model.columns.filter(
601
565
  (col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
602
566
  );
603
- const visibleModel = columns.length === model.columns.length ? model : { ...model, columns };
567
+ const visibleModel = removeHiddenEntityReferences(
568
+ columns.length === model.columns.length ? model : { ...model, columns },
569
+ hiddenClasses
570
+ );
604
571
  const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
605
572
  const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
606
573
  enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
@@ -651,6 +618,14 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
651
618
  });
652
619
  return { title, groups, ...description !== void 0 && { description } };
653
620
  }
621
+ function removeHiddenEntityReferences(model, hiddenClasses) {
622
+ if (model.extendsEntity === void 0 || !hiddenClasses.has(model.extendsEntity)) {
623
+ return model;
624
+ }
625
+ const visibleModel = { ...model };
626
+ delete visibleModel.extendsEntity;
627
+ return visibleModel;
628
+ }
654
629
  function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
655
630
  const merged = new Map(ownPropDocs);
656
631
  for (const col of columns) {
@@ -762,6 +737,142 @@ async function withTsMorphMetadataProvider(options, onWarn) {
762
737
  }
763
738
  }
764
739
 
740
+ // src/render/escape.ts
741
+ var MARKDOWN_INLINE_SPECIAL_CHARS = /[\\`|*#\[\]]/g;
742
+ var MARKDOWN_EMPHASIS_UNDERSCORE = /(?<![a-zA-Z0-9])_|_(?![a-zA-Z0-9])/g;
743
+ var MERMAID_IDENTIFIER_INVALID_CHARS = /[^a-zA-Z0-9_]/g;
744
+ function normalizeInlineText(value) {
745
+ return value.replace(/\r\n?/g, "\n").replace(/[\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim();
746
+ }
747
+ function splitNormalizedLines(value) {
748
+ return value.replace(/\r\n?/g, "\n").split("\n");
749
+ }
750
+ function escapeHtmlText(value) {
751
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
752
+ }
753
+ function escapeMarkdownInline(value) {
754
+ return escapeHtmlText(normalizeInlineText(value)).replace(MARKDOWN_INLINE_SPECIAL_CHARS, "\\$&").replace(MARKDOWN_EMPHASIS_UNDERSCORE, "\\_");
755
+ }
756
+ function escapeMarkdownTableCell(value) {
757
+ return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join("<br>");
758
+ }
759
+ function escapeMarkdownParagraph(value) {
760
+ return splitNormalizedLines(value).map((line) => escapeMarkdownInline(line)).join(" \n");
761
+ }
762
+ function renderMarkdownBlockQuote(value) {
763
+ return splitNormalizedLines(value).map((line) => `> ${escapeMarkdownInline(line)}`).join("\n");
764
+ }
765
+ function renderMarkdownInlineCode(value) {
766
+ const normalized = normalizeInlineText(value);
767
+ const backtickRuns = normalized.match(/`+/g) ?? [];
768
+ const longestRun = Math.max(0, ...backtickRuns.map((run) => run.length));
769
+ const fence = "`".repeat(longestRun + 1);
770
+ const needsPadding = normalized.startsWith("`") || normalized.endsWith("`");
771
+ const content = needsPadding ? ` ${normalized} ` : normalized;
772
+ return `${fence}${content}${fence}`;
773
+ }
774
+ function toMermaidIdentifier(value) {
775
+ const normalized = normalizeInlineText(value).replace(MERMAID_IDENTIFIER_INVALID_CHARS, "_").replace(/_+/g, "_");
776
+ const identifier = normalized === "" ? "_" : normalized;
777
+ return /^[a-zA-Z_]/.test(identifier) ? identifier : `_${identifier}`;
778
+ }
779
+ function escapeMermaidQuotedText(value) {
780
+ return normalizeInlineText(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
781
+ }
782
+ function toMarkdownAnchor(value) {
783
+ return normalizeInlineText(value).toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s+/g, "-");
784
+ }
785
+
786
+ // src/render/mermaid.ts
787
+ var GENERIC_TYPE_BY_BASE_NAME = /* @__PURE__ */ new Map([
788
+ ["uuid", "string"],
789
+ ["text", "string"],
790
+ ["string", "string"],
791
+ ["varchar", "string"],
792
+ ["character varying", "string"],
793
+ ["character", "string"],
794
+ ["char", "string"],
795
+ ["tinytext", "string"],
796
+ ["mediumtext", "string"],
797
+ ["longtext", "string"],
798
+ ["timestamptz", "datetime"],
799
+ ["timestamp", "datetime"],
800
+ ["datetime", "datetime"],
801
+ ["integer", "integer"],
802
+ ["int", "integer"],
803
+ ["bigint", "integer"],
804
+ ["smallint", "integer"],
805
+ ["tinyint", "integer"],
806
+ ["mediumint", "integer"],
807
+ ["serial", "integer"],
808
+ ["bigserial", "integer"],
809
+ ["doubletype", "float"],
810
+ ["double precision", "float"],
811
+ ["double", "float"],
812
+ ["float", "float"],
813
+ ["decimal", "float"],
814
+ ["numeric", "float"],
815
+ ["real", "float"],
816
+ ["boolean", "boolean"],
817
+ ["bool", "boolean"],
818
+ ["jsonb", "json"]
819
+ ]);
820
+ function normalizeType(type) {
821
+ const t = type.toLowerCase().replace(/\s+/g, " ").trim();
822
+ if (/^tinyint\s*\(\s*1\s*\)$/.test(t)) {
823
+ return "boolean";
824
+ }
825
+ const baseName = t.replace(/\(.*\)/, "").replace(/\b(un)?signed\b/, "").trim();
826
+ return GENERIC_TYPE_BY_BASE_NAME.get(baseName) ?? type;
827
+ }
828
+ function renderErDiagram(model, mermaid) {
829
+ const lines = [];
830
+ if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
831
+ lines.push("---", "config:");
832
+ if (mermaid.layout !== void 0) {
833
+ lines.push(` layout: ${mermaid.layout}`);
834
+ }
835
+ if (mermaid.theme !== void 0) {
836
+ lines.push(` theme: ${mermaid.theme}`);
837
+ }
838
+ lines.push("---");
839
+ }
840
+ lines.push("erDiagram");
841
+ for (const entity of model.entities) {
842
+ lines.push(` ${toMermaidIdentifier(entity.className)} {`);
843
+ for (const col of entity.columns) {
844
+ lines.push(` ${renderColumnLine(col)}`);
845
+ }
846
+ lines.push(" }");
847
+ }
848
+ for (const rel of model.relations) {
849
+ lines.push(
850
+ ` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
851
+ );
852
+ }
853
+ return lines.join("\n");
854
+ }
855
+ function renderColumnLine(col) {
856
+ let qualifier = "";
857
+ if (col.isPrimary) {
858
+ qualifier = " PK";
859
+ } else if (col.isUnique) {
860
+ qualifier = " UK";
861
+ }
862
+ let comment;
863
+ if (col.formula !== void 0) {
864
+ comment = col.formula ? `formula: ${col.formula}` : "formula";
865
+ } else if (col.isDiscriminator) {
866
+ comment = "discriminator";
867
+ } else if (col.embeddedIn !== void 0) {
868
+ comment = `[${col.embeddedIn}]`;
869
+ } else if (col.isSelfReference) {
870
+ comment = "self-ref";
871
+ }
872
+ const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
873
+ return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
874
+ }
875
+
765
876
  // src/render/markdown.ts
766
877
  function renderMarkdown(docModel, mermaid) {
767
878
  const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
@@ -785,7 +896,7 @@ function renderBulletSection(header, items, renderItem) {
785
896
  }
786
897
  function renderTableOfContents(groups, anchors) {
787
898
  return renderBulletSection("## Contents", groups, (group) => {
788
- const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
899
+ const label = escapeMarkdownInline(group.name);
789
900
  return `- [${label}](#${anchors.get(group) ?? toMarkdownAnchor(group.name)})`;
790
901
  });
791
902
  }
@@ -870,7 +981,7 @@ function resolveColumnKey(col) {
870
981
  return "PK";
871
982
  }
872
983
  if (col.isForeignKey) {
873
- return fkKey;
984
+ return col.isUnique ? `${fkKey}, UK` : fkKey;
874
985
  }
875
986
  if (col.isUnique) {
876
987
  return "UK";