Tables & entities

A |table| is a grid with ruled gutters; an |entity| is that table shaped like a database card. Neither adds grammar — cells are bare strings, and relationships are ordinary links.

Cells are strings, in row order

|table#plans| "Plans" { columns: 120, 80, 90; } [
  "Plan"  "Seats" "Price"
  "Free"  "1"     "$0"
  "Team"  "10"    "$120"
  "Scale" "50"    "$480"
]
Plan Seats Price Free 1 $0 Team 10 $120 Scale 50 $480 Plans

columns: fixes the track widths and the count, so the rows need no separators — the count is what breaks them. The first row becomes the header band, and the table’s label is its caption.

lini fmt knows that count too, and pads the cells into aligned columns, so the source keeps the shape of the thing it draws.

{
  |table| { fill: --purple-wash; stroke: --purple-deep; }
  .bad { color: --red-ink; font-weight: bold; }
}

|table#regions| "Edge regions" { columns: 110, 90, 80, 90; align: start, end, end, center; } [
  "Region"   "Requests" "p95"    "Status"
  "us-east"  "18.2M"    "84 ms"  "Healthy"
  "eu-west"  "11.4M"    "96 ms"  "Healthy"
  "ap-south" "6.8M"     "142 ms" "Degraded" .bad
  |footer| "Total"; |footer| "36.4M"; |footer| "—"; |footer| "3 regions"
]
Region Requests p95 Status us-east 18.2M 84 ms Healthy eu-west 11.4M 96 ms Healthy ap-south 6.8M 142 ms Degraded Total 36.4M 3 regions Edge regions

align: takes one value per column — start, end or center — and moves the header with the body, so a numeric column lines up on its digits.

A cell is a node like any other, which is the whole trick here:

  • Give one a class or a { } and it styles itself, as Degraded does.
  • Wrap a last-row cell in |footer| and it joins the muted footer band.
  • Reach them all with |cell| { }, or just one table’s with |table#regions| |cell| { }.

Entities and the crow’s foot

{
  gap: 120;
  |entity| { fill: --rose-wash; stroke: --rose-deep; }
}

|entity#users| "Users" { columns: auto, auto, auto; } [
  "PK" "id"    "int"
  ""   "email" "varchar"
  ""   "name"  "varchar"
]
|entity#orders| "Orders" { columns: auto, auto, auto; } [
  "PK" "id"      "int"
  "FK" "user_id" "int"
  ""   "total"   "decimal"
]

users +-< orders "places"
Users PK id int email varchar name varchar Orders PK id int FK user_id int total decimal places

An |entity| is a table whose label is a title spanning every column, over left-aligned rows. Two auto columns is the default; a third gives you the PK / FK gutter.

The relationship is a plain link wearing a crow’s-foot operator:

OpReadsOpReads
-+one-o+zero or one
-<many-+<one or many
-++exactly one-o<zero or many

Each marker composes [min][max]: the ring o or the bar + hugs the line, and the crow < sits against the entity. Either end takes one, so users +-< orders reads one-to-many.

They are sugar over marker: crow, one, zero-or-many and the rest — so marker-end: still overrides a single end when you need it to.

A schema, wired field to field

{
  layout: grid; columns: repeat(3); gap: 60 90; clearance: 16;
  |entity| { fill: --sky-wash; stroke: --sky-deep; }
  #post { fill: --purple-wash; stroke: --purple-deep; }
  #post_tag { fill: --amber-wash; stroke: --amber-deep; }
}

|entity#author| "Author" { cell: 1 1; } [ "id" "int"  "name" "varchar" ]
|entity#post| "Post" { cell: 2 1; } [
  |cell#pid| "id"; "int"
  "author_id" "int"
  "title" "varchar"
]
|entity#post_tag| "PostTag" { cell: 3 1; } [
  |cell#pref| "post_id"; "int"
  "tag_id" "int"
]
|entity#tag| "Tag" { cell: 3 2; } [ "id" "int"  "name" "varchar" ]

author -< post "writes"
post.pid -< post_tag.pref
tag -< post_tag
Author id int name varchar Post id int author_id int title varchar PostTag post_id int tag_id int Tag id int name varchar writes

Entities lay out like any other node — a grid here, but a flow, or pin and translate, work the same.

A relationship lands on the card’s edge by default. To wire a specific field, give that cell an id and link the path — |cell#pid| "id" makes the cell addressable, so post.pid -< post_tag.pref leaves and arrives at those two rows rather than at the two cards.

The join table needs no special treatment either: it is an entity like the others, and the two links into it are what make the many-to-many.

Go deeper

The |table|, |cell|, |header|, |footer| and |entity| bundles are in templates; the operators in links.