mikro-orm-markdown 0.1.0-alpha.3 → 0.1.0-alpha.5

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/cli.js CHANGED
@@ -237,6 +237,8 @@ function toMarkdownAnchor(value) {
237
237
  }
238
238
 
239
239
  // src/render/mermaid.ts
240
+ var MERMAID_LAYOUTS = ["dagre", "elk", "elk.stress"];
241
+ var MERMAID_THEMES = ["default", "neutral", "dark", "forest", "base"];
240
242
  var FORMULA_DUMMY_TABLE = {
241
243
  alias: "e0",
242
244
  name: "",
@@ -375,9 +377,31 @@ function resolveFkTypes(prop, metaByClass, fieldNameCount) {
375
377
  return Array.from({ length: fieldNameCount }, (_value, index) => {
376
378
  const referencedColumnName = referencedColumnNames[index];
377
379
  const pkProp = referencedColumnName !== void 0 ? primaryProps.find((candidate) => candidate.fieldNames.includes(referencedColumnName)) : void 0;
378
- return (pkProp ?? primaryProps[index] ?? primaryProps[0])?.type ?? "integer";
380
+ const resolvedProp = pkProp ?? primaryProps[index] ?? primaryProps[0];
381
+ const rawType = resolvedProp?.type ?? "integer";
382
+ const pkIndex = resolvedProp !== void 0 ? primaryProps.indexOf(resolvedProp) : 0;
383
+ return resolveScalarType(rawType, metaByClass, pkIndex);
379
384
  });
380
385
  }
386
+ function resolveScalarType(type, metaByClass, pkIndex = 0, depth = 0) {
387
+ if (depth >= 5) {
388
+ return "integer";
389
+ }
390
+ const refMeta = metaByClass.get(type);
391
+ if (!refMeta) {
392
+ return type;
393
+ }
394
+ const primaryProps = getPrimaryProps(refMeta);
395
+ if (primaryProps.length === 0) {
396
+ return type;
397
+ }
398
+ const targetProp = primaryProps[pkIndex] ?? primaryProps[0];
399
+ const nextType = targetProp?.type ?? type;
400
+ if (nextType === type) {
401
+ return type;
402
+ }
403
+ return resolveScalarType(nextType, metaByClass, pkIndex, depth + 1);
404
+ }
381
405
  function getPrimaryProps(meta) {
382
406
  const primaryKeys = meta.primaryKeys ?? [];
383
407
  const orderedPrimaryProps = primaryKeys.map((key) => meta.properties[String(key)]).filter((prop) => prop !== void 0);
@@ -479,8 +503,41 @@ function buildEdge(fromEntity, prop) {
479
503
  }
480
504
  return null;
481
505
  }
482
- function renderErDiagram(model) {
483
- const lines = ["erDiagram"];
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");
484
541
  for (const entity of model.entities) {
485
542
  lines.push(` ${toMermaidIdentifier(entity.className)} {`);
486
543
  for (const col of entity.columns) {
@@ -513,7 +570,7 @@ function renderColumnLine(col) {
513
570
  comment = "self-ref";
514
571
  }
515
572
  const commentStr = comment !== void 0 ? ` "${escapeMermaidQuotedText(comment)}"` : "";
516
- return `${toMermaidIdentifier(col.type)} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
573
+ return `${toMermaidIdentifier(normalizeType(col.type))} ${toMermaidIdentifier(col.fieldName)}${qualifier}${commentStr}`;
517
574
  }
518
575
 
519
576
  // src/model/build.ts
@@ -560,9 +617,16 @@ function buildDocumentModel(metas, jsDocResult, title, description, onWarn) {
560
617
  const groups = [];
561
618
  for (const groupName of groupNames) {
562
619
  const isDefault = groupName === "default";
563
- const erdEntities = [...enrichedByClass.values()].filter(
564
- ({ jsDoc }) => belongsToGroupForErd(jsDoc, groupName, isDefault)
565
- );
620
+ const erdEntities = [...enrichedByClass.values()].filter(({ jsDoc }) => belongsToGroupForErd(jsDoc, groupName, isDefault)).map((entity) => {
621
+ if (isCrossNamespaceInGroup(entity.jsDoc, groupName, isDefault)) {
622
+ const pkColumns = entity.model.columns.filter((col) => col.isPrimary);
623
+ if (pkColumns.length === 0) {
624
+ return null;
625
+ }
626
+ return { ...entity, model: { ...entity.model, columns: pkColumns } };
627
+ }
628
+ return entity;
629
+ }).filter((entity) => entity !== null);
566
630
  const textEntities = [...enrichedByClass.values()].filter(
567
631
  ({ jsDoc }) => belongsToGroupForText(jsDoc, groupName, isDefault)
568
632
  );
@@ -664,6 +728,12 @@ function belongsToGroupForText(jsDoc, groupName, isDefault) {
664
728
  }
665
729
  return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
666
730
  }
731
+ function isCrossNamespaceInGroup(jsDoc, groupName, isDefault) {
732
+ if (isDefault || !jsDoc) {
733
+ return false;
734
+ }
735
+ return jsDoc.erdNamespaces.includes(groupName) && !jsDoc.namespaces.includes(groupName) && !jsDoc.describeNamespaces.includes(groupName);
736
+ }
667
737
 
668
738
  // src/provider.ts
669
739
  async function withTsMorphMetadataProvider(options, onWarn) {
@@ -686,7 +756,7 @@ async function withTsMorphMetadataProvider(options, onWarn) {
686
756
  }
687
757
 
688
758
  // src/render/markdown.ts
689
- function renderMarkdown(docModel) {
759
+ function renderMarkdown(docModel, mermaid) {
690
760
  const sections = [`# ${escapeMarkdownInline(docModel.title)}`];
691
761
  if (docModel.description) {
692
762
  sections.push(escapeMarkdownParagraph(docModel.description));
@@ -695,7 +765,7 @@ function renderMarkdown(docModel) {
695
765
  sections.push(renderTableOfContents(docModel.groups));
696
766
  }
697
767
  for (const group of docModel.groups) {
698
- sections.push(renderGroupSection(group));
768
+ sections.push(renderGroupSection(group, mermaid));
699
769
  }
700
770
  return sections.join("\n\n");
701
771
  }
@@ -712,14 +782,14 @@ function renderTableOfContents(groups) {
712
782
  return `- [${label}](#${toMarkdownAnchor(group.name)})`;
713
783
  });
714
784
  }
715
- function renderGroupSection(group) {
785
+ function renderGroupSection(group, mermaid) {
716
786
  const parts = [`## ${escapeMarkdownInline(group.name)}`];
717
787
  if (group.erdEntities.length > 0) {
718
788
  const diagramModel = {
719
789
  entities: group.erdEntities.map((e) => e.model),
720
790
  relations: group.erdRelations
721
791
  };
722
- parts.push("```mermaid\n" + renderErDiagram(diagramModel) + "\n```");
792
+ parts.push("```mermaid\n" + renderErDiagram(diagramModel, mermaid) + "\n```");
723
793
  }
724
794
  for (const entity of group.textEntities) {
725
795
  parts.push(renderEntitySection(entity));
@@ -761,7 +831,7 @@ function renderColumnTable(entity) {
761
831
  const docDesc = entity.propDocs.get(col.propName)?.description ?? col.comment ?? "";
762
832
  const enumDesc = col.enumItems !== void 0 ? `One of: ${col.enumItems.join(", ")}` : "";
763
833
  const desc = [docDesc, enumDesc].filter((part) => part !== "").join("\n");
764
- return `| ${escapeMarkdownTableCell(col.fieldName)} | ${escapeMarkdownTableCell(col.type)} | ${escapeMarkdownTableCell(key)} | ${nullable} | ${escapeMarkdownTableCell(desc)} |`;
834
+ return `| ${escapeMarkdownTableCell(col.fieldName)} | ${escapeMarkdownTableCell(normalizeType(col.type))} | ${escapeMarkdownTableCell(key)} | ${nullable} | ${escapeMarkdownTableCell(desc)} |`;
765
835
  });
766
836
  return [header, sep, ...rows].join("\n");
767
837
  }
@@ -875,7 +945,7 @@ async function loadEntityMetadataWithTsMorphFallback(originalOrm, effectiveOrm)
875
945
  }
876
946
  }
877
947
  async function generateMarkdown(options) {
878
- const { orm, title = "Database Schema", description, src, onWarn } = options;
948
+ const { orm, title = "Database Schema", description, src, onWarn, mermaid } = options;
879
949
  const effectiveOrm = await withTsMorphMetadataProvider(orm, onWarn);
880
950
  const { metas, sourcePaths } = await loadEntityMetadataWithTsMorphFallback(orm, effectiveOrm);
881
951
  const jsDocResult = loadJsDoc(resolveJsDocSources(sourcePaths, src, onWarn));
@@ -883,7 +953,7 @@ async function generateMarkdown(options) {
883
953
  assertExplicitJsDocSourceCoverage(metas, jsDocResult, src, onWarn);
884
954
  }
885
955
  const docModel = buildDocumentModel(metas, jsDocResult, title, description, onWarn);
886
- return renderMarkdown(docModel);
956
+ return renderMarkdown(docModel, mermaid);
887
957
  }
888
958
 
889
959
  // src/cli.ts
@@ -998,9 +1068,34 @@ async function writeMarkdownFile(outPath, markdown) {
998
1068
  ${formatFileSystemError(cause)}`, { cause });
999
1069
  }
1000
1070
  }
1071
+ function parseMermaidOptions(opts) {
1072
+ const { mermaidLayout, mermaidTheme } = opts;
1073
+ if (mermaidLayout !== void 0 && !MERMAID_LAYOUTS.includes(mermaidLayout)) {
1074
+ process.stderr.write(
1075
+ `Error: Invalid --mermaid-layout "${mermaidLayout}". Allowed values: ${MERMAID_LAYOUTS.join(", ")}
1076
+ `
1077
+ );
1078
+ process.exit(1);
1079
+ }
1080
+ if (mermaidTheme !== void 0 && !MERMAID_THEMES.includes(mermaidTheme)) {
1081
+ process.stderr.write(
1082
+ `Error: Invalid --mermaid-theme "${mermaidTheme}". Allowed values: ${MERMAID_THEMES.join(", ")}
1083
+ `
1084
+ );
1085
+ process.exit(1);
1086
+ }
1087
+ if (mermaidLayout === void 0 && mermaidTheme === void 0) {
1088
+ return void 0;
1089
+ }
1090
+ return {
1091
+ ...mermaidLayout !== void 0 && { layout: mermaidLayout },
1092
+ ...mermaidTheme !== void 0 && { theme: mermaidTheme }
1093
+ };
1094
+ }
1001
1095
  async function run(opts) {
1002
1096
  const configPath = path2.resolve(opts.config);
1003
1097
  const outPath = path2.resolve(opts.out);
1098
+ const mermaid = parseMermaidOptions(opts);
1004
1099
  let ormOptions;
1005
1100
  try {
1006
1101
  ormOptions = await loadOrmOptions(configPath, opts.tsconfig);
@@ -1019,6 +1114,7 @@ ${err instanceof Error ? err.message : String(err)}
1019
1114
  title: opts.title,
1020
1115
  ...opts.description !== void 0 && { description: opts.description },
1021
1116
  ...opts.src !== void 0 && { src: opts.src },
1117
+ ...mermaid !== void 0 && { mermaid },
1022
1118
  onWarn: (message) => void process.stderr.write(`Warning: ${message}
1023
1119
  `)
1024
1120
  });
@@ -1043,7 +1139,7 @@ var program = new Command().name("mikro-orm-markdown").description("Generate Mer
1043
1139
  ).option(
1044
1140
  "--src <paths...>",
1045
1141
  "Source .ts file globs to read JSDoc from when entities run from compiled .js (comments are stripped at build time)"
1046
- ).action(run);
1142
+ ).option("--mermaid-layout <layout>", `Mermaid layout engine injected as frontmatter (${MERMAID_LAYOUTS.join("|")})`).option("--mermaid-theme <theme>", `Mermaid theme injected as frontmatter (${MERMAID_THEMES.join("|")})`).action(run);
1047
1143
  function isDirectCliExecution() {
1048
1144
  const entryPoint = process.argv[1];
1049
1145
  if (entryPoint === void 0) {