Skip to content

Releases

v2.3.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 09:31

🚀 Added

  • Excalidraw support, emitting a .excalidraw file you open and edit in Excalidraw directly. Excalidraw has no table shape, so each entity becomes a frame named after the entity with one rectangle per field, and each relation an arrow docked to the FK and referenced column rows, using Excalidraw's own crow's-foot cardinality arrowheads. Like .drawio, the format has no auto-layout, so the emitter lays the entities out on a grid itself. (eea51b7)

💊 Fixed

  • draw.io, Excalidraw, Structurizr, and Pikchr diagrams no longer contain em dashes. Field and entity descriptions, and --names both aliases, were separated with —. draw.io, Excalidraw, and Pikchr now use ·, and Structurizr uses , alias: / , description: to match its other comma-joined column properties. --check will report drift on committed diagrams in these four formats that include descriptions or aliases. Regenerate and recommit to clear it. (26cfca7)

v2.2.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 08:01

🚀 Added

  • erd (BurntSushi) support, rendering each entity as an [Entity] header followed by one line per field (* marks a primary key, + a foreign key, with type/nullability/default carried in the field's {label: "..."}), and each relation as a ?--1 / *--1 cardinality expression. Identifiers outside erd's [A-Za-z_][A-Za-z0-9_]* grammar are backtick-quoted. (0d0984a)
  • draw.io support, emitting a .drawio file you open and edit in draw.io / diagrams.net directly, instead of a text diagram language you render. Each entity becomes a real draw.io table shape (header + one row per field, with PK/FK marker cells) and each relation an edge with the matching ER cardinality markers. Since .drawio is absolute-positioned XML with no auto-layout, the emitter lays the tables out on a grid itself. (72ab38e)

💊 Fixed

  • 1-1 relation labels named the wrong side. Relation.to is always the FK-holding side, so a 1-1 swaps the two sides to put the FK on to — but fieldName kept coming from the FK-holding side instead of following the swap, so the edge label showed the alias belonging to to while labelling the relation from from. Fixed across the Prisma, Mongoose, TypeORM, and MikroORM adapters; TypeORM and MikroORM fall back to the owning side's own property name when no inverse is declared, since that's the only alias that exists then. --check will report drift on any committed diagram with a 1-1 relation — regenerate and recommit to clear it. (8ac1758)
  • A bare UK on a Sequelize column stranded by two overlapping belongsToMany. Two belongsToMany declared through the same junction table fight over the FK attribute they share, and the second pair's Object.assign overwrites the first's unique group — stranding one attribute as the lone member of a group whose generated name still names two columns. The attribute kept a truthy unique, so it rendered as a bare UK reading "globally unique", the opposite of what a junction table means. Such attributes are now detected by Sequelize's own ${tableName}_${foreignKey}_${otherKey}_unique naming and excluded, leaving the composite unique intact. --check will report drift on affected Sequelize diagrams — regenerate and recommit. (d4764a4)

v2.1.0

Choose a tag to compare

@github-actions github-actions released this 06 Aug 05:53

🚀 Added

  • Structurizr support, joining Mermaid/DBML/PlantUML/D2/Nomnoml/QuickDBD/GraphvizDOT as a registered emitter — entities render as elements inside a model { } block with a properties {} block per field, relationships as -> expressions, using # for comments to match the Structurizr playground's own default. (460d9e3, 26f79ca)
  • Pikchr support, rendering entities as boxes with a field list and relationship lines drawn between them. (1e1b6b6)
  • --case <mode> letter-cases rendered identifiers — preserve (default, source casing as-is), snake, screaming_snake, camel, pascal, kebab, title, lower, or upper. Only touches entity/field/enum-type names, never type labels, enum member values, or the --names both alias. (089a106)
  • --inflect <mode> pluralizes or singularizes entity/table identifiers only (never fields, never the --names both alias) — preserve (default), plural, or singular, applied before --case (e.g. --inflect plural --case kebab on PostTag → post-tags). (947ca12)
  • -y, --yes forces non-interactive mode so every omitted flag falls back to its default instead of prompting, without needing --stdout or a CI environment to trigger it. (49b0a77)
  • --check --summary prints a structural (schema-level) diff grouped by entity (e.g. users: +column "last_login_at") instead of the raw line diff, by comparing the freshly extracted model against a gitignored snapshot written alongside the diagram on the last non---check run. Falls back to the raw line diff, with an explanatory note, when no snapshot exists yet or the drift is formatting/ordering-only. (92d315a)
  • "Generated by orm2erd" watermark on every emitter's output, using each format's own comment syntax. Note: this changes the output of every existing emitter by one line — the first --check run after upgrading will report drift on previously-generated files; regenerate and recommit to clear it. (82f5af4)
  • CLI errors now print a (run with --help to see all flags) hint. (5d57863)

💊 Fixed

  • --check without --out now errors instead of silently guessing a path to verify against. --check has no memory of what --out was used on the run that produced the committed file, so there was no safe default to fall back to — it's now a hard requirement, checked non-interactively before any prompting starts. (31e57ac)
  • Mermaid, DBML, PlantUML, and QuickDBD now quote entity/field identifiers that become syntactically unsafe after --case kebab (bare hyphen) or --case title/other space-containing modes, instead of emitting broken output — the exact trigger character(s) and quote style differ per format, verified empirically against each one's own grammar. (a63af11)

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 03 Aug 07:21

⚠️ Breaking

  • Default entity/field names switched from ORM model names to physical table/column names. New --names <table|model|both> flag controls this — table (physical names, e.g. Sequelize's pluralized Users table or Prisma's @@map-ed name) is now the default, since an ERD describes a database and should match what you'd see in psql; model restores the previous default (the ORM's own model/field names), and both renders the physical name with the ORM name as an alias where the output format supports one. A companion --relation-label <both|alias|column> flag (default both) controls whether relation edges show the association alias, the FK column, or both when they disambiguate two relations between the same entity pair. Existing --check/CI pipelines that don't pass --names/--relation-label explicitly will see diagrams regenerate with different content under the new default — pass --names model to keep prior output unchanged. (e290816)
  • --out <name> resolves inside an existing same-named directory instead of next to it. Previously a bare, extension-less --out erd always resolved to a sibling file, erd.mmd, regardless of what else existed on disk. Now, if erd already exists as a directory, the same flag resolves to erd/erd.mmd instead — silently different output for anyone who already had a directory at that path (e.g. a folder collecting several exported diagrams), breaking --check/CI configs and any script reading the old sibling-file location. Only bare names colliding with an existing directory are affected; a trailing slash (--out erd/) was always directory-aware and a name with no on-disk collision is unchanged. (c73a3f7)

