mikro-orm-markdown 0.1.0 → 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: "",
@@ -298,6 +331,9 @@ function buildColumns(prop, metaByClass, owningMeta) {
298
331
  return [];
299
332
  }
300
333
  const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
334
+ if (prop.persist === false && formulaExpr === void 0) {
335
+ return [];
336
+ }
301
337
  let embeddedIn;
302
338
  let embeddedPropName;
303
339
  if (prop.embedded !== void 0) {
@@ -484,6 +520,9 @@ function buildEdge(fromEntity, prop) {
484
520
  };
485
521
  }
486
522
  if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner === true) {
523
+ if (prop.type === fromEntity) {
524
+ return null;
525
+ }
487
526
  return {
488
527
  fromEntity,
489
528
  toEntity: prop.type,
@@ -503,75 +542,6 @@ function buildEdge(fromEntity, prop) {
503
542
  }
504
543
  return null;
505
544
  }
506
- function normalizeType(type) {
507
- const t = type.toLowerCase().trim();
508
- if (t === "uuid" || t === "text" || t === "string" || t.startsWith("varchar")) {
509
- return "string";
510
- }
511
- if (t === "timestamptz" || t === "timestamp" || t === "datetime") {
512
- return "datetime";
513
- }
514
- if (t === "integer" || t === "int" || t === "bigint" || t === "smallint") {
515
- return "integer";
516
- }
517
- if (t === "doubletype" || t === "double precision" || t === "double" || t === "float" || t === "decimal") {
518
- return "float";
519
- }
520
- if (t === "boolean" || t === "bool") {
521
- return "boolean";
522
- }
523
- if (t === "jsonb") {
524
- return "json";
525
- }
526
- return type;
527
- }
528
- function renderErDiagram(model, mermaid) {
529
- const lines = [];
530
- if (mermaid?.layout !== void 0 || mermaid?.theme !== void 0) {
531
- lines.push("---", "config:");
532
- if (mermaid.layout !== void 0) {
533
- lines.push(` layout: ${mermaid.layout}`);
534
- }
535
- if (mermaid.theme !== void 0) {
536
- lines.push(` theme: ${mermaid.theme}`);
537
- }
538
- lines.push("---");
539
- }
540
- lines.push("erDiagram");
541
- for (const entity of model.entities) {
542
- lines.push(` ${toMermaidIdentifier(entity.className)} {`);
543
- for (const col of entity.columns) {
544
- lines.push(` ${renderColumnLine(col)}`);
545
- }
546
- lines.push(" }");
547
- }
548
- for (const rel of model.relations) {
549
- lines.push(
550
- ` ${toMermaidIdentifier(rel.fromEntity)} ${rel.fromCardinality}--${rel.toCardinality} ${toMermaidIdentifier(rel.toEntity)} : "${escapeMermaidQuotedText(rel.label)}"`
551
- );
552
- }
553
- return lines.join("\n");
554
- }
555
- function renderColumnLine(col) {
556
- let qualifier = "";
557
- if (col.isPrimary) {
558
- qualifier = " PK";
559
- } else if (col.isUnique) {
560
- qualifier = " UK";
561
- }
562
- let comment;
563
- if (col.formula !== void 0) {
564
- comment = col.formula ? `formula: ${col.formula}` : "formula";
565
- } else if (col.isDiscriminator) {
566
- comment = "discriminator";
567
- } else if (col.embeddedIn !== void 0) {
568
- comment = `[${col.embeddedIn}]`;
569
- } else if (col.isSelfReference) {
570
- comment = "self-ref";
571
- }
572
- const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
573
- return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
574
- }
575
545
 
576
546
  // src/model/build.ts
577
547
  var FROM_ONE_OR_MORE = "}|";
@@ -594,7 +564,10 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
594
564
  const columns = model.columns.filter(
595
565
  (col) => !(col.isForeignKey && col.referencedEntity !== void 0 && hiddenClasses.has(col.referencedEntity))
596
566
  );
597
- 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
+ );
598
571
  const ownPropDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
599
572
  const propDocs = withEmbeddedPropDocs(ownPropDocs, visibleModel.columns, jsDocResult.props);
600
573
  enrichedByClass.set(model.className, { model: visibleModel, jsDoc, propDocs });
@@ -645,6 +618,14 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
645
618
  });
646
619
  return { title, groups, ...description !== void 0 && { description } };
647
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
+ }
648
629
  function withEmbeddedPropDocs(ownPropDocs, columns, allPropDocs) {
649
630
  const merged = new Map(ownPropDocs);
650
631
  for (const col of columns) {
@@ -756,6 +737,142 @@ async function withTsMorphMetadataProvider(options, onWarn) {
756
737
  }
757
738
  }
758
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
+
759
876
  // src/render/markdown.ts
760
877
  function renderMarkdown(docModel, mermaid) {
761
878
  const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
@@ -779,7 +896,7 @@ function renderBulletSection(header, items, renderItem) {
779
896
  }
780
897
  function renderTableOfContents(groups, anchors) {
781
898
  return renderBulletSection("## Contents", groups, (group) => {
782
- const label = escapeMarkdownInline(group.name).replace(/[[\]]/g, "\\$&");
899
+ const label = escapeMarkdownInline(group.name);
783
900
  return `- [${label}](#${anchors.get(group) ?? toMarkdownAnchor(group.name)})`;
784
901
  });
785
902
  }
@@ -864,7 +981,7 @@ function resolveColumnKey(col) {
864
981
  return "PK";
865
982
  }
866
983
  if (col.isForeignKey) {
867
- return fkKey;
984
+ return col.isUnique ? `${fkKey}, UK` : fkKey;
868
985
  }
869
986
  if (col.isUnique) {
870
987
  return "UK";