@jhzlo/resin
v0.2.8
Published
A small language for entity-relationship diagrams, drawn as SVG.
Maintainers
Readme
resin
A small language for entity-relationship diagrams.
resin describes a database schema the way you read DDL: columns, keys, nullability, physical and logical references, indexes, encrypted columns, audit tables. It checks what you wrote and draws it as an SVG where every foreign key row is wired to the primary key it points at.
Try it in the playground | Docs | Language reference | Contributing
Paste DDL from DataGrip or another database tool. Focus on connected entities and trace key
references. Save named documents locally, review import notes, add logical relations, compare DDL
versions, and export a diagram or an editable .erd file.
Watch the 16-second demo | Download MP4 (7.1 MB)
service accounts "Accounts service" {
external table users "People who sign in" {
id bigint pk
}
}
table orders "Customer orders" {
id bigint pk
user_id bigint ~> users "Customer" index as idx_user_id
order_no varchar(32) uk as uk_order_no "Order number"
buyer_name varchar? enc "Buyer name"
status varchar enum(PENDING, PAID, CANCELED) index as idx_status
created_at datetime
updated_at datetime
} audit envers(user_id, status)
table order_items "Order lines" {
id bigint pk
order_id bigint -> orders.id
product_id bigint
quantity int
unique(order_id, product_id) as uk_order_product
}Why
A schema carries facts that its diagram usually loses: which columns may be empty, whether a
reference is a real FOREIGN KEY or one only the application keeps, which column points where,
which columns are indexed, unique or encrypted. They end up in notes and comment strings, written by
convention and checked by nobody.
resin makes them syntax. Because the facts are structured, they can be checked (a reference to a missing table is an error, not a typo in a note), and they can be drawn precisely.
What it does
varchar? is nullable. -> is a physical foreign key and ~> a logical one. uk, index,
enum(...), enc and composite unique(...) are modifiers, not comments.
References are checked. Missing tables and columns, nullable primary keys, duplicate names and type
mismatches come back with a line, a column and a hint on how to fix them. external table declares
a table owned by another service, so references to it are checked too, and the diagram shows where
the boundary is. audit envers(...) generates the Hibernate Envers *_aud and revinfo tables.
resin draws the SVG itself, with each connector running from a foreign key row to the primary key row it points at. Its default look is plain ink that reads on light and dark pages; the glass looks set the tables on frosted glass over a background of their own, in three themes (aurora, silk and caustic), each dark or light. The same input always gives byte-identical output, and the renderer has no runtime dependencies: it takes an ELK instance that you pass in.
For a schema split across services, npx resin schema.erd --services draws each service as one card
that lists its tables, with references inside a service counted and the ones between services
running from table to table, so the dependencies between services are the picture.
Lint rules go further than compiling: they point at a reference column without an index, a table
without a primary key or one nothing joins, a column name typed two ways, and an index another one
covers. npx resin schema.erd --lint fails a CI step on any of them.
resin has one layout, with columns lined up within each table. npx resin fmt schema.erd
rewrites a file in it, keeping every comment, and --check fails a CI step on a file that is not
formatted. The playground formats with Shift + Alt + F.
Schema changes can be reviewed as pictures. npx resin diff old.erd new.erd draws the newer
version with added, removed and changed tables and columns marked, and as a GitHub Action
(uses: JHZLO/resin@main) resin keeps a comment with that drawing on every pull request that
changes a .erd file.
The other way works too: npx resin schema.erd --to-sql postgres writes the DDL for MySQL,
PostgreSQL, SQLite, SQL Server or Oracle, a schema per service, or one service's own database with
--service. Importing that DDL gives the same document back.
An existing schema does not have to be written by hand. Paste a CREATE TABLE script into the
playground, or run npx resin schema.sql --from-sql, and it comes back as resin: keys, indexes,
foreign keys, enum values and comments carry over, from MySQL, PostgreSQL, SQLite, SQL Server or
Oracle DDL, dumps included. What resin cannot write yet stays as a comment in its table.
In the playground the glass is live: the background drifts, the stars twinkle and the glass catches the light under your pointer. Click a table name to see its columns, indexes and relations as tables, or a column to see its details. Click a service's name to see what it owns, what it depends on and what uses it. Connectors can be angular or curved.
Getting started
Install resin from npm in the project that keeps your schemas, with elkjs for drawing. It needs Node 22.18 or newer.
npm install --save-dev @jhzlo/resin elkjsDraw a file as SVG:
npx resin schema.erd > schema.svg
npx resin schema.erd --look aurora-dark > schema.aurora-dark.svg
npx resin schema.erd --keys --curved > schema.keys.svgConvert SQL DDL to resin:
npx resin schema.sql --from-sql > schema.erdErrors come out in compiler format:
schema.erd:3:26: error: referenced table `user` is not in this document
|
3 | user_id bigint ~> user
| ^^^^
= hint: declare it with `external table` if it lives outside this documentUsing the library
npm install @jhzlo/resin elkjsimport ELK from "elkjs";
import { compile, formatDiagnostic, toSvg } from "@jhzlo/resin";
const result = compile(source);
for (const d of result.diagnostics) console.error(formatDiagnostic(d, source, "schema.erd"));
if (result.model) {
const { svg } = await toSvg(result.model, new ELK(), { standalone: true });
}compile returns the syntax tree, the diagnostics, and, when there are no errors, the resolved
model. toSvg draws a model; options choose the look (graphite, the
default, or a glass theme such as aurora-dark), all columns or key columns only, and folded or
expanded audit tables. fromSchema writes a schema held as data, such as what a live database
reports, as resin. @jhzlo/resin/viewer is the playground's canvas and table panel for a page of your own;
see Embedding the viewer.
The language at a glance
| You write | It means |
|---|---|
| name varchar(32)? | A nullable column. Without ? it is NOT NULL |
| pk, uk, uk as uk_name | Primary key, single-column unique (optionally named) |
| -> table.column, ~> table | Physical foreign key; logical reference (defaults to the primary key) |
| index, index as ix_name | An index starting at this column |
| enum(A, B), enc | Allowed values; stored encrypted |
| unique(a, b) as uk_ab, index(a, b) | Composite constraints |
| external table t { ... } | A table owned elsewhere that you can reference |
| service s "Order service" { tables } | The tables one service owns, drawn as an area; a -> into another service is a warning |
| } audit envers(a, b) | Hibernate Envers audit tables for the listed columns |
| `order-items` | A name with characters outside [A-Za-z0-9_] |
The docs walk through each construct with a diagram for every example. The full grammar and the rules for cardinality are in the language reference.
Status
resin is young and pre-1.0. The grammar may still change between minor versions; when it does, the parser recognizes the old form and tells you how to write it now. See the changelog.
Contributing
Issues and pull requests are welcome. CONTRIBUTING.md explains how the project is laid out and how a grammar change goes from the spec to the implementation.