🚀 Added

  • MikroORM support, targeting both v6 (@mikro-orm/core@^6, classic @Entity()/@Property() decorators) and v7 (@mikro-orm/core@^7) via the same adapter — entities, fields, relations, and composite keys are read from MikroORM's own getMetadata().getAll(). The one real behavioral difference between the two majors — getAll() returning a plain Dictionary in v6 vs. a real Map in v7 — is branched on via instanceof Map so both are normalized the same way; v6's mandatory connect: false (an unreachable DB otherwise kills discovery entirely) is a harmless no-op pass-through under v7, which dropped auto-connect. See docs/adapters.md for the full detection/entry/parsing details. (c63f7d4, 7c3dc31)
  • --stdout prints the generated diagram to stdout instead of writing a file (requires exactly one --format); status/log lines route to stderr so the diagram itself can be piped cleanly, e.g. orm2erd ... --stdout > erd.mmd. (9d4294f)
  • --copy copies the generated diagram straight to the clipboard instead of writing a file (requires exactly one --format), via clipboardy (pure-JS, cross-platform, including WSL). (8c6dea1)
  • --format all now works non-interactively too (previously only reachable via the interactive multi-select), expanding to every registered emitter. (c6d2953)
  • Detection confidence for ORMs with multiple entry-point candidates is now computed dynamically (1 / candidateCount, rounded) instead of a hardcoded 0.5 for any 2+ candidates — three candidates now correctly reads as 33% instead of the same 50% shown for two. (b405325)

💊 Fixed

  • DBML: one-to-many relations emitted the > ref operator, which DBML tooling reads as the left column allowing "many" — backwards, since the IR's from side is always the one/referenced end. Now emits < instead (rel.from stays on the left; < reads correctly for one-to-many, matching the invariant that rel.to is always the FK-holding side). Optional relations (isFromOptional) also now emit DBML's ? prefix, e.g. users.id ?< posts.editor_id. (7857da1)

v1.9.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 02:09

