From your models to a diagram
The generators take plain options — a name, some fields, some associations. That
is fine when you are drawing something by hand, but it means the picture of your
schema is a second copy of the schema, and it goes stale the moment a column
changes.The adapters read the real thing instead. Point one at a connection, a model
class, a query result or a table description, and it produces the options a
generator wants.import { prormToER, saveDiagram, DiagramType } from 'ts-prorm-orm';
saveDiagram(DiagramType.ER, prormToER(prorm, { title: 'Schema' }), './schema.svg');
That is the whole thing: every model registered on the connection, drawn, in one
call.Which adapter#
| You have | Use | Produces |
|---|
A connected Prorm | prormToER(prorm, opts?) | ERDiagramOptions |
| Model classes | modelsToER(models, opts?) | ERDiagramOptions |
| One model class | modelToDiagram(Model) | ModelDiagramOptions |
| Rows, or any objects | dataToDiagram(name, data) | ModelDiagramOptions |
| Named sets of rows | dataToER(collections, opts?) | ERDiagramOptions |
describeTable() output | tableDescriptionToDiagram(name, desc) | ModelDiagramOptions |
| Several described tables | tableDescriptionsToER(tables, opts?) | ERDiagramOptions |
From a connection#
prormToER() walks every registered model, reads its columns, keys and
associations, and hands back ER options:const options = prormToER(prorm, {
title: 'Production schema',
columns: 4, // grid width
exclude: ['AuditLog'], // omit noisy tables
});
saveDiagram(DiagramType.ER, options, './docs/schema.svg');
exclude takes model names. Join tables and audit tables are the usual
candidates — they add boxes without adding understanding.Note: the adapter reads what the model declares, so a column added straight
to the database without a matching attribute will not appear. To draw what the
database actually contains, go through describeTable() below.
From one model#
import { modelToDiagram, renderDiagram, DiagramType } from 'ts-prorm-orm';
const svg = renderDiagram(DiagramType.Model, modelToDiagram(User));
Primary keys, unique columns, allowNull, defaults, auto-increment, enum values
and field column names all carry across, as do the model's associations —
hasMany, belongsTo, belongsToMany and their foreign keys, aliases and
join tables. timestamps, paranoid and underscored come from the model's
options, so the implicit createdAt / updatedAt / deletedAt columns are
drawn when they exist.It works on a model that has been defined but not yet added, and on any object
carrying the same statics — useful in tests, and for schemas that never became
models.From data#
Sometimes there is no model: an API response, a CSV you just parsed, a
collection from a store the ORM does not map. dataToDiagram() infers a shape
from the values.const rows = await fetch('/api/orders').then((r) => r.json());
const svg = renderDiagram(DiagramType.Model, dataToDiagram('order', rows));
| Value | Inferred |
|---|
1, 42 | INTEGER |
1.5 | FLOAT |
10n | BIGINT |
'text' | STRING |
true | BOOLEAN |
new Date() | DATE |
{…}, […] | JSON |
Buffer | BLOB |
null alone | UNKNOWN |
Pass several rows when you have them. Columns are unioned across rows, and a
column that is null in the first row can still be typed from a later one — one
row gives you one row's worth of guesses.Naming conventions supply the keys: id is marked PK, and a trailing _id or
Id is marked FK. That is a guess about intent, not a fact about the data, so
correct it if it reads wrong:const diagram = dataToDiagram('order', rows);
delete diagram.fields.externalId.foreignKey; // not actually a reference
Several collections at once:const svg = renderDiagram(DiagramType.ER, dataToER({
users: userRows,
orders: orderRows,
}, { title: 'Export' }));
From the database itself#
To draw what is really there — including tables no model covers —
read the schema and adapt that:const qi = prorm.getQueryInterface();
const tables = {};
for (const name of await qi.showTables()) {
tables[name] = await qi.describeTable(name);
}
saveDiagram(DiagramType.ER, tableDescriptionsToER(tables, { title: 'Live' }), './live.svg');
This is the honest picture: it comes from the catalog, not from your model
definitions, so it shows drift between the two.describeTable() reports type, nullability, default, primary key and
auto-increment. It does not report unique constraints or comments, so those are
left blank; a trailing _id is still marked FK by the same naming convention as
above.Naming a diagram instead of importing it#
DiagramType names all fifteen generators, so the choice can come from a CLI
flag, a config file or a request instead of a hand-written switch:import { DiagramType, parseDiagramType, saveDiagram, DIAGRAM_TYPES } from 'ts-prorm-orm';
const type = parseDiagramType(process.argv[2]); // throws, listing valid names
saveDiagram(type, options, './out.svg');
DIAGRAM_TYPES; // every name, for a --help listing or a dropdown
| Value | Generator |
|---|
model | ModelDiagram |
er | ERDiagram |
chen | ChenDiagram |
relational | RelationalDiagram |
class | ClassDiagram |
migration | MigrationDiagram |
schemadoc | SchemaDocDiagram |
index | IndexDiagram |
dependency | DependencyDiagram |
sequence | SequenceDiagram |
state | StateDiagram |
flow | FlowDiagram |
tree | TreeDiagram |
package | PackageDiagram |
gantt | GanttDiagram |
Three ways in, depending on what you need back:createDiagram(DiagramType.ER, options); // the generator
renderDiagram(DiagramType.ER, options); // the SVG string
saveDiagram(DiagramType.ER, options, './x.svg'); // written to disk, SVG returned
Each is typed against the diagram you named, so passing ERDiagramOptions for
DiagramType.Model is a compile error rather than an empty picture.
isDiagramType() guards a value that came from outside the program;
parseDiagramType() converts one, throwing a message that lists the valid names.Keeping a diagram current#
Because the adapters read the live schema, regenerating is a script, not a
chore — wire it into your build and the picture can never drift:// scripts/schema-diagram.ts
import { Prorm, prormToER, saveDiagram, DiagramType } from 'ts-prorm-orm';
const prorm = new Prorm({ dialect: 'sqlite', storage: './app.db' });
await prorm.connect();
saveDiagram(DiagramType.ER, prormToER(prorm, { title: 'Schema' }), 'docs/schema.svg');
await prorm.close();
Models — where rawAttributes and associations come from Modified at 2026-10-04 00:40:16