All pages

Author diagrams in TypeScript

Recipes for writing a .diagram.ts source. Assumes you have compiled one before — if not, do Tutorial 1 first.

Every method is listed in the Builder API reference.

Start a diagram

Any file matching .diagrams/src/**/*.diagram.ts is picked up. The filename minus .diagram.ts names both the artifact and the entry in the studio's picker.

import { model } from '@diagc/core';

const m = model('shop', { name: 'Shop' });
// ...
export default m;

Compile one file instead of all of them:

diagc compile .diagrams/src/shop.diagram.ts

What you can import

Import from @diagc/core: model() and the builder it returns, the model types (DiagramModel and the rest), and validate(). From 1.0 on, those change only in a major release. @diagc/core/internal is diagc's own plumbing and changes in any release.

Give a node a look

type and icon are free-form strings resolved by the renderer's registries. Unknown values draw as a plain box, silently.

const db = m.node('orders-db', {
  type: 'database',        // cylinder
  icon: 'postgres',
  name: 'Orders DB',
  color: '#0891b2',        // overrides the type's default look
  description: 'Shared by the API and the worker.',
});

description appears in the studio's detail panel, never on the canvas. If a reader must see it, it belongs in name.

See registry defaults for the types and icons that already exist.

Nest nodes

platform.contains(comms, identity, billing);
comms.contains(mail, push);

A node can have several parents — that is the point of containment being a DAG:

identity.contains(sharedDb);
billing.contains(sharedDb);   // one database, two owners

When both owners are folded, the shared node is promoted to where both can see it and marked ⚭. You do not have to do anything for that to happen.

Connect nodes

m.relate(api, db, { kind: 'writes' });
m.relate(api, worker, { kind: 'async', label: 'order placed' });

kind is free-form. Built-in kinds: sync, async (dashed), reads, writes (thick), hosted-on (dashed), flow (animated), fk (crow's foot to bar).

Several relations between the same pair are fine — ids are generated as from->to#0, #1, and so on.

Style one connection

m.relate(api, worker, {
  kind: 'async',
  label: 'order placed',
  style: { shape: 'step', line: 'dashed', color: '#c2410c', end: 'diamond' },
});

Anything left unset falls back to the kind's registry style and the layer tint. Full option list in Model reference.

Attach data

metadata is free-form and untouched by validation. The studio can surface chosen keys as badges.

const svc = m.node('mail-svc', {
  type: 'service',
  metadata: { language: 'typescript', repo: 'https://github.com/acme/mail-svc' },
});

Draw an ER diagram

const users = m.table('users', {
  columns: [
    { name: 'id', type: 'uuid', pk: true },
    { name: 'email', type: 'text' },
  ],
});

const orders = m.table('orders', {
  columns: [
    { name: 'id', type: 'uuid', pk: true },
    { name: 'user_id', type: 'uuid', fk: true },
  ],
});

m.fk(orders, 'user_id', users);   // toColumn defaults to the target's PK

m.fk throws if the target has zero or several primary-key columns and you did not pass toColumn. Declare the target table, with its PK, before calling.

Rich labels

m.node('warn', {
  name: 'Legacy path',
  rich: [{ text: 'Legacy', bold: true }, { text: ' path' }],
  textAlign: 'center',
  fontScale: 'lg',
});

name must equal the concatenated run text, or validation fails with invalid-rich.

Pin a visual style

Presets are clean, sketch, hand-drawn, pencil, blueprint, marker. Picking one in the studio header is a viewer preference and is not saved to the diagram.

To make a diagram always render hand-drawn, pin style on the model:

m.style('hand-drawn');

An unknown preset id is legal — the renderer falls back to the app-level preference.

Organise the file

Nothing enforces an order, but this reads well and matches the shipped acme example:

  1. model(...)
  2. m.layer(...) declarations
  3. m.plane(...) declarations
  4. nodes, grouped by area
  5. containment, grouped by plane
  6. relations, base ones first then layered ones
  7. export default m

Subfolders under .diagrams/src/ are mirrored into the artifact directory, so src/team-a/app.diagram.ts never clobbers src/team-b/app.diagram.ts.

When compile fails

The message names the file and the issue, and no artifact is written. Every code is listed in Model reference. The four you will actually hit:

Code Usually means
duplicate-node You called m.node twice with the same id.
dangling-endpoint A relation names a node you renamed or removed.
unknown-layer You used a layer on a relation without m.layer(...).
containment-cycle A node ended up inside itself, transitively.

See also

View this page on GitHub