🚀 Added

  • Optional-relation support. The IR's Relation gained isFromOptional, marking whether the from (referenced/parent) end is optional — i.e. the FK column on the to (child) side is nullable, so a child row can exist without a parent. All five adapters now resolve it: Prisma from field.isRequired, Sequelize/Drizzle from the FK column's own nullability (both quirkily never set allowNull/notNull on primary keys, so that's special-cased to always non-nullable), TypeORM from the owning join column's isNullable, and Mongoose from path.isRequired on whichever side holds the ref. Getting adapters to report this consistently also meant fixing the relation from/to assignment itself in a few spots (Prisma 1-1, TypeORM one-to-one, Mongoose paired/standalone refs) so the FK-holding side always lands on to, matching the invariant every other relation shape already followed. Mermaid renders it directly: the to end is always o (nothing can force a parent to have a child), while the from end downgrades from || to |o when isFromOptional is set. (ac86c73)
  • Physical table/column names in the IR. Entity.tableName and Field.columnName are now populated whenever the physical name differs from the logical one — Prisma's @@map/@map, Sequelize's tableName/field options, TypeORM's entityMetadata.tableName/column.databaseName, and Mongoose's model.collection.name. Drizzle is exempt: its name/getColumnName() already resolve the physical SQL name directly (it has no separate ORM-level model name to diverge from), so tableName/columnName would always just equal name there. Not yet rendered by any emitter — this lays the groundwork for surfacing physical names in generated diagrams. (5015f1c)

💊 Fixed

  • Two relations sharing the same alias but different FK columns (e.g. two FKs from the same entity pair) rendered identical, ambiguous edge labels. A new relationLabel() helper (emitters/label.ts) appends the FK column in parentheses when it differs from the field name — e.g. author (authorId) — used by Mermaid, D2, and PlantUML. (6f21524)

v1.8.0

Choose a tag to compare

@github-actions github-actions released this 29 Jul 02:40

🚀 Added

  • Per-phase progress during generation. The CLI's spinner now updates its label through Resolving entry… → Parsing schema… → Generating diagram(s)… → Writing output… instead of sitting on a single generic Generating... for the whole run — useful feedback on larger schemas where parsing alone can take a few seconds. Non-interactive mode prints each phase as its own log line instead. Output-suppression during extract() is bypassed for the spinner's own writes specifically, and each phase yields briefly before starting so its label has a chance to paint before a long synchronous phase (e.g. importing the target's models) starves the spinner's redraw interval. (1cceee2)

💊 Fixed

  • Sequelize: an FK column declared only via HasMany/HasOne/BelongsToMany (i.e. every association type other than BelongsTo) was missed by isForeignKey, since 1.7.0's fix matched FK-ness against BelongsTo associations specifically. Reading the attribute's own resolved references (set by addForeignKeyConstraints regardless of which side declared the association) is accurate for all of them instead of reconstructing FK-ness from association direction. (a399138)
  • Sequelize/Drizzle/TypeORM: array columns lost their element type. Sequelize's DataTypes.ARRAY wraps its element type rather than exposing it directly, so an array column fell through to unknown with no list marker; Drizzle's .array() columns (e.g. pg-core's PgArray) wrap theirs on .baseColumn the same way; TypeORM's @Column("text", { array: true }) sets ColumnMetadata.isArray, which wasn't read at all. All three now unwrap to the element for canonical type, native type, and enumValues, and set Field.isList so emitters render the [] suffix. Also fixed, in the same pass: Sequelize's DataTypes.JSON rendered as JSONTYPE (its internal class name) instead of JSON (its public type key, now preferred when present), and canonical-type coverage was extended to several previously-unmapped Sequelize types (UUID, MEDIUMINT, DOUBLE PRECISION, TIME, VIRTUAL, RANGE, GEOMETRY, GEOGRAPHY, HSTORE, INET, CIDR, MACADDR). (dc3c817)
  • Sequelize: a composite unique declared via the unique: 'groupName' shorthand on individual attributes was silently dropped, since that grouping only ever surfaces on model.uniqueKeys — never on model.options.indexes, which the adapter read exclusively. Both sources are now read and merged (deduped by sorted field list, in case the same group is redundantly declared through both). Mermaid, D2, GraphViz DOT, and PlantUML now also mark a field UK and annotate its composite-unique group members ("unique with: ...") when it's unique only by virtue of multi-column membership, not its own isUnique flag. (2a73940)

v1.7.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 07:17

🚀 Added

  • Drizzle support. Detection requires drizzle-orm in dependencies and checks for drizzle.config.ts, then .js, then .json at the project root — the same priority order drizzle-kit's own CLI falls back through. Extraction takes the config file itself as the entry point: its dialect field selects which drizzle-orm dialect-core package (pg-core/mysql-core/sqlite-core/singlestore-core) to introspect tables with, and its schema glob(s) point at the actual table files, all imported via tsx's tsImport() and read through drizzle-orm's own is()/getTableConfig() — the target project's own installed drizzle-orm, not orm2erd's. Supports composite primary keys, multi-column unique constraints and indexes (including functional/expression indexes resolved via the dialect's sqlToQuery), the project's casing: "camelCase" | "snake_case" strategy for columns with no explicit name, onDelete/onUpdate relation actions, and raw sql defaults. Cardinality falls back to FK-column uniqueness the same way the Sequelize adapter's undeclared BelongsTo does, since Drizzle has no separate OneToOne/ManyToOne declaration to trust instead. Drizzle has no comment/description option on a column or table, so description is never populated. See the new "Drizzle" section in docs/adapters.md. (b926c03)
  • Programmatic API. orm2erd now exports detectORMs, getAdapter, getEmitter, and the ERDModel/Entity/Field/Relation/Index IR types from its package root (src/index.ts), mirroring the CLI's own detect → extract → emit pipeline for use as a library, not just a CLI. (871a9e7)
  • Grid layout for the interactive format picker. The Output format(s) prompt now lays options out in an auto-sized grid — column count computed from terminal width and label length, capped at 3 — instead of one per line, with arrow keys navigating by row/column. Ragged last rows (e.g. 7 options in 3 columns) are handled explicitly so up/down/left/right always land on a real cell instead of wrapping into the wrong one. (0d8be5a)

💊 Fixed

  • Sequelize: a plain column that happened to share its name with another model's foreign key was incorrectly marked as an FK itself, because the FK-column set was built from every association's foreignKey regardless of association type. Only BelongsTo associations contribute now, since HasMany/HasOne name a column on the target model, not the one declaring the association. (fba2390)
  • Sequelize: a 1-1 relation backed only by a lone BelongsTo (no reciprocal HasOne registered) always rendered 1-1 even when the FK column wasn't actually unique — indistinguishable from an undeclared HasMany's "many" side. It's now trusted as 1-1 only when an explicit HasOne pairing exists or the FK column is itself unique; otherwise it renders 1-n, and the parent/child direction is normalized consistently with the HasMany case above. (fba2390)

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 24 Jul 06:40

🚀 Added

  • --check flag: regenerates the ERD in memory and compares it against what's already on disk instead of writing — up to date exits 0; drifted prints a diff and exits 1; missing exits 1 too. Never touches the filesystem, so it's safe in a pre-commit hook or CI. The diff highlights only the changed words within an edited line (via an LCS-based word diff), not the whole line, alongside plain +/- for added/removed lines. Forces non-interactive mode so it never blocks on a prompt. See the new "Keeping the ERD in sync (CI)" README section for a drop-in GitHub Actions workflow. (bb2249e, b43c3c5)
  • Composite primary keys and multi-column unique constraints. Entity.primaryKey/.uniques carry the ordered member columns of a composite key/multi-column unique across all four adapters (Prisma's @@id/@@unique, Sequelize's primaryKeyAttributes/options.indexes, Mongoose's compound schema.indexes(), TypeORM's primaryColumns/uniques); single-column keys still use the existing per-field isPrimaryKey/isUnique. DBML renders these as a native indexes { (a, b) [pk] }/[unique] block; other emitters mark the member fields individually. (8c8287c)
  • Plain (non-unique) indexes. Entity.indexes carries single- or multi-column lookup indexes (Prisma @@index, Sequelize options.indexes, Mongoose schema.indexes(), TypeORM @Index()), rendered as DBML indexes { ... } entries; other emitters have no equivalent construct and skip them. (02cc05a)
  • Descriptions and comments. Doc comments (Prisma's ///, Sequelize's comment option, TypeORM's @Entity({ comment })/@Column({ comment })) are read onto Entity.description/Field.description and rendered as a DBML Note/inline note:, a Mermaid comment line/trailing annotation, or a PlantUML note. Mongoose has no comment concept on a schema, so its adapter never populates these. (1db3ca1)
  • onDelete/onUpdate relation actions. Referential actions declared on a foreign key (Prisma's @relation(onDelete: ...), Sequelize's FK attribute options — including the defaults Sequelize itself applies when nothing is declared, and TypeORM's @ManyToOne/@OneToOne options) are carried on Relation.onDelete/.onUpdate and rendered as DBML's [delete: cascade, update: restrict]. Mongoose has no FK-constraint concept, so its adapter never populates these. (f466993)
  • GraphViz DOT output (--format graphvizdot), emitting an HTML-label table per entity with PK/FK/UK markers and directed edges with crow's-foot-style arrowheads for 1-1/1-n/n-n cardinality. (bcbe496)
  • Friendlier errors for common Sequelize/Mongoose/TypeORM extraction failures: a missing target-project dependency, a missing reflect-metadata import, an eager database connection attempt at import time, or a CommonJS/ESM mismatch now get an actionable hint appended below the raw error, instead of surfacing only the bare error message. (c68bbbd)

💊 Fixed

  • A Sequelize BelongsToMany whose through junction is itself an emitted entity produced a redundant derived n-n relation on top of the two 1-n edges already implied by the junction's own associations. The derived edge is now suppressed whenever the junction is actually rendered as its own table (an implicit, unnamed join table that isn't emitted as an entity still keeps its n-n edge, since nothing else conveys the relationship for it). (c90e697)
  • DBML Note/index-name values were wrapped in single quotes with embedded single quotes escaped to double quotes, which read oddly for descriptions that were themselves already double-quoted prose. Both are now consistently double-quoted, with embedded double quotes escaped to single quotes instead. (cff5807)

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 21 Jul 09:10

🚀 Added

  • TypeORM support. Detection recognizes legacy 0.2.x ormconfig.* (JSON) alongside the 0.3+ convention of a data-source.ts/.js file exporting a DataSource, falling back to a content scan when neither exists — the same tiered approach as Sequelize/Mongoose detection. Extraction builds metadata via TypeORM's own internal ConnectionMetadataBuilder (the same class TypeORM itself uses inside DataSource#initialize(), before ever opening a real connection), so it stays DB-connection-free. .ts entity/DataSource files are compiled with the target project's own installed typescript + tsconfig.json first, since TypeORM's decorators rely on emitDecoratorMetadata/the legacy experimentalDecorators calling convention, neither of which tsx's esbuild-based transform can emit correctly. Supports decorator entities, EntitySchema (plain-object) entities, and legacy 0.2.x connections built without ever calling .connect(). (6db88ff)
  • QuickDBD output (--format quickdbd), emitting dbdiagram.io's QuickDBD syntax with inline PK/FK/UNIQUE/NULL field constraints — QuickDBD has no separate relationship section, so the FK marker sits directly on whichever field physically holds the key — plus enum/default values as trailing # comments. (69528f2)
  • Colorized CLI output (help text, prompts, success/error messages) via picocolors, plus an icon on each interactive step (detection, entry point, format, output path, type mode, generating, result). Both color and icons auto-disable in environments without color/Unicode support (CI, NO_COLOR, non-UTF8 terminals), using the same detection heuristic @clack/prompts already relies on for its own prompt symbols — falling back to plain ASCII (o/x) for status icons and to nothing for purely decorative ones. (5e19229)

💊 Fixed

  • The Mermaid emitter's %% Entities/%% Relationships section comments weren't indented to match the rest of the diagram body. (bd2d1eb)

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 20 Jul 09:49

🚀 Added

  • D2 output (--format d2), emitting sql_table-shaped nodes with pk/fk/unique field constraints, NOT NULL/DEFAULT ... inline comments, and crow's-foot cardinality via cf-one/cf-many arrowhead shapes on <-> connections (D2 has no inline symbol for it like Mermaid/DBML). Identifiers are always quoted to sidestep D2's reserved top-level keywords (shape, style, layers, …). (81d9fc0)
  • nomnoml output (--format nomnoml), emitting <table>-classifier nodes with PK/FK/unique/NN field tags and 1 -- 1/1 -- */* -- * multiplicity relations. (ec375fd)
  • Every emitter now separates entities and relationships with an // Entities / // Relationships heading (or the format's own comment syntax) and a blank line between entity blocks, instead of one unbroken stream of lines. (c9c645c)

💊 Fixed

  • Target codebases whose entry file logs straight to process.stdout/process.stderr (e.g. pino, morgan, winston's default console transport) instead of going through console.* leaked that output past --verbose's suppression, since only console.* was being patched. Both streams are now patched too. (f523e5c)
  • The CLI could hang after a successful run instead of exiting — importing the target codebase to introspect it (a DB connection, timers, etc.) can leave open handles that hold Node's event loop open forever. The process now exits explicitly once output is written. (9829cb6)
  • Sequelize.literal(...) default values (e.g. nextval('posts_id_seq') for a Postgres sequence default) were resolved to the wrapper's constructor name instead of the actual SQL expression — the fallback for sentinel DataTypes instances (UUIDV4, NOW, …) matched it too, since both are property-less class instances. Literal's wrapped val is now read directly instead. (6f20968)
  • A default value containing a double quote (e.g. a JSON default like {"a":""}) broke the emitted DBML/Mermaid output by prematurely closing the surrounding quoted attribute. Embedded double quotes are now escaped to single quotes before emitting, consistent with how string defaults are already handled elsewhere. (bb452ee)