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/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/docs/jsdoc.ts","../src/metadata/load.ts","../src/render/mermaid.ts","../src/model/build.ts","../src/render/markdown.ts","../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { Options } from '@mikro-orm/core';\nimport { Command } from 'commander';\nimport { MetadataLoadError, generateMarkdown } from './index.js';\n\ninterface CliOptions {\n config: string;\n out: string;\n title: string;\n description?: string;\n src: string[];\n}\n\nasync function run(opts: CliOptions): Promise<void> {\n const configPath = path.resolve(opts.config);\n const outPath = path.resolve(opts.out);\n\n let ormOptions: Options;\n try {\n const mod = (await import(configPath)) as { default?: unknown };\n ormOptions = (mod.default ?? mod);\n } catch (err) {\n const hint = configPath.endsWith('.ts')\n ? '\\nHint: TypeScript configs require tsx or ts-node:\\n npx tsx ./node_modules/.bin/mikro-orm-markdown ...'\n : '';\n process.stderr.write(\n `Error: Cannot load config: ${configPath}\\n${err instanceof Error ? err.message : String(err)}${hint}\\n`,\n );\n process.exit(1);\n }\n\n let markdown: string;\n try {\n markdown = await generateMarkdown({\n orm: ormOptions,\n title: opts.title,\n src: opts.src,\n ...(opts.description !== undefined && { description: opts.description }),\n });\n } catch (err) {\n const msg = err instanceof MetadataLoadError ? err.message : (err instanceof Error ? err.message : String(err));\n process.stderr.write(`Error: ${msg}\\n`);\n process.exit(1);\n }\n\n await fs.writeFile(outPath, markdown, 'utf-8');\n process.stdout.write(`✓ Written to ${path.relative(process.cwd(), outPath)}\\n`);\n}\n\nconst program = new Command()\n .name('mikro-orm-markdown')\n .description('Generate Mermaid ERD + markdown docs from MikroORM entities')\n .requiredOption('-c, --config <path>', 'MikroORM config file path')\n .option('-o, --out <path>', 'Output markdown file path', './ERD.md')\n .option('-t, --title <string>', 'Document title', 'Database Schema')\n .option('-d, --description <string>', 'Optional description paragraph shown below the title')\n .option(\n '-s, --src <glob>',\n 'Glob pattern for entity source files, repeatable (for JSDoc extraction)',\n (val: string, prev: string[]) => [...prev, val],\n [] as string[],\n )\n .action(run);\n\nprogram.parseAsync(process.argv).catch((err: unknown) => {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import type { JSDoc } from 'ts-morph';\nimport { Project } from 'ts-morph';\n\n/** JSDoc information extracted from an entity class. */\nexport interface EntityJsDocInfo {\n /** Class-level description (text before any @tags). */\n description?: string;\n /** Namespaces from @namespace tags — appears in both ERD and text table. */\n namespaces: string[];\n /** Namespaces from @erd tags — appears in ERD only. */\n erdNamespaces: string[];\n /** Namespaces from @describe tags — appears in text table only. */\n describeNamespaces: string[];\n /** True when @hidden tag is present — entity is excluded from all output. */\n hidden: boolean;\n}\n\n/** JSDoc information extracted from a single entity property. */\nexport interface PropJsDocInfo {\n /** Property description text. */\n description?: string;\n}\n\n/** Keyed by entity class name. */\nexport type EntityJsDocMap = Map<string, EntityJsDocInfo>;\n\n/** Outer key: entity class name. Inner key: property name. */\nexport type PropJsDocMap = Map<string, Map<string, PropJsDocInfo>>;\n\nexport interface JsDocResult {\n entities: EntityJsDocMap;\n props: PropJsDocMap;\n}\n\n/**\n * Parses TypeScript source files matched by the provided globs and extracts\n * JSDoc descriptions and custom tags (@namespace, @erd, @describe, @hidden)\n * from entity classes and their properties.\n *\n * Returns empty maps if no source files are matched or no JSDoc is found.\n * Never throws — errors are silently ignored so missing docs don't block generation.\n */\nexport function loadJsDoc(srcGlobs: string[]): JsDocResult {\n const entities: EntityJsDocMap = new Map();\n const props: PropJsDocMap = new Map();\n\n if (srcGlobs.length === 0) return { entities, props };\n\n const project = new Project({\n skipAddingFilesFromTsConfig: true,\n skipLoadingLibFiles: true,\n compilerOptions: {\n experimentalDecorators: true,\n skipLibCheck: true,\n },\n });\n\n project.addSourceFilesAtPaths(srcGlobs);\n\n for (const sourceFile of project.getSourceFiles()) {\n for (const cls of sourceFile.getClasses()) {\n const className = cls.getName();\n if (!className) continue;\n\n const classDocs = cls.getJsDocs();\n if (classDocs.length > 0) {\n entities.set(className, parseEntityJsDoc(classDocs));\n }\n\n const propMap = new Map<string, PropJsDocInfo>();\n for (const prop of cls.getProperties()) {\n const propDocs = prop.getJsDocs();\n if (propDocs.length === 0) continue;\n const desc = extractDescription(propDocs);\n if (desc !== undefined) {\n propMap.set(prop.getName(), { description: desc });\n }\n }\n if (propMap.size > 0) {\n props.set(className, propMap);\n }\n }\n }\n\n return { entities, props };\n}\n\nfunction parseEntityJsDoc(jsDocs: JSDoc[]): EntityJsDocInfo {\n const namespaces: string[] = [];\n const erdNamespaces: string[] = [];\n const describeNamespaces: string[] = [];\n let hidden = false;\n let description: string | undefined;\n\n for (const doc of jsDocs) {\n const desc = doc.getDescription().trim();\n if (desc && description === undefined) description = desc;\n\n for (const tag of doc.getTags()) {\n const tagName = tag.getTagName();\n const comment = tag.getCommentText()?.trim();\n\n if (tagName === 'namespace' && comment) namespaces.push(comment);\n else if (tagName === 'erd' && comment) erdNamespaces.push(comment);\n else if (tagName === 'describe' && comment) describeNamespaces.push(comment);\n else if (tagName === 'hidden') hidden = true;\n }\n }\n\n return {\n ...(description !== undefined && { description }),\n namespaces,\n erdNamespaces,\n describeNamespaces,\n hidden,\n };\n}\n\nfunction extractDescription(jsDocs: JSDoc[]): string | undefined {\n for (const doc of jsDocs) {\n const desc = doc.getDescription().trim();\n if (desc) return desc;\n }\n return undefined;\n}\n","import { MikroORM } from '@mikro-orm/core';\nimport type { EntityMetadata, Options } from '@mikro-orm/core';\n\n/** Errors thrown during metadata loading */\nexport class MetadataLoadError extends Error {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'MetadataLoadError';\n }\n}\n\n/**\n * Runs MikroORM entity discovery without connecting to the database,\n * and returns all discovered EntityMetadata objects.\n *\n * The caller is responsible for filtering (e.g. excluding abstract,\n * embeddable, or pivot entities) based on rendering needs.\n */\nexport async function loadEntityMetadata(options: Options): Promise<EntityMetadata[]> {\n let orm: MikroORM | undefined;\n\n try {\n orm = await MikroORM.init({\n ...options,\n debug: false,\n });\n } catch (cause) {\n throw new MetadataLoadError(\n 'Failed to initialize MikroORM and run entity discovery. ' +\n 'Make sure your config is valid and all entity files are accessible.',\n cause,\n );\n }\n\n try {\n const all = Object.values(orm.getMetadata().getAll());\n\n if (all.length === 0) {\n throw new MetadataLoadError(\n 'No entities were discovered. ' +\n 'Check that your config specifies at least one entity path or class.',\n );\n }\n\n return all;\n } finally {\n await orm.close(true);\n }\n}\n","import { ReferenceKind } from '@mikro-orm/core';\nimport type { EntityMetadata, EntityProperty, FormulaTable } from '@mikro-orm/core';\nimport type {\n ColumnModel,\n ConstraintModel,\n DiagramModel,\n EntityModel,\n RelationEdge,\n} from '../model/types.js';\n\n// Dummy table descriptor used when resolving formula expressions for documentation.\n// String-based formulas ignore both arguments; function-based formulas use the alias.\nconst FORMULA_DUMMY_TABLE: FormulaTable = {\n alias: 'e0',\n name: '',\n qualifiedName: '',\n toString: () => 'e0',\n};\n\n/**\n * Converts raw MikroORM EntityMetadata array into a DiagramModel.\n *\n * Excluded from entity boxes:\n * - Pivot tables (auto-generated M:N join tables) — represented as edges\n * - @Embeddable classes — their columns appear inline inside the owning entity\n */\nexport function buildDiagramModel(metas: EntityMetadata[]): DiagramModel {\n const metaByClass = new Map(metas.map((m) => [m.className, m]));\n\n const entities: EntityModel[] = metas\n .filter((meta) => !meta.pivotTable && !meta.embeddable)\n .map((meta) => buildEntityModel(meta, metaByClass));\n\n const relations: RelationEdge[] = buildRelationEdges(metas);\n\n return { entities, relations };\n}\n\nfunction buildEntityModel(\n meta: EntityMetadata,\n metaByClass: Map<string, EntityMetadata>,\n): EntityModel {\n // STI root: has discriminatorColumn, no discriminatorValue of its own.\n // Its properties list includes all child-only columns (marked inherited=true) — filter them out.\n const isStiRoot = meta.discriminatorColumn !== undefined && !meta.discriminatorValue;\n const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== undefined;\n\n const columns: ColumnModel[] = [];\n for (const prop of Object.values(meta.properties)) {\n if (isStiRoot && prop.inherited === true) continue;\n const col = buildColumn(prop, metaByClass, meta);\n if (col !== null) columns.push(col);\n }\n\n return {\n className: meta.className,\n tableName: meta.tableName,\n columns,\n isPivot: false,\n isEmbeddable: meta.embeddable === true,\n ...(isStiRoot && { discriminatorColumn: meta.discriminatorColumn as string }),\n ...(isStiChild && { extendsEntity: meta.extends }),\n constraints: buildConstraints(meta),\n };\n}\n\n/** Returns a ColumnModel for renderable properties, or null to skip. */\nfunction buildColumn(\n prop: EntityProperty,\n metaByClass: Map<string, EntityMetadata>,\n owningMeta: EntityMetadata,\n): ColumnModel | null {\n // Skip the EMBEDDED group reference — individual flat columns appear as SCALAR entries\n if (prop.kind === ReferenceKind.EMBEDDED) return null;\n\n if (prop.kind === ReferenceKind.SCALAR) {\n // For @Formula columns, formula is set on a SCALAR-kinded property\n const formulaExpr: string | undefined = prop.formula !== undefined\n ? resolveFormulaExpr(prop.formula as (table: FormulaTable, cols: Record<string, string>) => string)\n : undefined;\n\n // Flat embedded columns carry `embedded: [ownerPropName, embeddedPropName]`\n let embeddedIn: string | undefined;\n if (prop.embedded !== undefined) {\n const parentPropName = prop.embedded[0];\n embeddedIn = owningMeta.properties[parentPropName]?.type;\n }\n\n const isDiscriminator =\n owningMeta.discriminatorColumn !== undefined &&\n prop.name === owningMeta.discriminatorColumn;\n\n return {\n propName: prop.name,\n fieldName: prop.fieldNames?.[0] ?? prop.name,\n type: normalizeType(prop.type),\n isPrimary: prop.primary === true,\n isForeignKey: false,\n isUnique: prop.unique === true,\n isNullable: prop.nullable === true,\n ...(formulaExpr !== undefined && { formula: formulaExpr }),\n ...(embeddedIn !== undefined && { embeddedIn }),\n ...(isDiscriminator && { isDiscriminator: true }),\n };\n }\n\n // FK columns: m:1 always owns the FK; 1:1 only when owner === true\n if (\n prop.kind === ReferenceKind.MANY_TO_ONE ||\n (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner === true)\n ) {\n const fkType = resolveFkType(prop.type, metaByClass);\n return {\n propName: prop.name,\n fieldName: prop.fieldNames?.[0] ?? `${prop.name}_id`,\n type: fkType,\n isPrimary: false,\n isForeignKey: true,\n isUnique: prop.unique === true,\n isNullable: prop.nullable === true,\n };\n }\n\n // ONE_TO_MANY, MANY_TO_MANY (both owner and inverse) → no physical column\n return null;\n}\n\n/**\n * Calls the FormulaCallback with a dummy table and column proxy to extract the SQL expression.\n * String-based formulas (most common) ignore their arguments and return the literal string.\n * Function-based formulas use the alias/column names from the dummy objects.\n * Returns empty string on unexpected errors.\n */\nfunction resolveFormulaExpr(\n cb: (table: FormulaTable, cols: Record<string, string>) => string,\n): string {\n try {\n const cols = new Proxy({}, {\n get: (_target, key: string | symbol) => (typeof key === 'string' ? key : ''),\n });\n return cb(FORMULA_DUMMY_TABLE, cols);\n } catch {\n return '';\n }\n}\n\n/** Looks up the PK type of the referenced entity to use as FK column type. */\nfunction resolveFkType(\n referencedClassName: string,\n metaByClass: Map<string, EntityMetadata>,\n): string {\n const refMeta = metaByClass.get(referencedClassName);\n if (!refMeta) return 'integer';\n const pkProp = Object.values(refMeta.properties).find((p) => p.primary === true);\n return pkProp ? normalizeType(pkProp.type) : 'integer';\n}\n\n/** Collects indexes, unique constraints, and check constraints from entity-level metadata. */\nfunction buildConstraints(meta: EntityMetadata): ConstraintModel[] {\n const result: ConstraintModel[] = [];\n\n for (const idx of meta.indexes ?? []) {\n const props = idx.properties;\n result.push({\n type: 'index',\n properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],\n ...(idx.name !== undefined && { name: idx.name }),\n });\n }\n\n for (const uniq of meta.uniques ?? []) {\n const props = uniq.properties;\n result.push({\n type: 'unique',\n properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],\n ...(uniq.name !== undefined && { name: uniq.name }),\n });\n }\n\n for (const check of meta.checks ?? []) {\n // Skip function-based check expressions (they require column reference objects at runtime)\n if (typeof check.expression !== 'string') continue;\n result.push({\n type: 'check',\n properties: [],\n expression: check.expression,\n ...(check.name !== undefined && { name: check.name }),\n });\n }\n\n return result;\n}\n\n/** Builds edges only from owning sides to avoid duplicate arrows. Includes STI inheritance. */\nfunction buildRelationEdges(metas: EntityMetadata[]): RelationEdge[] {\n const edges: RelationEdge[] = [];\n\n for (const meta of metas) {\n if (meta.pivotTable || meta.embeddable) continue;\n\n for (const prop of Object.values(meta.properties)) {\n const edge = buildEdge(meta.className, prop);\n if (edge !== null) edges.push(edge);\n }\n\n }\n\n return edges;\n}\n\nfunction buildEdge(fromEntity: string, prop: EntityProperty): RelationEdge | null {\n const isNullable = prop.nullable === true;\n\n if (prop.kind === ReferenceKind.MANY_TO_ONE) {\n return {\n fromEntity,\n toEntity: prop.type,\n fromCardinality: '}o',\n toCardinality: isNullable ? 'o|' : '||',\n label: prop.name,\n };\n }\n\n if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner === true) {\n return {\n fromEntity,\n toEntity: prop.type,\n fromCardinality: '||',\n toCardinality: isNullable ? 'o|' : '||',\n label: prop.name,\n };\n }\n\n if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner === true) {\n return {\n fromEntity,\n toEntity: prop.type,\n fromCardinality: '}o',\n toCardinality: 'o{',\n label: prop.name,\n };\n }\n\n return null;\n}\n\n/**\n * Renders a DiagramModel as a Mermaid erDiagram block string.\n * The returned string starts with \"erDiagram\" and is ready to embed in a\n * markdown code fence.\n */\nexport function renderErDiagram(model: DiagramModel): string {\n const lines: string[] = ['erDiagram'];\n\n for (const entity of model.entities) {\n lines.push(` ${entity.className} {`);\n for (const col of entity.columns) {\n lines.push(` ${renderColumnLine(col)}`);\n }\n lines.push(' }');\n }\n\n for (const rel of model.relations) {\n lines.push(\n ` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : \"${rel.label}\"`,\n );\n }\n\n return lines.join('\\n');\n}\n\nfunction renderColumnLine(col: ColumnModel): string {\n // Priority: PK > FK > UK\n let qualifier = '';\n if (col.isPrimary) qualifier = ' PK';\n else if (col.isForeignKey) qualifier = ' FK';\n else if (col.isUnique) qualifier = ' UK';\n\n // Comment priority (v1 differentiators over prisma-markdown):\n // 1. @Formula SQL expression — \"formula: LENGTH(name)\"\n // 2. STI discriminator column — \"discriminator\"\n // 3. Embedded source type — \"[Address]\"\n // 4. DB/TS name mismatch — \"<tsPropName>\"\n let comment: string | undefined;\n if (col.formula !== undefined) {\n comment = col.formula ? `formula: ${col.formula}` : 'formula';\n } else if (col.isDiscriminator) {\n comment = 'discriminator';\n } else if (col.embeddedIn !== undefined) {\n comment = `[${col.embeddedIn}]`;\n } else if (col.fieldName !== col.propName) {\n comment = col.propName;\n }\n\n const commentStr = comment !== undefined ? ` \"${comment}\"` : '';\n return `${col.type} ${col.fieldName}${qualifier}${commentStr}`;\n}\n\n/** Strips characters that are invalid in Mermaid type identifiers. */\nfunction normalizeType(type: string): string {\n return type.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n","import type { EntityMetadata } from '@mikro-orm/core';\nimport type { EntityJsDocInfo, JsDocResult, PropJsDocInfo } from '../docs/jsdoc.js';\nimport type { EntityModel, RelationEdge } from './types.js';\nimport { buildDiagramModel } from '../render/mermaid.js';\n\n/** An entity with its structural model and JSDoc info merged together. */\nexport interface EnrichedEntity {\n model: EntityModel;\n /** Undefined if the entity has no class-level JSDoc. */\n jsDoc: EntityJsDocInfo | undefined;\n /** Per-property description map (empty map if no property JSDoc). */\n propDocs: Map<string, PropJsDocInfo>;\n}\n\n/**\n * A single namespace group, ready to render as one section of the document.\n *\n * - `erdEntities`: shown in the Mermaid ERD block (@namespace + @erd)\n * - `textEntities`: shown in the column-table sections (@namespace + @describe)\n * - `erdRelations`: relation edges where both endpoints are in `erdEntities`\n */\nexport interface NamespaceGroup {\n name: string;\n erdEntities: EnrichedEntity[];\n textEntities: EnrichedEntity[];\n erdRelations: RelationEdge[];\n}\n\n/** Complete document model — input to the markdown renderer. */\nexport interface DocumentModel {\n title: string;\n /** Optional paragraph rendered below the H1 heading. */\n description?: string;\n groups: NamespaceGroup[];\n}\n\n/**\n * Merges MikroORM structural metadata with JSDoc information and organises the\n * result into namespace groups for rendering.\n *\n * Entities with @hidden are excluded.\n * Entities with no namespace tags fall into the \"default\" group.\n * Groups are ordered alphabetically, with \"default\" always last.\n */\nexport function buildDocumentModel(\n metas: EntityMetadata[],\n jsDocResult: JsDocResult,\n title: string,\n description?: string,\n): DocumentModel {\n const { entities: diagramEntities, relations: allRelations } = buildDiagramModel(metas);\n\n // Build enriched entity map, filtering out @hidden entities.\n const enrichedByClass = new Map<string, EnrichedEntity>();\n for (const model of diagramEntities) {\n const jsDoc = jsDocResult.entities.get(model.className);\n if (jsDoc?.hidden) continue;\n const propDocs = jsDocResult.props.get(model.className) ?? new Map<string, PropJsDocInfo>();\n enrichedByClass.set(model.className, { model, jsDoc, propDocs });\n }\n\n // Collect all unique namespace names referenced by any entity.\n const groupNames = new Set<string>();\n let anyUntagged = false;\n for (const { jsDoc } of enrichedByClass.values()) {\n const allNs = [\n ...(jsDoc?.namespaces ?? []),\n ...(jsDoc?.erdNamespaces ?? []),\n ...(jsDoc?.describeNamespaces ?? []),\n ];\n if (allNs.length === 0) {\n anyUntagged = true;\n } else {\n for (const ns of allNs) groupNames.add(ns);\n }\n }\n if (anyUntagged) groupNames.add('default');\n\n const groups: NamespaceGroup[] = [];\n for (const groupName of groupNames) {\n const isDefault = groupName === 'default';\n\n const erdEntities = [...enrichedByClass.values()].filter(({ jsDoc }) =>\n belongsToGroupForErd(jsDoc, groupName, isDefault),\n );\n\n const textEntities = [...enrichedByClass.values()].filter(({ jsDoc }) =>\n belongsToGroupForText(jsDoc, groupName, isDefault),\n );\n\n const erdClassNames = new Set(erdEntities.map((e) => e.model.className));\n const erdRelations = allRelations.filter(\n (r) => erdClassNames.has(r.fromEntity) && erdClassNames.has(r.toEntity),\n );\n\n groups.push({ name: groupName, erdEntities, textEntities, erdRelations });\n }\n\n // Sort alphabetically; \"default\" is always last.\n groups.sort((a, b) => {\n if (a.name === 'default') return 1;\n if (b.name === 'default') return -1;\n return a.name.localeCompare(b.name);\n });\n\n return { title, groups, ...(description !== undefined && { description }) };\n}\n\nfunction hasNoNamespaceTags(jsDoc: EntityJsDocInfo | undefined): boolean {\n if (!jsDoc) return true;\n return (\n jsDoc.namespaces.length === 0 &&\n jsDoc.erdNamespaces.length === 0 &&\n jsDoc.describeNamespaces.length === 0\n );\n}\n\nfunction belongsToGroupForErd(\n jsDoc: EntityJsDocInfo | undefined,\n groupName: string,\n isDefault: boolean,\n): boolean {\n if (isDefault) return hasNoNamespaceTags(jsDoc);\n if (!jsDoc) return false;\n return jsDoc.namespaces.includes(groupName) || jsDoc.erdNamespaces.includes(groupName);\n}\n\nfunction belongsToGroupForText(\n jsDoc: EntityJsDocInfo | undefined,\n groupName: string,\n isDefault: boolean,\n): boolean {\n if (isDefault) return hasNoNamespaceTags(jsDoc);\n if (!jsDoc) return false;\n return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);\n}\n","import type { EnrichedEntity, DocumentModel, NamespaceGroup } from '../model/build.js';\nimport type { ColumnModel, ConstraintModel, DiagramModel } from '../model/types.js';\nimport { renderErDiagram } from './mermaid.js';\n\n/**\n * Renders a DocumentModel as a markdown string.\n * Each namespace group becomes a level-2 section with a Mermaid ERD block\n * followed by per-entity column tables.\n */\nexport function renderMarkdown(docModel: DocumentModel): string {\n const sections: string[] = [`# ${docModel.title}`];\n\n if (docModel.description) {\n sections.push(docModel.description);\n }\n\n for (const group of docModel.groups) {\n sections.push(renderGroupSection(group));\n }\n\n return sections.join('\\n\\n');\n}\n\nfunction renderGroupSection(group: NamespaceGroup): string {\n const parts: string[] = [`## ${group.name}`];\n\n if (group.erdEntities.length > 0) {\n const diagramModel: DiagramModel = {\n entities: group.erdEntities.map((e) => e.model),\n relations: group.erdRelations,\n };\n parts.push('```mermaid\\n' + renderErDiagram(diagramModel) + '\\n```');\n }\n\n for (const entity of group.textEntities) {\n parts.push(renderEntitySection(entity));\n }\n\n return parts.join('\\n\\n');\n}\n\nfunction renderEntitySection(entity: EnrichedEntity): string {\n const parts: string[] = [`### ${entity.model.className}`];\n\n if (entity.jsDoc?.description) {\n parts.push(`> ${entity.jsDoc.description}`);\n }\n\n // STI metadata note\n if (entity.model.discriminatorColumn) {\n parts.push(\n `*STI root — discriminator column: \\`${entity.model.discriminatorColumn}\\`*`,\n );\n } else if (entity.model.extendsEntity) {\n parts.push(`*Extends \\`${entity.model.extendsEntity}\\` (Single Table Inheritance)*`);\n }\n\n if (entity.model.columns.length > 0) {\n parts.push(renderColumnTable(entity));\n }\n\n if (entity.model.constraints.length > 0) {\n parts.push(renderConstraints(entity.model.constraints));\n }\n\n return parts.join('\\n\\n');\n}\n\nfunction renderColumnTable(entity: EnrichedEntity): string {\n const header = '| Column | Type | Key | Nullable | Description |';\n const sep = '|--------|------|-----|----------|-------------|';\n const rows = entity.model.columns.map((col) => {\n const key = resolveColumnKey(col);\n const nullable = col.isNullable && !col.isPrimary ? 'Y' : '';\n const desc = entity.propDocs.get(col.propName)?.description ?? '';\n return `| ${col.fieldName} | ${col.type} | ${key} | ${nullable} | ${desc} |`;\n });\n return [header, sep, ...rows].join('\\n');\n}\n\n/** Returns the \"Key\" cell value for the column table. */\nfunction resolveColumnKey(col: ColumnModel): string {\n if (col.isPrimary) return 'PK';\n if (col.isForeignKey) {\n // Show TS property name in parentheses if it differs from the DB column name\n return col.fieldName !== col.propName ? `FK (${col.propName})` : 'FK';\n }\n if (col.isUnique) return 'UK';\n if (col.formula !== undefined) return `formula: ${col.formula}`;\n if (col.isDiscriminator) return 'discriminator';\n if (col.embeddedIn !== undefined) return `[${col.embeddedIn}]`;\n return '';\n}\n\n\nfunction renderConstraints(constraints: ConstraintModel[]): string {\n const lines = ['**Constraints:**', ''];\n for (const c of constraints) {\n const name = c.name ? ` \\`${c.name}\\`` : '';\n if (c.type === 'index') {\n lines.push(`- Index${name}: (${c.properties.join(', ')})`);\n } else if (c.type === 'unique') {\n lines.push(`- Unique${name}: (${c.properties.join(', ')})`);\n } else if (c.type === 'check') {\n lines.push(`- Check${name}: \\`${c.expression ?? ''}\\``);\n }\n }\n return lines.join('\\n');\n}\n","import type { Options } from '@mikro-orm/core';\nimport { loadJsDoc } from './docs/jsdoc.js';\nimport { loadEntityMetadata } from './metadata/load.js';\nimport { buildDocumentModel } from './model/build.js';\nimport { renderMarkdown } from './render/markdown.js';\n\nexport type { GenerateOptions } from './model/types.js';\nexport { MetadataLoadError } from './metadata/load.js';\n\n/** Options for the programmatic API. */\nexport interface GenerateMarkdownOptions {\n /** MikroORM configuration (driver, entities, dbName, …). */\n orm: Options;\n /** Title shown as the H1 heading in the generated document. */\n title?: string;\n /** Optional description paragraph rendered below the H1 heading. */\n description?: string;\n /**\n * Glob patterns for TypeScript entity source files.\n * Used for JSDoc extraction (@namespace, @hidden, descriptions).\n * Omit to skip JSDoc parsing — all entities go into the \"default\" section.\n */\n src?: string[];\n}\n\n/**\n * Generates a Mermaid ERD + markdown documentation document from MikroORM\n * entity metadata.\n *\n * @example\n * ```ts\n * import { generateMarkdown } from 'mikro-orm-markdown';\n * import ormConfig from './mikro-orm.config.js';\n *\n * const markdown = await generateMarkdown({\n * orm: ormConfig,\n * title: 'My Database',\n * src: ['src/entities/*.ts'],\n * });\n * ```\n */\nexport async function generateMarkdown(options: GenerateMarkdownOptions): Promise<string> {\n const { orm, title = 'Database Schema', description, src = [] } = options;\n\n const metas = await loadEntityMetadata(orm);\n const jsDocResult = loadJsDoc(src);\n const docModel = buildDocumentModel(metas, jsDocResult, title, description);\n return renderMarkdown(docModel);\n}\n"],"mappings":"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","names":["import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/docs/jsdoc.ts","../src/metadata/load.ts","../src/render/mermaid.ts","../src/model/build.ts","../src/render/markdown.ts","../src/index.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport type { Options } from '@mikro-orm/core';\nimport { Command } from 'commander';\nimport { generateMarkdown, MetadataLoadError } from './index.js';\n\ninterface CliOptions {\n config: string;\n out: string;\n title: string;\n description?: string;\n src?: string[];\n}\n\nasync function run(opts: CliOptions): Promise<void> {\n const configPath = path.resolve(opts.config);\n const outPath = path.resolve(opts.out);\n\n let ormOptions: Options;\n try {\n const mod = (await import(configPath)) as { default?: unknown };\n ormOptions = mod.default ?? mod;\n } catch (err) {\n const hint = configPath.endsWith('.ts')\n ? '\\nHint: TypeScript configs require tsx or ts-node:\\n npx tsx ./node_modules/.bin/mikro-orm-markdown ...'\n : '';\n process.stderr.write(\n `Error: Cannot load config: ${configPath}\\n${err instanceof Error ? err.message : String(err)}${hint}\\n`\n );\n process.exit(1);\n }\n\n let markdown: string;\n try {\n markdown = await generateMarkdown({\n orm: ormOptions,\n title: opts.title,\n src: opts.src ?? [],\n ...(opts.description !== undefined && { description: opts.description }),\n });\n } catch (err) {\n const msg = err instanceof MetadataLoadError ? err.message : err instanceof Error ? err.message : String(err);\n process.stderr.write(`Error: ${msg}\\n`);\n process.exit(1);\n }\n\n await fs.writeFile(outPath, markdown, 'utf-8');\n process.stdout.write(`✓ Written to ${path.relative(process.cwd(), outPath)}\\n`);\n}\n\nconst program = new Command()\n .name('mikro-orm-markdown')\n .description('Generate Mermaid ERD + markdown docs from MikroORM entities')\n .requiredOption('-c, --config <path>', 'MikroORM config file path')\n .option('-o, --out <path>', 'Output markdown file path', './ERD.md')\n .option('-t, --title <string>', 'Document title', 'Database Schema')\n .option('-d, --description <string>', 'Optional description paragraph shown below the title')\n .option('-s, --src <glob>', 'JSDoc source glob (repeatable)', (val: string, prev: string[] = []) => [...prev, val])\n .action(run);\n\nprogram.parseAsync(process.argv).catch((err: unknown) => {\n process.stderr.write(`${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n","import type { JSDoc } from 'ts-morph';\nimport { Project } from 'ts-morph';\n\n/** JSDoc information extracted from an entity class. */\nexport interface EntityJsDocInfo {\n /** Class-level description (text before any @tags). */\n description?: string;\n /** Namespaces from @namespace tags — appears in both ERD and text table. */\n namespaces: string[];\n /** Namespaces from @erd tags — appears in ERD only. */\n erdNamespaces: string[];\n /** Namespaces from @describe tags — appears in text table only. */\n describeNamespaces: string[];\n /** True when @hidden tag is present — entity is excluded from all output. */\n hidden: boolean;\n}\n\n/** JSDoc information extracted from a single entity property. */\nexport interface PropJsDocInfo {\n /** Property description text. */\n description?: string;\n}\n\n/** Keyed by entity class name. */\nexport type EntityJsDocMap = Map<string, EntityJsDocInfo>;\n\n/** Outer key: entity class name. Inner key: property name. */\nexport type PropJsDocMap = Map<string, Map<string, PropJsDocInfo>>;\n\nexport interface JsDocResult {\n entities: EntityJsDocMap;\n props: PropJsDocMap;\n}\n\n/**\n * Parses TypeScript source files matched by the provided globs and extracts\n * JSDoc descriptions and custom tags (@namespace, @erd, @describe, @hidden)\n * from entity classes and their properties.\n *\n * Returns empty maps if no source files are matched or no JSDoc is found.\n * Never throws — errors are silently ignored so missing docs don't block generation.\n */\nexport function loadJsDoc(srcGlobs: string[]): JsDocResult {\n const entities: EntityJsDocMap = new Map();\n const props: PropJsDocMap = new Map();\n\n if (srcGlobs.length === 0) {\n return { entities, props };\n }\n\n const project = new Project({\n skipAddingFilesFromTsConfig: true,\n skipLoadingLibFiles: true,\n compilerOptions: {\n experimentalDecorators: true,\n skipLibCheck: true,\n },\n });\n\n project.addSourceFilesAtPaths(srcGlobs);\n\n for (const sourceFile of project.getSourceFiles()) {\n for (const cls of sourceFile.getClasses()) {\n const className = cls.getName();\n if (!className) {\n continue;\n }\n\n const classDocs = cls.getJsDocs();\n if (classDocs.length > 0) {\n entities.set(className, parseEntityJsDoc(classDocs));\n }\n\n const propMap = new Map<string, PropJsDocInfo>();\n for (const prop of cls.getProperties()) {\n const propDocs = prop.getJsDocs();\n if (propDocs.length === 0) {\n continue;\n }\n const desc = extractDescription(propDocs);\n if (desc !== undefined) {\n propMap.set(prop.getName(), { description: desc });\n }\n }\n if (propMap.size > 0) {\n props.set(className, propMap);\n }\n }\n }\n\n return { entities, props };\n}\n\nfunction parseEntityJsDoc(jsDocs: JSDoc[]): EntityJsDocInfo {\n const namespaces: string[] = [];\n const erdNamespaces: string[] = [];\n const describeNamespaces: string[] = [];\n let hidden = false;\n let description: string | undefined;\n\n for (const doc of jsDocs) {\n const desc = doc.getDescription().trim();\n if (desc && description === undefined) {\n description = desc;\n }\n\n for (const tag of doc.getTags()) {\n const tagName = tag.getTagName();\n const comment = tag.getCommentText()?.trim();\n\n if (tagName === 'namespace' && comment) {\n namespaces.push(comment);\n } else if (tagName === 'erd' && comment) {\n erdNamespaces.push(comment);\n } else if (tagName === 'describe' && comment) {\n describeNamespaces.push(comment);\n } else if (tagName === 'hidden') {\n hidden = true;\n }\n }\n }\n\n return {\n ...(description !== undefined && { description }),\n namespaces,\n erdNamespaces,\n describeNamespaces,\n hidden,\n };\n}\n\nfunction extractDescription(jsDocs: JSDoc[]): string | undefined {\n for (const doc of jsDocs) {\n const desc = doc.getDescription().trim();\n if (desc) {\n return desc;\n }\n }\n return undefined;\n}\n","import type { EntityMetadata, Options } from '@mikro-orm/core';\nimport { MikroORM } from '@mikro-orm/core';\n\n/** Errors thrown during metadata loading */\nexport class MetadataLoadError extends Error {\n constructor(\n message: string,\n public override readonly cause?: unknown\n ) {\n super(message);\n this.name = 'MetadataLoadError';\n }\n}\n\n/**\n * Runs MikroORM entity discovery without connecting to the database,\n * and returns all discovered EntityMetadata objects.\n *\n * The caller is responsible for filtering (e.g. excluding abstract,\n * embeddable, or pivot entities) based on rendering needs.\n */\nexport async function loadEntityMetadata(options: Options): Promise<EntityMetadata[]> {\n let orm: MikroORM;\n try {\n orm = await MikroORM.init({\n ...options,\n debug: false,\n connect: false,\n });\n } catch (cause) {\n throw new MetadataLoadError(\n 'Failed to initialize MikroORM and run entity discovery. ' +\n 'Make sure your config is valid and all entity files are accessible.',\n cause\n );\n }\n\n const all = Object.values(orm.getMetadata().getAll());\n\n if (all.length === 0) {\n throw new MetadataLoadError(\n 'No entities were discovered. ' + 'Check that your config specifies at least one entity path or class.'\n );\n }\n\n return all;\n}\n","import type { EntityMetadata, EntityProperty, FormulaTable } from '@mikro-orm/core';\nimport { ReferenceKind } from '@mikro-orm/core';\nimport type { ColumnModel, ConstraintModel, DiagramModel, EntityModel, RelationEdge } from '../model/types.js';\n\n// Dummy table descriptor used when resolving formula expressions for documentation.\n// String-based formulas ignore both arguments; function-based formulas use the alias.\nconst FORMULA_DUMMY_TABLE: FormulaTable = {\n alias: 'e0',\n name: '',\n qualifiedName: '',\n toString: () => 'e0',\n};\n\n/**\n * Converts raw MikroORM EntityMetadata array into a DiagramModel.\n *\n * Excluded from entity boxes:\n * - Pivot tables (auto-generated M:N join tables) — represented as edges\n * - @Embeddable classes — their columns appear inline inside the owning entity\n */\nexport function buildDiagramModel(metas: EntityMetadata[]): DiagramModel {\n const metaByClass = new Map(metas.map((m) => [m.className, m]));\n\n const entities: EntityModel[] = metas\n .filter((meta) => !meta.pivotTable && !meta.embeddable)\n .map((meta) => buildEntityModel(meta, metaByClass));\n\n const relations: RelationEdge[] = buildRelationEdges(metas);\n\n return { entities, relations };\n}\n\nfunction buildEntityModel(meta: EntityMetadata, metaByClass: Map<string, EntityMetadata>): EntityModel {\n // STI root: has discriminatorColumn, no discriminatorValue of its own.\n // Its properties list includes all child-only columns (marked inherited=true) — filter them out.\n const isStiRoot = meta.discriminatorColumn !== undefined && !meta.discriminatorValue;\n const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== undefined;\n\n const columns: ColumnModel[] = [];\n for (const prop of Object.values(meta.properties)) {\n if (isStiRoot && prop.inherited === true) {\n continue;\n }\n const col = buildColumn(prop, metaByClass, meta);\n if (col !== null) {\n columns.push(col);\n }\n }\n\n return {\n className: meta.className,\n tableName: meta.tableName,\n columns,\n isPivot: false,\n isEmbeddable: meta.embeddable === true,\n ...(isStiRoot && { discriminatorColumn: meta.discriminatorColumn as string }),\n ...(isStiChild && { extendsEntity: meta.extends }),\n constraints: buildConstraints(meta),\n };\n}\n\n/** Returns a ColumnModel for renderable properties, or null to skip. */\nfunction buildColumn(\n prop: EntityProperty,\n metaByClass: Map<string, EntityMetadata>,\n owningMeta: EntityMetadata\n): ColumnModel | null {\n // Skip the EMBEDDED group reference — individual flat columns appear as SCALAR entries\n if (prop.kind === ReferenceKind.EMBEDDED) {\n return null;\n }\n\n if (prop.kind === ReferenceKind.SCALAR) {\n // For @Formula columns, formula is set on a SCALAR-kinded property\n const formulaExpr: string | undefined =\n prop.formula !== undefined\n ? resolveFormulaExpr(prop.formula as (table: FormulaTable, cols: Record<string, string>) => string)\n : undefined;\n\n // Flat embedded columns carry `embedded: [ownerPropName, embeddedPropName]`\n let embeddedIn: string | undefined;\n if (prop.embedded !== undefined) {\n const parentPropName = prop.embedded[0];\n embeddedIn = owningMeta.properties[parentPropName]?.type;\n }\n\n const isDiscriminator =\n owningMeta.discriminatorColumn !== undefined && prop.name === owningMeta.discriminatorColumn;\n\n return {\n propName: prop.name,\n fieldName: prop.fieldNames?.[0] ?? prop.name,\n type: normalizeType(prop.type),\n isPrimary: prop.primary === true,\n isForeignKey: false,\n isUnique: prop.unique === true,\n isNullable: prop.nullable === true,\n ...(formulaExpr !== undefined && { formula: formulaExpr }),\n ...(embeddedIn !== undefined && { embeddedIn }),\n ...(isDiscriminator && { isDiscriminator: true }),\n };\n }\n\n // FK columns: m:1 always owns the FK; 1:1 only when owner === true\n if (prop.kind === ReferenceKind.MANY_TO_ONE || (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner === true)) {\n const fkType = resolveFkType(prop.type, metaByClass);\n return {\n propName: prop.name,\n fieldName: prop.fieldNames?.[0] ?? `${prop.name}_id`,\n type: fkType,\n isPrimary: false,\n isForeignKey: true,\n isUnique: prop.unique === true,\n isNullable: prop.nullable === true,\n };\n }\n\n // ONE_TO_MANY, MANY_TO_MANY (both owner and inverse) → no physical column\n return null;\n}\n\n/**\n * Calls the FormulaCallback with a dummy table and column proxy to extract the SQL expression.\n * String-based formulas (most common) ignore their arguments and return the literal string.\n * Function-based formulas use the alias/column names from the dummy objects.\n * Returns empty string on unexpected errors.\n */\nfunction resolveFormulaExpr(cb: (table: FormulaTable, cols: Record<string, string>) => string): string {\n try {\n const cols = new Proxy<Record<string, string>>(\n {},\n {\n get: (_target: Record<string, string>, key: string | symbol): string => (typeof key === 'string' ? key : ''),\n }\n );\n return cb(FORMULA_DUMMY_TABLE, cols);\n } catch {\n return '';\n }\n}\n\n/** Looks up the PK type of the referenced entity to use as FK column type. */\nfunction resolveFkType(referencedClassName: string, metaByClass: Map<string, EntityMetadata>): string {\n const refMeta = metaByClass.get(referencedClassName);\n if (!refMeta) {\n return 'integer';\n }\n const pkProp = Object.values(refMeta.properties).find((p) => p.primary === true);\n return pkProp ? normalizeType(pkProp.type) : 'integer';\n}\n\n/** Collects indexes, unique constraints, and check constraints from entity-level metadata. */\nfunction buildConstraints(meta: EntityMetadata): ConstraintModel[] {\n const result: ConstraintModel[] = [];\n\n for (const idx of meta.indexes ?? []) {\n const props = idx.properties;\n result.push({\n type: 'index',\n properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],\n ...(idx.name !== undefined && { name: idx.name }),\n });\n }\n\n for (const uniq of meta.uniques ?? []) {\n const props = uniq.properties;\n result.push({\n type: 'unique',\n properties: Array.isArray(props) ? props.map(String) : props ? [String(props)] : [],\n ...(uniq.name !== undefined && { name: uniq.name }),\n });\n }\n\n for (const check of meta.checks ?? []) {\n // Skip function-based check expressions (they require column reference objects at runtime)\n if (typeof check.expression !== 'string') {\n continue;\n }\n result.push({\n type: 'check',\n properties: [],\n expression: check.expression,\n ...(check.name !== undefined && { name: check.name }),\n });\n }\n\n return result;\n}\n\n/** Builds edges only from owning sides to avoid duplicate arrows. Includes STI inheritance. */\nfunction buildRelationEdges(metas: EntityMetadata[]): RelationEdge[] {\n const edges: RelationEdge[] = [];\n\n for (const meta of metas) {\n if (meta.pivotTable || meta.embeddable) {\n continue;\n }\n\n for (const prop of Object.values(meta.properties)) {\n const edge = buildEdge(meta.className, prop);\n if (edge !== null) {\n edges.push(edge);\n }\n }\n }\n\n return edges;\n}\n\nfunction buildEdge(fromEntity: string, prop: EntityProperty): RelationEdge | null {\n const isNullable = prop.nullable === true;\n\n if (prop.kind === ReferenceKind.MANY_TO_ONE) {\n return {\n fromEntity,\n toEntity: prop.type,\n fromCardinality: '}o',\n toCardinality: isNullable ? 'o|' : '||',\n label: prop.name,\n };\n }\n\n if (prop.kind === ReferenceKind.ONE_TO_ONE && prop.owner === true) {\n return {\n fromEntity,\n toEntity: prop.type,\n fromCardinality: '||',\n toCardinality: isNullable ? 'o|' : '||',\n label: prop.name,\n };\n }\n\n if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner === true) {\n return {\n fromEntity,\n toEntity: prop.type,\n fromCardinality: '}o',\n toCardinality: 'o{',\n label: prop.name,\n };\n }\n\n return null;\n}\n\n/**\n * Renders a DiagramModel as a Mermaid erDiagram block string.\n * The returned string starts with \"erDiagram\" and is ready to embed in a\n * markdown code fence.\n */\nexport function renderErDiagram(model: DiagramModel): string {\n const lines: string[] = ['erDiagram'];\n\n for (const entity of model.entities) {\n lines.push(` ${entity.className} {`);\n for (const col of entity.columns) {\n lines.push(` ${renderColumnLine(col)}`);\n }\n lines.push(' }');\n }\n\n for (const rel of model.relations) {\n lines.push(` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : \"${rel.label}\"`);\n }\n\n return lines.join('\\n');\n}\n\nfunction renderColumnLine(col: ColumnModel): string {\n // Priority: PK > FK > UK\n let qualifier = '';\n if (col.isPrimary) {\n qualifier = ' PK';\n } else if (col.isForeignKey) {\n qualifier = ' FK';\n } else if (col.isUnique) {\n qualifier = ' UK';\n }\n\n // Comment priority (v1 differentiators over prisma-markdown):\n // 1. @Formula SQL expression — \"formula: LENGTH(name)\"\n // 2. STI discriminator column — \"discriminator\"\n // 3. Embedded source type — \"[Address]\"\n // 4. DB/TS name mismatch — \"<tsPropName>\"\n let comment: string | undefined;\n if (col.formula !== undefined) {\n comment = col.formula ? `formula: ${col.formula}` : 'formula';\n } else if (col.isDiscriminator) {\n comment = 'discriminator';\n } else if (col.embeddedIn !== undefined) {\n comment = `[${col.embeddedIn}]`;\n } else if (col.fieldName !== col.propName) {\n comment = col.propName;\n }\n\n const commentStr = comment !== undefined ? ` \"${comment}\"` : '';\n return `${col.type} ${col.fieldName}${qualifier}${commentStr}`;\n}\n\n/** Strips characters that are invalid in Mermaid type identifiers. */\nfunction normalizeType(type: string): string {\n return type.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n","import type { EntityMetadata } from '@mikro-orm/core';\nimport type { EntityJsDocInfo, JsDocResult, PropJsDocInfo } from '../docs/jsdoc.js';\nimport { buildDiagramModel } from '../render/mermaid.js';\nimport type { EntityModel, RelationEdge } from './types.js';\n\n/** An entity with its structural model and JSDoc info merged together. */\nexport interface EnrichedEntity {\n model: EntityModel;\n /** Undefined if the entity has no class-level JSDoc. */\n jsDoc: EntityJsDocInfo | undefined;\n /** Per-property description map (empty map if no property JSDoc). */\n propDocs: Map<string, PropJsDocInfo>;\n}\n\n/**\n * A single namespace group, ready to render as one section of the document.\n *\n * - `erdEntities`: shown in the Mermaid ERD block (@namespace + @erd)\n * - `textEntities`: shown in the column-table sections (@namespace + @describe)\n * - `erdRelations`: relation edges where both endpoints are in `erdEntities`\n */\nexport interface NamespaceGroup {\n name: string;\n erdEntities: EnrichedEntity[];\n textEntities: EnrichedEntity[];\n erdRelations: RelationEdge[];\n}\n\n/** Complete document model — input to the markdown renderer. */\nexport interface DocumentModel {\n title: string;\n /** Optional paragraph rendered below the H1 heading. */\n description?: string;\n groups: NamespaceGroup[];\n}\n\n/**\n * Merges MikroORM structural metadata with JSDoc information and organises the\n * result into namespace groups for rendering.\n *\n * Entities with @hidden are excluded.\n * Entities with no namespace tags fall into the \"default\" group.\n * Groups are ordered alphabetically, with \"default\" always last.\n */\nexport function buildDocumentModel(\n metas: EntityMetadata[],\n jsDocResult: JsDocResult,\n title: string,\n description?: string\n): DocumentModel {\n const { entities: diagramEntities, relations: allRelations } = buildDiagramModel(metas);\n\n // Build enriched entity map, filtering out @hidden entities.\n const enrichedByClass = new Map<string, EnrichedEntity>();\n for (const model of diagramEntities) {\n const jsDoc = jsDocResult.entities.get(model.className);\n if (jsDoc?.hidden) {\n continue;\n }\n const propDocs = jsDocResult.props.get(model.className) ?? new Map<string, PropJsDocInfo>();\n enrichedByClass.set(model.className, { model, jsDoc, propDocs });\n }\n\n // Collect all unique namespace names referenced by any entity.\n const groupNames = new Set<string>();\n let anyUntagged = false;\n for (const { jsDoc } of enrichedByClass.values()) {\n const allNs = [...(jsDoc?.namespaces ?? []), ...(jsDoc?.erdNamespaces ?? []), ...(jsDoc?.describeNamespaces ?? [])];\n if (allNs.length === 0) {\n anyUntagged = true;\n } else {\n for (const ns of allNs) {\n groupNames.add(ns);\n }\n }\n }\n if (anyUntagged) {\n groupNames.add('default');\n }\n\n const groups: NamespaceGroup[] = [];\n for (const groupName of groupNames) {\n const isDefault = groupName === 'default';\n\n const erdEntities = [...enrichedByClass.values()].filter(({ jsDoc }) =>\n belongsToGroupForErd(jsDoc, groupName, isDefault)\n );\n\n const textEntities = [...enrichedByClass.values()].filter(({ jsDoc }) =>\n belongsToGroupForText(jsDoc, groupName, isDefault)\n );\n\n const erdClassNames = new Set(erdEntities.map((e) => e.model.className));\n const erdRelations = allRelations.filter((r) => erdClassNames.has(r.fromEntity) && erdClassNames.has(r.toEntity));\n\n groups.push({ name: groupName, erdEntities, textEntities, erdRelations });\n }\n\n // Sort alphabetically; \"default\" is always last.\n groups.sort((a, b) => {\n if (a.name === 'default') {\n return 1;\n }\n if (b.name === 'default') {\n return -1;\n }\n return a.name.localeCompare(b.name);\n });\n\n return { title, groups, ...(description !== undefined && { description }) };\n}\n\nfunction hasNoNamespaceTags(jsDoc: EntityJsDocInfo | undefined): boolean {\n if (!jsDoc) {\n return true;\n }\n return jsDoc.namespaces.length === 0 && jsDoc.erdNamespaces.length === 0 && jsDoc.describeNamespaces.length === 0;\n}\n\nfunction belongsToGroupForErd(jsDoc: EntityJsDocInfo | undefined, groupName: string, isDefault: boolean): boolean {\n if (isDefault) {\n return hasNoNamespaceTags(jsDoc);\n }\n if (!jsDoc) {\n return false;\n }\n return jsDoc.namespaces.includes(groupName) || jsDoc.erdNamespaces.includes(groupName);\n}\n\nfunction belongsToGroupForText(jsDoc: EntityJsDocInfo | undefined, groupName: string, isDefault: boolean): boolean {\n if (isDefault) {\n return hasNoNamespaceTags(jsDoc);\n }\n if (!jsDoc) {\n return false;\n }\n return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);\n}\n","import type { DocumentModel, EnrichedEntity, NamespaceGroup } from '../model/build.js';\nimport type { ColumnModel, ConstraintModel, DiagramModel } from '../model/types.js';\nimport { renderErDiagram } from './mermaid.js';\n\n/**\n * Renders a DocumentModel as a markdown string.\n * Each namespace group becomes a level-2 section with a Mermaid ERD block\n * followed by per-entity column tables.\n */\nexport function renderMarkdown(docModel: DocumentModel): string {\n const sections: string[] = [`# ${docModel.title}`];\n\n if (docModel.description) {\n sections.push(docModel.description);\n }\n\n for (const group of docModel.groups) {\n sections.push(renderGroupSection(group));\n }\n\n return sections.join('\\n\\n');\n}\n\nfunction renderGroupSection(group: NamespaceGroup): string {\n const parts: string[] = [`## ${group.name}`];\n\n if (group.erdEntities.length > 0) {\n const diagramModel: DiagramModel = {\n entities: group.erdEntities.map((e) => e.model),\n relations: group.erdRelations,\n };\n parts.push('```mermaid\\n' + renderErDiagram(diagramModel) + '\\n```');\n }\n\n for (const entity of group.textEntities) {\n parts.push(renderEntitySection(entity));\n }\n\n return parts.join('\\n\\n');\n}\n\nfunction renderEntitySection(entity: EnrichedEntity): string {\n const parts: string[] = [`### ${entity.model.className}`];\n\n if (entity.jsDoc?.description) {\n parts.push(`> ${entity.jsDoc.description}`);\n }\n\n // STI metadata note\n if (entity.model.discriminatorColumn) {\n parts.push(`*STI root — discriminator column: \\`${entity.model.discriminatorColumn}\\`*`);\n } else if (entity.model.extendsEntity) {\n parts.push(`*Extends \\`${entity.model.extendsEntity}\\` (Single Table Inheritance)*`);\n }\n\n if (entity.model.columns.length > 0) {\n parts.push(renderColumnTable(entity));\n }\n\n if (entity.model.constraints.length > 0) {\n parts.push(renderConstraints(entity.model.constraints));\n }\n\n return parts.join('\\n\\n');\n}\n\nfunction renderColumnTable(entity: EnrichedEntity): string {\n const header = '| Column | Type | Key | Nullable | Description |';\n const sep = '|--------|------|-----|----------|-------------|';\n const rows = entity.model.columns.map((col) => {\n const key = resolveColumnKey(col);\n const nullable = col.isNullable && !col.isPrimary ? 'Y' : '';\n const desc = entity.propDocs.get(col.propName)?.description ?? '';\n return `| ${col.fieldName} | ${col.type} | ${key} | ${nullable} | ${desc} |`;\n });\n return [header, sep, ...rows].join('\\n');\n}\n\n/** Returns the \"Key\" cell value for the column table. */\nfunction resolveColumnKey(col: ColumnModel): string {\n if (col.isPrimary) {\n return 'PK';\n }\n if (col.isForeignKey) {\n // Show TS property name in parentheses if it differs from the DB column name\n return col.fieldName !== col.propName ? `FK (${col.propName})` : 'FK';\n }\n if (col.isUnique) {\n return 'UK';\n }\n if (col.formula !== undefined) {\n return `formula: ${col.formula}`;\n }\n if (col.isDiscriminator) {\n return 'discriminator';\n }\n if (col.embeddedIn !== undefined) {\n return `[${col.embeddedIn}]`;\n }\n return '';\n}\n\nfunction renderConstraints(constraints: ConstraintModel[]): string {\n const lines = ['**Constraints:**', ''];\n for (const c of constraints) {\n const name = c.name ? ` \\`${c.name}\\`` : '';\n if (c.type === 'index') {\n lines.push(`- Index${name}: (${c.properties.join(', ')})`);\n } else if (c.type === 'unique') {\n lines.push(`- Unique${name}: (${c.properties.join(', ')})`);\n } else if (c.type === 'check') {\n lines.push(`- Check${name}: \\`${c.expression ?? ''}\\``);\n }\n }\n return lines.join('\\n');\n}\n","import type { Options } from '@mikro-orm/core';\nimport { loadJsDoc } from './docs/jsdoc.js';\nimport { loadEntityMetadata } from './metadata/load.js';\nimport { buildDocumentModel } from './model/build.js';\nimport { renderMarkdown } from './render/markdown.js';\n\nexport { MetadataLoadError } from './metadata/load.js';\nexport type { GenerateOptions } from './model/types.js';\n\n/** Options for the programmatic API. */\nexport interface GenerateMarkdownOptions {\n /** MikroORM configuration (driver, entities, dbName, …). */\n orm: Options;\n /** Title shown as the H1 heading in the generated document. */\n title?: string;\n /** Optional description paragraph rendered below the H1 heading. */\n description?: string;\n /**\n * Glob patterns for TypeScript entity source files.\n * Used for JSDoc extraction (@namespace, @hidden, descriptions).\n * Omit to skip JSDoc parsing — all entities go into the \"default\" section.\n */\n src?: string[];\n}\n\n/**\n * Generates a Mermaid ERD + markdown documentation document from MikroORM\n * entity metadata.\n *\n * @example\n * ```ts\n * import { generateMarkdown } from 'mikro-orm-markdown';\n * import ormConfig from './mikro-orm.config.js';\n *\n * const markdown = await generateMarkdown({\n * orm: ormConfig,\n * title: 'My Database',\n * src: ['src/entities/*.ts'],\n * });\n * ```\n */\nexport async function generateMarkdown(options: GenerateMarkdownOptions): Promise<string> {\n const { orm, title = 'Database Schema', description, src = [] } = options;\n\n const metas = await loadEntityMetadata(orm);\n const jsDocResult = loadJsDoc(src);\n const docModel = buildDocumentModel(metas, jsDocResult, title, description);\n return renderMarkdown(docModel);\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,9 @@ import { Project } from "ts-morph";
|
|
|
10
10
|
function loadJsDoc(srcGlobs) {
|
|
11
11
|
const entities = /* @__PURE__ */ new Map();
|
|
12
12
|
const props = /* @__PURE__ */ new Map();
|
|
13
|
-
if (srcGlobs.length === 0)
|
|
13
|
+
if (srcGlobs.length === 0) {
|
|
14
|
+
return { entities, props };
|
|
15
|
+
}
|
|
14
16
|
const project = new Project({
|
|
15
17
|
skipAddingFilesFromTsConfig: true,
|
|
16
18
|
skipLoadingLibFiles: true,
|
|
@@ -23,7 +25,9 @@ function loadJsDoc(srcGlobs) {
|
|
|
23
25
|
for (const sourceFile of project.getSourceFiles()) {
|
|
24
26
|
for (const cls of sourceFile.getClasses()) {
|
|
25
27
|
const className = cls.getName();
|
|
26
|
-
if (!className)
|
|
28
|
+
if (!className) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
27
31
|
const classDocs = cls.getJsDocs();
|
|
28
32
|
if (classDocs.length > 0) {
|
|
29
33
|
entities.set(className, parseEntityJsDoc(classDocs));
|
|
@@ -31,7 +35,9 @@ function loadJsDoc(srcGlobs) {
|
|
|
31
35
|
const propMap = /* @__PURE__ */ new Map();
|
|
32
36
|
for (const prop of cls.getProperties()) {
|
|
33
37
|
const propDocs = prop.getJsDocs();
|
|
34
|
-
if (propDocs.length === 0)
|
|
38
|
+
if (propDocs.length === 0) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
35
41
|
const desc = extractDescription(propDocs);
|
|
36
42
|
if (desc !== void 0) {
|
|
37
43
|
propMap.set(prop.getName(), { description: desc });
|
|
@@ -52,14 +58,21 @@ function parseEntityJsDoc(jsDocs) {
|
|
|
52
58
|
let description;
|
|
53
59
|
for (const doc of jsDocs) {
|
|
54
60
|
const desc = doc.getDescription().trim();
|
|
55
|
-
if (desc && description === void 0)
|
|
61
|
+
if (desc && description === void 0) {
|
|
62
|
+
description = desc;
|
|
63
|
+
}
|
|
56
64
|
for (const tag of doc.getTags()) {
|
|
57
65
|
const tagName = tag.getTagName();
|
|
58
66
|
const comment = tag.getCommentText()?.trim();
|
|
59
|
-
if (tagName === "namespace" && comment)
|
|
60
|
-
|
|
61
|
-
else if (tagName === "
|
|
62
|
-
|
|
67
|
+
if (tagName === "namespace" && comment) {
|
|
68
|
+
namespaces.push(comment);
|
|
69
|
+
} else if (tagName === "erd" && comment) {
|
|
70
|
+
erdNamespaces.push(comment);
|
|
71
|
+
} else if (tagName === "describe" && comment) {
|
|
72
|
+
describeNamespaces.push(comment);
|
|
73
|
+
} else if (tagName === "hidden") {
|
|
74
|
+
hidden = true;
|
|
75
|
+
}
|
|
63
76
|
}
|
|
64
77
|
}
|
|
65
78
|
return {
|
|
@@ -73,7 +86,9 @@ function parseEntityJsDoc(jsDocs) {
|
|
|
73
86
|
function extractDescription(jsDocs) {
|
|
74
87
|
for (const doc of jsDocs) {
|
|
75
88
|
const desc = doc.getDescription().trim();
|
|
76
|
-
if (desc)
|
|
89
|
+
if (desc) {
|
|
90
|
+
return desc;
|
|
91
|
+
}
|
|
77
92
|
}
|
|
78
93
|
return void 0;
|
|
79
94
|
}
|
|
@@ -92,7 +107,8 @@ async function loadEntityMetadata(options) {
|
|
|
92
107
|
try {
|
|
93
108
|
orm = await MikroORM.init({
|
|
94
109
|
...options,
|
|
95
|
-
debug: false
|
|
110
|
+
debug: false,
|
|
111
|
+
connect: false
|
|
96
112
|
});
|
|
97
113
|
} catch (cause) {
|
|
98
114
|
throw new MetadataLoadError(
|
|
@@ -100,17 +116,13 @@ async function loadEntityMetadata(options) {
|
|
|
100
116
|
cause
|
|
101
117
|
);
|
|
102
118
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
return all;
|
|
111
|
-
} finally {
|
|
112
|
-
await orm.close(true);
|
|
119
|
+
const all = Object.values(orm.getMetadata().getAll());
|
|
120
|
+
if (all.length === 0) {
|
|
121
|
+
throw new MetadataLoadError(
|
|
122
|
+
"No entities were discovered. Check that your config specifies at least one entity path or class."
|
|
123
|
+
);
|
|
113
124
|
}
|
|
125
|
+
return all;
|
|
114
126
|
}
|
|
115
127
|
|
|
116
128
|
// src/render/mermaid.ts
|
|
@@ -132,9 +144,13 @@ function buildEntityModel(meta, metaByClass) {
|
|
|
132
144
|
const isStiChild = Boolean(meta.extends) && meta.discriminatorValue !== void 0;
|
|
133
145
|
const columns = [];
|
|
134
146
|
for (const prop of Object.values(meta.properties)) {
|
|
135
|
-
if (isStiRoot && prop.inherited === true)
|
|
147
|
+
if (isStiRoot && prop.inherited === true) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
136
150
|
const col = buildColumn(prop, metaByClass, meta);
|
|
137
|
-
if (col !== null)
|
|
151
|
+
if (col !== null) {
|
|
152
|
+
columns.push(col);
|
|
153
|
+
}
|
|
138
154
|
}
|
|
139
155
|
return {
|
|
140
156
|
className: meta.className,
|
|
@@ -148,7 +164,9 @@ function buildEntityModel(meta, metaByClass) {
|
|
|
148
164
|
};
|
|
149
165
|
}
|
|
150
166
|
function buildColumn(prop, metaByClass, owningMeta) {
|
|
151
|
-
if (prop.kind === ReferenceKind.EMBEDDED)
|
|
167
|
+
if (prop.kind === ReferenceKind.EMBEDDED) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
152
170
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
153
171
|
const formulaExpr = prop.formula !== void 0 ? resolveFormulaExpr(prop.formula) : void 0;
|
|
154
172
|
let embeddedIn;
|
|
@@ -186,9 +204,12 @@ function buildColumn(prop, metaByClass, owningMeta) {
|
|
|
186
204
|
}
|
|
187
205
|
function resolveFormulaExpr(cb) {
|
|
188
206
|
try {
|
|
189
|
-
const cols = new Proxy(
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
const cols = new Proxy(
|
|
208
|
+
{},
|
|
209
|
+
{
|
|
210
|
+
get: (_target, key) => typeof key === "string" ? key : ""
|
|
211
|
+
}
|
|
212
|
+
);
|
|
192
213
|
return cb(FORMULA_DUMMY_TABLE, cols);
|
|
193
214
|
} catch {
|
|
194
215
|
return "";
|
|
@@ -196,7 +217,9 @@ function resolveFormulaExpr(cb) {
|
|
|
196
217
|
}
|
|
197
218
|
function resolveFkType(referencedClassName, metaByClass) {
|
|
198
219
|
const refMeta = metaByClass.get(referencedClassName);
|
|
199
|
-
if (!refMeta)
|
|
220
|
+
if (!refMeta) {
|
|
221
|
+
return "integer";
|
|
222
|
+
}
|
|
200
223
|
const pkProp = Object.values(refMeta.properties).find((p) => p.primary === true);
|
|
201
224
|
return pkProp ? normalizeType(pkProp.type) : "integer";
|
|
202
225
|
}
|
|
@@ -219,7 +242,9 @@ function buildConstraints(meta) {
|
|
|
219
242
|
});
|
|
220
243
|
}
|
|
221
244
|
for (const check of meta.checks ?? []) {
|
|
222
|
-
if (typeof check.expression !== "string")
|
|
245
|
+
if (typeof check.expression !== "string") {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
223
248
|
result.push({
|
|
224
249
|
type: "check",
|
|
225
250
|
properties: [],
|
|
@@ -232,10 +257,14 @@ function buildConstraints(meta) {
|
|
|
232
257
|
function buildRelationEdges(metas) {
|
|
233
258
|
const edges = [];
|
|
234
259
|
for (const meta of metas) {
|
|
235
|
-
if (meta.pivotTable || meta.embeddable)
|
|
260
|
+
if (meta.pivotTable || meta.embeddable) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
236
263
|
for (const prop of Object.values(meta.properties)) {
|
|
237
264
|
const edge = buildEdge(meta.className, prop);
|
|
238
|
-
if (edge !== null)
|
|
265
|
+
if (edge !== null) {
|
|
266
|
+
edges.push(edge);
|
|
267
|
+
}
|
|
239
268
|
}
|
|
240
269
|
}
|
|
241
270
|
return edges;
|
|
@@ -281,17 +310,19 @@ function renderErDiagram(model) {
|
|
|
281
310
|
lines.push(" }");
|
|
282
311
|
}
|
|
283
312
|
for (const rel of model.relations) {
|
|
284
|
-
lines.push(
|
|
285
|
-
` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : "${rel.label}"`
|
|
286
|
-
);
|
|
313
|
+
lines.push(` ${rel.fromEntity} ${rel.fromCardinality}--${rel.toCardinality} ${rel.toEntity} : "${rel.label}"`);
|
|
287
314
|
}
|
|
288
315
|
return lines.join("\n");
|
|
289
316
|
}
|
|
290
317
|
function renderColumnLine(col) {
|
|
291
318
|
let qualifier = "";
|
|
292
|
-
if (col.isPrimary)
|
|
293
|
-
|
|
294
|
-
else if (col.
|
|
319
|
+
if (col.isPrimary) {
|
|
320
|
+
qualifier = " PK";
|
|
321
|
+
} else if (col.isForeignKey) {
|
|
322
|
+
qualifier = " FK";
|
|
323
|
+
} else if (col.isUnique) {
|
|
324
|
+
qualifier = " UK";
|
|
325
|
+
}
|
|
295
326
|
let comment;
|
|
296
327
|
if (col.formula !== void 0) {
|
|
297
328
|
comment = col.formula ? `formula: ${col.formula}` : "formula";
|
|
@@ -315,25 +346,27 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
315
346
|
const enrichedByClass = /* @__PURE__ */ new Map();
|
|
316
347
|
for (const model of diagramEntities) {
|
|
317
348
|
const jsDoc = jsDocResult.entities.get(model.className);
|
|
318
|
-
if (jsDoc?.hidden)
|
|
349
|
+
if (jsDoc?.hidden) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
319
352
|
const propDocs = jsDocResult.props.get(model.className) ?? /* @__PURE__ */ new Map();
|
|
320
353
|
enrichedByClass.set(model.className, { model, jsDoc, propDocs });
|
|
321
354
|
}
|
|
322
355
|
const groupNames = /* @__PURE__ */ new Set();
|
|
323
356
|
let anyUntagged = false;
|
|
324
357
|
for (const { jsDoc } of enrichedByClass.values()) {
|
|
325
|
-
const allNs = [
|
|
326
|
-
...jsDoc?.namespaces ?? [],
|
|
327
|
-
...jsDoc?.erdNamespaces ?? [],
|
|
328
|
-
...jsDoc?.describeNamespaces ?? []
|
|
329
|
-
];
|
|
358
|
+
const allNs = [...jsDoc?.namespaces ?? [], ...jsDoc?.erdNamespaces ?? [], ...jsDoc?.describeNamespaces ?? []];
|
|
330
359
|
if (allNs.length === 0) {
|
|
331
360
|
anyUntagged = true;
|
|
332
361
|
} else {
|
|
333
|
-
for (const ns of allNs)
|
|
362
|
+
for (const ns of allNs) {
|
|
363
|
+
groupNames.add(ns);
|
|
364
|
+
}
|
|
334
365
|
}
|
|
335
366
|
}
|
|
336
|
-
if (anyUntagged)
|
|
367
|
+
if (anyUntagged) {
|
|
368
|
+
groupNames.add("default");
|
|
369
|
+
}
|
|
337
370
|
const groups = [];
|
|
338
371
|
for (const groupName of groupNames) {
|
|
339
372
|
const isDefault = groupName === "default";
|
|
@@ -344,30 +377,42 @@ function buildDocumentModel(metas, jsDocResult, title, description) {
|
|
|
344
377
|
({ jsDoc }) => belongsToGroupForText(jsDoc, groupName, isDefault)
|
|
345
378
|
);
|
|
346
379
|
const erdClassNames = new Set(erdEntities.map((e) => e.model.className));
|
|
347
|
-
const erdRelations = allRelations.filter(
|
|
348
|
-
(r) => erdClassNames.has(r.fromEntity) && erdClassNames.has(r.toEntity)
|
|
349
|
-
);
|
|
380
|
+
const erdRelations = allRelations.filter((r) => erdClassNames.has(r.fromEntity) && erdClassNames.has(r.toEntity));
|
|
350
381
|
groups.push({ name: groupName, erdEntities, textEntities, erdRelations });
|
|
351
382
|
}
|
|
352
383
|
groups.sort((a, b) => {
|
|
353
|
-
if (a.name === "default")
|
|
354
|
-
|
|
384
|
+
if (a.name === "default") {
|
|
385
|
+
return 1;
|
|
386
|
+
}
|
|
387
|
+
if (b.name === "default") {
|
|
388
|
+
return -1;
|
|
389
|
+
}
|
|
355
390
|
return a.name.localeCompare(b.name);
|
|
356
391
|
});
|
|
357
392
|
return { title, groups, ...description !== void 0 && { description } };
|
|
358
393
|
}
|
|
359
394
|
function hasNoNamespaceTags(jsDoc) {
|
|
360
|
-
if (!jsDoc)
|
|
395
|
+
if (!jsDoc) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
361
398
|
return jsDoc.namespaces.length === 0 && jsDoc.erdNamespaces.length === 0 && jsDoc.describeNamespaces.length === 0;
|
|
362
399
|
}
|
|
363
400
|
function belongsToGroupForErd(jsDoc, groupName, isDefault) {
|
|
364
|
-
if (isDefault)
|
|
365
|
-
|
|
401
|
+
if (isDefault) {
|
|
402
|
+
return hasNoNamespaceTags(jsDoc);
|
|
403
|
+
}
|
|
404
|
+
if (!jsDoc) {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
366
407
|
return jsDoc.namespaces.includes(groupName) || jsDoc.erdNamespaces.includes(groupName);
|
|
367
408
|
}
|
|
368
409
|
function belongsToGroupForText(jsDoc, groupName, isDefault) {
|
|
369
|
-
if (isDefault)
|
|
370
|
-
|
|
410
|
+
if (isDefault) {
|
|
411
|
+
return hasNoNamespaceTags(jsDoc);
|
|
412
|
+
}
|
|
413
|
+
if (!jsDoc) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
371
416
|
return jsDoc.namespaces.includes(groupName) || jsDoc.describeNamespaces.includes(groupName);
|
|
372
417
|
}
|
|
373
418
|
|
|
@@ -402,9 +447,7 @@ function renderEntitySection(entity) {
|
|
|
402
447
|
parts.push(`> ${entity.jsDoc.description}`);
|
|
403
448
|
}
|
|
404
449
|
if (entity.model.discriminatorColumn) {
|
|
405
|
-
parts.push(
|
|
406
|
-
`*STI root \u2014 discriminator column: \`${entity.model.discriminatorColumn}\`*`
|
|
407
|
-
);
|
|
450
|
+
parts.push(`*STI root \u2014 discriminator column: \`${entity.model.discriminatorColumn}\`*`);
|
|
408
451
|
} else if (entity.model.extendsEntity) {
|
|
409
452
|
parts.push(`*Extends \`${entity.model.extendsEntity}\` (Single Table Inheritance)*`);
|
|
410
453
|
}
|
|
@@ -428,14 +471,24 @@ function renderColumnTable(entity) {
|
|
|
428
471
|
return [header, sep, ...rows].join("\n");
|
|
429
472
|
}
|
|
430
473
|
function resolveColumnKey(col) {
|
|
431
|
-
if (col.isPrimary)
|
|
474
|
+
if (col.isPrimary) {
|
|
475
|
+
return "PK";
|
|
476
|
+
}
|
|
432
477
|
if (col.isForeignKey) {
|
|
433
478
|
return col.fieldName !== col.propName ? `FK (${col.propName})` : "FK";
|
|
434
479
|
}
|
|
435
|
-
if (col.isUnique)
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (col.
|
|
480
|
+
if (col.isUnique) {
|
|
481
|
+
return "UK";
|
|
482
|
+
}
|
|
483
|
+
if (col.formula !== void 0) {
|
|
484
|
+
return `formula: ${col.formula}`;
|
|
485
|
+
}
|
|
486
|
+
if (col.isDiscriminator) {
|
|
487
|
+
return "discriminator";
|
|
488
|
+
}
|
|
489
|
+
if (col.embeddedIn !== void 0) {
|
|
490
|
+
return `[${col.embeddedIn}]`;
|
|
491
|
+
}
|
|
439
492
|
return "";
|
|
440
493
|
}
|
|
441
494
|
function renderConstraints(constraints) {
|
|
@@ -484,7 +537,7 @@ ${err instanceof Error ? err.message : String(err)}${hint}
|
|
|
484
537
|
markdown = await generateMarkdown({
|
|
485
538
|
orm: ormOptions,
|
|
486
539
|
title: opts.title,
|
|
487
|
-
src: opts.src,
|
|
540
|
+
src: opts.src ?? [],
|
|
488
541
|
...opts.description !== void 0 && { description: opts.description }
|
|
489
542
|
});
|
|
490
543
|
} catch (err) {
|
|
@@ -497,12 +550,7 @@ ${err instanceof Error ? err.message : String(err)}${hint}
|
|
|
497
550
|
process.stdout.write(`\u2713 Written to ${path.relative(process.cwd(), outPath)}
|
|
498
551
|
`);
|
|
499
552
|
}
|
|
500
|
-
var program = new 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(
|
|
501
|
-
"-s, --src <glob>",
|
|
502
|
-
"Glob pattern for entity source files, repeatable (for JSDoc extraction)",
|
|
503
|
-
(val, prev) => [...prev, val],
|
|
504
|
-
[]
|
|
505
|
-
).action(run);
|
|
553
|
+
var program = new 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);
|
|
506
554
|
program.parseAsync(process.argv).catch((err) => {
|
|
507
555
|
process.stderr.write(`${err instanceof Error ? err.message : String(err)}
|
|
508
556
|
`);
|