@milehimikey/em 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +160 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/emit/dot.d.ts +3 -0
  7. package/dist/emit/dot.js +150 -0
  8. package/dist/emit/dot.js.map +1 -0
  9. package/dist/emit/theme.d.ts +9 -0
  10. package/dist/emit/theme.js +35 -0
  11. package/dist/emit/theme.js.map +1 -0
  12. package/dist/layout/grid.d.ts +33 -0
  13. package/dist/layout/grid.js +101 -0
  14. package/dist/layout/grid.js.map +1 -0
  15. package/dist/model/edges.d.ts +8 -0
  16. package/dist/model/edges.js +78 -0
  17. package/dist/model/edges.js.map +1 -0
  18. package/dist/model/model.d.ts +49 -0
  19. package/dist/model/model.js +109 -0
  20. package/dist/model/model.js.map +1 -0
  21. package/dist/model/validate.d.ts +13 -0
  22. package/dist/model/validate.js +127 -0
  23. package/dist/model/validate.js.map +1 -0
  24. package/dist/parser/ast.d.ts +44 -0
  25. package/dist/parser/ast.js +10 -0
  26. package/dist/parser/ast.js.map +1 -0
  27. package/dist/parser/lexer.d.ts +6 -0
  28. package/dist/parser/lexer.js +46 -0
  29. package/dist/parser/lexer.js.map +1 -0
  30. package/dist/parser/parser.d.ts +6 -0
  31. package/dist/parser/parser.js +207 -0
  32. package/dist/parser/parser.js.map +1 -0
  33. package/dist/pipeline.d.ts +11 -0
  34. package/dist/pipeline.js +16 -0
  35. package/dist/pipeline.js.map +1 -0
  36. package/dist/render/drawEdges.d.ts +7 -0
  37. package/dist/render/drawEdges.js +83 -0
  38. package/dist/render/drawEdges.js.map +1 -0
  39. package/dist/render/drawNotes.d.ts +13 -0
  40. package/dist/render/drawNotes.js +117 -0
  41. package/dist/render/drawNotes.js.map +1 -0
  42. package/dist/render/render.d.ts +9 -0
  43. package/dist/render/render.js +106 -0
  44. package/dist/render/render.js.map +1 -0
  45. package/dist/render/svgGeometry.d.ts +10 -0
  46. package/dist/render/svgGeometry.js +47 -0
  47. package/dist/render/svgGeometry.js.map +1 -0
  48. package/dist/render/watch.d.ts +2 -0
  49. package/dist/render/watch.js +18 -0
  50. package/dist/render/watch.js.map +1 -0
  51. package/dist/templates.d.ts +1 -0
  52. package/dist/templates.js +53 -0
  53. package/dist/templates.js.map +1 -0
  54. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 milehimikey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # em — AI friendly event modeling
2
+
3
+ `em` is a command-line tool for [Event Modeling](https://eventmodeling.org/). You write a
4
+ model in a small, **plain-text, slice-first DSL** (`.em`) and `em` renders it to a clean,
5
+ deterministic diagram (SVG by default, PNG on request). Because the source is just text —
6
+ diff-able, reviewable, and unambiguous — it's as easy for an LLM to generate and edit as it
7
+ is for a person to write by hand. Layout is a **strict grid**: swimlane rows and
8
+ time-ordered slice columns stay perfectly aligned with no manual offsets.
9
+
10
+ ```
11
+ examples/order-fulfillment.em -> em render -> order-fulfillment.svg
12
+ ```
13
+
14
+ Rendering is **self-contained** — Graphviz runs as bundled WebAssembly and PNG is
15
+ rasterized in-process, so there's nothing to `apt`/`brew` install for SVG or PNG output.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install -g @milehimikey/em # then `em` is on your PATH
21
+ ```
22
+
23
+ Requires **Node ≥ 18**. SVG and PNG need no other tools. PDF (and other Graphviz raster
24
+ formats) are optional and use a system `rsvg-convert` (librsvg) if one is installed.
25
+
26
+ Run without installing:
27
+
28
+ ```bash
29
+ npx @milehimikey/em init model.em
30
+ npx @milehimikey/em render model.em
31
+ ```
32
+
33
+ ## Quickstart
34
+
35
+ ```bash
36
+ em init model.em # scaffold a starter model
37
+ em render model.em # -> model.svg (open it in a browser)
38
+ em watch model.em # re-render on every save
39
+ em validate model.em # check event-modeling rules
40
+ ```
41
+
42
+ A `.em` model is a list of **slices** (vertical time steps), each holding elements that fall
43
+ into swimlane rows:
44
+
45
+ ```
46
+ model "Order Fulfillment"
47
+
48
+ persona Customer # UI swimlanes (top), one row per persona
49
+ context Order # event swimlanes (bottom), one row per context
50
+ context Payment
51
+
52
+ slice "Browse Catalog" { # each slice is one column (time, left -> right)
53
+ ui Product Catalog @Customer # @Persona places the screen in a persona row
54
+ command Place Order { # a `{ … }` block declares the element's fields
55
+ customerId
56
+ total: Money # optional `: Type` annotation
57
+ }
58
+ event Order Placed @Order note "notes/order-placed.md" { # note "…" links docs
59
+ orderId
60
+ total: Money
61
+ }
62
+ }
63
+
64
+ slice "View Open Orders" {
65
+ view Open Orders from "Order Placed" # from "<event>" wires the data flow
66
+ ui Order List @Customer
67
+ }
68
+ ```
69
+
70
+ ## The four patterns
71
+
72
+ Event Modeling is built from four patterns, all expressible as slices:
73
+
74
+ | Pattern | Flow | DSL elements |
75
+ |---|---|---|
76
+ | **Input** (state change) | UI → command → event | `ui`, `command`, `event` |
77
+ | **Output** (state view) | event → read model → UI | `event`, `view from "…"`, `ui` |
78
+ | **Automation** | read model (todo) → processor → command → event | `view from "…"` + `processor`, then `command` + `event` |
79
+ | **Translation** | external read model → translation → command → event | `view from "…"` + `translation`, then `command` + `event` |
80
+
81
+ Automation/translation slices are **not** triggered by a command — they react to a read
82
+ model (a "todo list") or a timer and *issue* a command when done. The automation slice
83
+ contains **only** the read model it reads and the processor/translation; the command it
84
+ triggers (and that command's event) is the **next** slice.
85
+
86
+ ## DSL reference
87
+
88
+ ### Element keywords → swimlane bands (top → bottom)
89
+
90
+ 0. *(header)* — each slice name is rendered as a title cell in the top row
91
+ 1. `automation` / `processor` / `saga` / `translation` — **Automation** band (only shown if used)
92
+ 2. `ui` — **persona** rows (one per `persona`)
93
+ 3. `command` + `view` — the **API** row (commands and read models share one lane)
94
+ 4. `event` — **context/concept** rows (one per `context`)
95
+
96
+ Commands and read models share the **API** row, so a slice holds *either* a command *or* a
97
+ read model. The empty API lane is dropped when a model has neither; pass `--keep-empty-lanes`
98
+ to keep it.
99
+
100
+ `@Persona` / `@Context` choose the row within a band (undeclared tags create a new row).
101
+ `view … from "Event"[, "Event2"]` declares which events feed a read model and draws the
102
+ data-flow arrow. Arrows within a slice are inferred from the pattern; use `arrow A -> B` for
103
+ anything extra (e.g. a read model feeding a different screen).
104
+
105
+ ### Fields
106
+
107
+ Any element can declare **data fields** in a `{ … }` block — the data it accepts (command),
108
+ records (event), projects (read model), or shows (UI). Each field is `name` with an optional
109
+ `: Type`; write one per line, or inline and comma-separated:
110
+
111
+ ```
112
+ command Place Order { # one per line
113
+ customerId
114
+ items: List<LineItem>
115
+ total: Money
116
+ }
117
+
118
+ event Payment Requested @Payment { orderId, amount: Money } # inline
119
+ ```
120
+
121
+ Fields render **in the box**, UML-style: the name, a divider rule, then the fields. The box
122
+ grows to fit them (width stays fixed, so columns stay aligned), and arrows still anchor to
123
+ the box edges so the lines stay stable. A field block coexists with `note`/`from` clauses on
124
+ the same element. Fields are the foundation of the later event-modeling phases (the slicing /
125
+ information-completeness process); field-level validation across slices is planned.
126
+
127
+ ### Notes
128
+
129
+ Any element can carry `note "path.md"` (valid on every keyword). The prose lives in the
130
+ markdown file — keeping the diagram uncluttered — and the box gets a small **numbered
131
+ folded-corner marker** in its top-right corner. A **legend** is appended below the diagram
132
+ mapping each number to its element and note file, so even a static export tells you which
133
+ note belongs to which box.
134
+
135
+ In **SVG** output the markers and legend rows are links; clicking one opens the markdown
136
+ file. Links resolve relative to the **output SVG's location**, so they keep working whether
137
+ the SVG is rendered beside the model or into another folder (as long as the notes travel
138
+ with it). Open the SVG in a **web browser** to use the links — image viewers like macOS
139
+ Preview/Quick Look show the markers but ignore SVG hyperlinks. Raster output (PNG/PDF) can't
140
+ carry links — that's what the footnote numbers + legend are for.
141
+
142
+ ## Commands
143
+
144
+ ```bash
145
+ em init [file] # scaffold a starter model (default: model.em)
146
+ em render <file> [-o out.svg] # render (format from -o extension or -T)
147
+ em render <file> -o out.png # PNG (in-process, no system deps)
148
+ em render <file> --emit-dot # print the generated DOT instead of rendering
149
+ em render <file> --keep-empty-lanes # don't collapse the API lane when empty
150
+ em watch <file> [-o out.svg] # re-render on every save
151
+ em validate <file> # check event-modeling rules (non-zero exit on errors)
152
+ ```
153
+
154
+ ## Validation rules (`em validate`)
155
+
156
+ - two same-band elements in one slice (collision) → **error** (split into separate slices)
157
+ - a read model whose `from "Event"` doesn't exist → **error**
158
+ - an automation/translation whose `from "<read model>"` doesn't exist → **error**
159
+ - an `arrow` endpoint that matches no element → **error**
160
+ - an automation/translation slice that also holds the command it triggers → **warning**
161
+ - a command that records no event → **warning**
162
+ - a read model with no source event → **warning**
163
+ - a name defined more than once and referenced → **warning** (resolves to first)
164
+
165
+ ## Documentation
166
+
167
+ - [docs/why-dot-not-plantuml.md](docs/why-dot-not-plantuml.md) — why DOT + the strict-grid /
168
+ self-drawn-edges architecture
169
+ - [docs/features.md](docs/features.md) — full feature list and roadmap
170
+ - [docs/dependencies.md](docs/dependencies.md) — how rendering works with no system deps
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ npm install
176
+ npm run build # produces dist/, exposes the `em` bin
177
+ npm test # vitest
178
+ npx tsx src/cli.ts <command> ... # run straight from source
179
+ ```
180
+
181
+ ## License
182
+
183
+ [MIT](LICENSE) © milehimikey
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: MIT
3
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
4
+ import { basename, dirname, extname, resolve } from "node:path";
5
+ import { Command } from "commander";
6
+ import { compile } from "./pipeline.js";
7
+ import { ParseError } from "./parser/parser.js";
8
+ import { renderDot, formatFromPath } from "./render/render.js";
9
+ import { watchFile } from "./render/watch.js";
10
+ import { formatDiagnostic, hasErrors } from "./model/validate.js";
11
+ import { STARTER_EM } from "./templates.js";
12
+ const program = new Command();
13
+ program
14
+ .name("em")
15
+ .description("Event Modeling CLI — slice-first DSL rendered as a strict Graphviz grid")
16
+ .version("0.1.0");
17
+ program
18
+ .command("init")
19
+ .description("scaffold a starter .em model")
20
+ .argument("[file]", "output file", "model.em")
21
+ .option("-f, --force", "overwrite if the file exists")
22
+ .action((file, opts) => {
23
+ if (existsSync(file) && !opts.force) {
24
+ console.error(`refusing to overwrite ${file} (use --force)`);
25
+ process.exit(1);
26
+ }
27
+ writeFileSync(file, STARTER_EM);
28
+ console.log(`wrote ${file}`);
29
+ });
30
+ program
31
+ .command("render")
32
+ .description("transpile a model and render it (or emit DOT)")
33
+ .argument("<file>", "input .em file")
34
+ .option("-o, --out <path>", "output path (extension picks the format)")
35
+ .option("-T, --format <fmt>", "output format (svg, png, pdf, ...)")
36
+ .option("--emit-dot", "print the generated DOT instead of rendering")
37
+ .option("--keep-empty-lanes", "keep the API lane even when empty")
38
+ .action(async (file, opts) => {
39
+ const { dot, model, diagnostics } = compileFile(file, {
40
+ keepEmptyLanes: opts.keepEmptyLanes,
41
+ });
42
+ printDiagnostics(diagnostics);
43
+ warnMissingNotes(file, model);
44
+ if (opts.emitDot) {
45
+ if (opts.out) {
46
+ writeFileSync(opts.out, dot);
47
+ console.log(`wrote ${opts.out}`);
48
+ }
49
+ else {
50
+ process.stdout.write(dot + "\n");
51
+ }
52
+ return;
53
+ }
54
+ if (hasErrors(diagnostics)) {
55
+ console.error("not rendering: fix the errors above");
56
+ process.exit(1);
57
+ }
58
+ const out = opts.out ?? defaultOut(file, opts.format ?? "svg");
59
+ const fmt = opts.format ?? formatFromPath(out);
60
+ await renderDot(dot, model, out, fmt, dirname(file));
61
+ console.log(`rendered ${out}`);
62
+ });
63
+ program
64
+ .command("watch")
65
+ .description("re-render on every save")
66
+ .argument("<file>", "input .em file")
67
+ .option("-o, --out <path>", "output path (extension picks the format)")
68
+ .option("-T, --format <fmt>", "output format (svg, png, pdf, ...)")
69
+ .option("--keep-empty-lanes", "keep the API lane even when empty")
70
+ .action(async (file, opts) => {
71
+ const out = opts.out ?? defaultOut(file, opts.format ?? "svg");
72
+ const fmt = opts.format ?? formatFromPath(out);
73
+ const build = async () => {
74
+ const started = Date.now();
75
+ try {
76
+ const { dot, model, diagnostics } = compileFile(file, {
77
+ keepEmptyLanes: opts.keepEmptyLanes,
78
+ });
79
+ printDiagnostics(diagnostics);
80
+ warnMissingNotes(file, model);
81
+ if (hasErrors(diagnostics)) {
82
+ console.error("skipped render (errors above)");
83
+ return;
84
+ }
85
+ await renderDot(dot, model, out, fmt, dirname(file));
86
+ console.log(`rendered ${out} (${Date.now() - started}ms)`);
87
+ }
88
+ catch (e) {
89
+ reportError(e);
90
+ }
91
+ };
92
+ await build();
93
+ watchFile(file, build);
94
+ console.log(`watching ${file} … (ctrl-c to stop)`);
95
+ });
96
+ program
97
+ .command("validate")
98
+ .description("check a model against event-modeling rules")
99
+ .argument("<file>", "input .em file")
100
+ .action((file) => {
101
+ const { diagnostics } = compileFile(file);
102
+ printDiagnostics(diagnostics);
103
+ if (hasErrors(diagnostics))
104
+ process.exit(1);
105
+ if (diagnostics.length === 0)
106
+ console.log("ok — no issues");
107
+ });
108
+ program.parseAsync().catch((e) => {
109
+ reportError(e);
110
+ process.exit(1);
111
+ });
112
+ // ---- helpers ----
113
+ function compileFile(file, opts = {}) {
114
+ let source;
115
+ try {
116
+ source = readFileSync(file, "utf8");
117
+ }
118
+ catch {
119
+ console.error(`cannot read ${file}`);
120
+ process.exit(1);
121
+ }
122
+ try {
123
+ return compile(source, opts);
124
+ }
125
+ catch (e) {
126
+ if (e instanceof ParseError) {
127
+ console.error(`parse error in ${file} ${e.message}`);
128
+ process.exit(1);
129
+ }
130
+ throw e;
131
+ }
132
+ }
133
+ function defaultOut(file, fmt) {
134
+ const base = basename(file, extname(file));
135
+ return `${base}.${fmt}`;
136
+ }
137
+ /** Warn (without failing) when an element's `note` file can't be found. */
138
+ function warnMissingNotes(file, model) {
139
+ const base = dirname(file);
140
+ for (const el of model.elements) {
141
+ if (el.note && !existsSync(resolve(base, el.note))) {
142
+ console.warn(` warn note file not found for "${el.name}": ${el.note}`);
143
+ }
144
+ }
145
+ }
146
+ function printDiagnostics(diags) {
147
+ if (diags.length === 0)
148
+ return;
149
+ for (const d of diags) {
150
+ const line = formatDiagnostic(d);
151
+ if (d.severity === "error")
152
+ console.error(line);
153
+ else
154
+ console.warn(line);
155
+ }
156
+ }
157
+ function reportError(e) {
158
+ console.error(e instanceof Error ? e.message : String(e));
159
+ }
160
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { Grid } from "../layout/grid.js";
3
+ export declare function emitDot(model: NormalizedModel, grid: Grid): string;
@@ -0,0 +1,150 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Emits Graphviz DOT for a normalized model using the strict-grid technique:
3
+ // - one heavily-weighted invisible vertical edge chain per column locks columns
4
+ // - one `rank=same` group per row locks rows and orders cells left -> right
5
+ // - a fixed-width title cell per slice anchors the column widths
6
+ // - empty coordinates are invisible spacers; elements are floating boxes
7
+ //
8
+ // No semantic arrows are emitted here: Graphviz only lays out the grid and
9
+ // renders the boxes/labels. The renderer reads each box's rectangle back out of
10
+ // the rendered SVG and draws the arrows itself (see src/render/drawEdges.ts), so
11
+ // it has full control over straight within-slice lines and curved cross-slice
12
+ // lines instead of fighting Graphviz's global edge router.
13
+ import { headerCellId, nodeIdAt, placeholderId } from "../layout/grid.js";
14
+ import { styleFor } from "./theme.js";
15
+ const HEADER_FILL = "#E3E7EB";
16
+ const HEADER_BORDER = "#C7CDD4";
17
+ // Every cell is the same width so columns are evenly spaced and the header row
18
+ // lines up with the element columns. Header width (pt) must match CELL_W (in).
19
+ const CELL_W = 2.0; // inches
20
+ const CELL_H = 0.62;
21
+ const HEADER_H = 0.46;
22
+ export function emitDot(model, grid) {
23
+ const L = [];
24
+ L.push(`digraph EventModel {`);
25
+ L.push(` graph [rankdir=TB, splines=false, nodesep=0.35, ranksep="0.5 equally", ` +
26
+ `fontname="Helvetica", fontsize=18, labelloc="t", label=${q(model.name)}];`);
27
+ L.push(` node [shape=box, style="filled", fontname="Helvetica", fontsize=11, ` +
28
+ `margin="0.18,0.10", penwidth=1.3];`);
29
+ L.push(` edge [fontname="Helvetica", fontsize=9];`);
30
+ L.push("");
31
+ // ---- row label nodes (leftmost column) ----
32
+ L.push(" // row (swimlane) labels");
33
+ grid.rows.forEach((row, r) => {
34
+ L.push(` ${rowLabelId(r)} [shape=plaintext, style="", label=${q(row.label)}, ` +
35
+ `fontsize=10, fontcolor="#5F6368"];`);
36
+ });
37
+ L.push("");
38
+ // ---- cell nodes ----
39
+ L.push(" // cells");
40
+ grid.rows.forEach((row, r) => {
41
+ for (let c = 0; c < grid.cols; c++) {
42
+ if (row.band === "header") {
43
+ L.push(" " + headerCell(c, grid.sliceNames[c]));
44
+ }
45
+ else {
46
+ const el = grid.cells[r][c];
47
+ L.push(" " + (el ? elementCell(el) : emptyCell(r, c)));
48
+ }
49
+ }
50
+ });
51
+ L.push("");
52
+ // ---- rigid grid: weighted invisible vertical chains per column ----
53
+ L.push(" // column locks");
54
+ L.push(` edge [style=invis, weight=1000, arrowhead=none];`);
55
+ L.push(" " + columnChain(grid, -1) + ";"); // label column
56
+ for (let c = 0; c < grid.cols; c++) {
57
+ L.push(" " + columnChain(grid, c) + ";");
58
+ }
59
+ L.push("");
60
+ // ---- rows: rank=same + left-right ordering ----
61
+ L.push(" // row ranks");
62
+ grid.rows.forEach((_row, r) => {
63
+ const ids = [rowLabelId(r)];
64
+ for (let c = 0; c < grid.cols; c++)
65
+ ids.push(nodeIdAt(grid, r, c));
66
+ L.push(` { rank=same; ${ids.join(" -> ")} [style=invis, arrowhead=none]; }`);
67
+ });
68
+ L.push(`}`);
69
+ return L.join("\n");
70
+ }
71
+ function elementCell(el) {
72
+ const s = styleFor(el.kind);
73
+ const shape = el.kind === "ui" ? `"filled"` : `"filled,rounded"`;
74
+ if (el.fields && el.fields.length > 0) {
75
+ // UML-style box: title, a divider rule, then the fields. The HTML table
76
+ // auto-sizes the box (height grows with the field count); width stays at
77
+ // CELL_W as a minimum so columns stay aligned. Arrows still anchor to the
78
+ // box edges read back from the SVG, so they remain stable.
79
+ return (`${el.id} [label=${fieldLabel(el)}, fillcolor="${s.fill}", ` +
80
+ `color="${s.stroke}", fontcolor="${s.fontColor}", style=${shape}, ` +
81
+ `shape=box, fixedsize=false, width=${CELL_W}, margin="0.04,0.03"];`);
82
+ }
83
+ return (`${el.id} [label=${q(wrapLabel(el.name))}, fillcolor="${s.fill}", ` +
84
+ `color="${s.stroke}", fontcolor="${s.fontColor}", style=${shape}, ` +
85
+ `fixedsize=true, width=${CELL_W}, height=${CELL_H}];`);
86
+ }
87
+ /** Graphviz HTML-table label: bold title, a horizontal rule, one row per field. */
88
+ function fieldLabel(el) {
89
+ const rows = [];
90
+ rows.push(`<TR><TD><B>${esc(el.name)}</B></TD></TR>`);
91
+ rows.push(`<HR/>`);
92
+ for (const f of el.fields ?? []) {
93
+ const ftype = f.type ? ` : <FONT COLOR="#5F6368">${esc(f.type)}</FONT>` : "";
94
+ rows.push(`<TR><TD ALIGN="LEFT"><FONT POINT-SIZE="9">${esc(f.name)}${ftype}</FONT></TD></TR>`);
95
+ }
96
+ return (`<<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0" CELLPADDING="1">` +
97
+ rows.join("") +
98
+ `</TABLE>>`);
99
+ }
100
+ /** Escape text for use inside a Graphviz HTML-like label. */
101
+ function esc(s) {
102
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
103
+ }
104
+ /** Empty cells are invisible spacers that hold the column width and row height. */
105
+ function emptyCell(r, c) {
106
+ return `${placeholderId(r, c)} [label="", style=invis, fixedsize=true, width=${CELL_W}, height=${CELL_H}];`;
107
+ }
108
+ /** Fixed-size title cell per slice (same width as cells, so columns line up). */
109
+ function headerCell(c, name) {
110
+ return (`${headerCellId(c)} [label=${q(wrapLabel(name, 20))}, shape=box, ` +
111
+ `style=filled, fillcolor="${HEADER_FILL}", color="${HEADER_BORDER}", ` +
112
+ `fontcolor="#1F2933", fontname="Helvetica-Bold", fontsize=10, ` +
113
+ `fixedsize=true, width=${CELL_W}, height=${HEADER_H}, penwidth=1];`);
114
+ }
115
+ function columnChain(grid, col) {
116
+ const ids = [];
117
+ grid.rows.forEach((_row, r) => {
118
+ ids.push(col < 0 ? rowLabelId(r) : nodeIdAt(grid, r, col));
119
+ });
120
+ return ids.join(" -> ");
121
+ }
122
+ function rowLabelId(r) {
123
+ return `__row_${r}`;
124
+ }
125
+ /** Word-wrap long labels onto multiple lines for tidier boxes. */
126
+ function wrapLabel(name, max = 18) {
127
+ return wrap(name, max).join("\\n");
128
+ }
129
+ function wrap(name, max) {
130
+ const words = name.split(/\s+/);
131
+ const lines = [];
132
+ let cur = "";
133
+ for (const w of words) {
134
+ if (cur && (cur + " " + w).length > max) {
135
+ lines.push(cur);
136
+ cur = w;
137
+ }
138
+ else {
139
+ cur = cur ? cur + " " + w : w;
140
+ }
141
+ }
142
+ if (cur)
143
+ lines.push(cur);
144
+ return lines;
145
+ }
146
+ /** Quote a string for DOT (label newlines already encoded as \n). */
147
+ function q(s) {
148
+ return `"${s.replace(/"/g, '\\"')}"`;
149
+ }
150
+ //# sourceMappingURL=dot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dot.js","sourceRoot":"","sources":["../../src/emit/dot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { ElementKind } from "../parser/ast.js";
2
+ export interface NodeStyle {
3
+ fill: string;
4
+ stroke: string;
5
+ fontColor: string;
6
+ }
7
+ export declare function styleFor(kind: ElementKind): NodeStyle;
8
+ /** Arrow colour keyed on the source element's kind. */
9
+ export declare function edgeColorFor(kind: ElementKind | undefined): string;
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Event Modeling colour conventions.
3
+ const STYLES = {
4
+ ui: { fill: "#FFFFFF", stroke: "#9AA0A6", fontColor: "#202124" },
5
+ command: { fill: "#B8D0F5", stroke: "#2B6CB0", fontColor: "#10243E" },
6
+ view: { fill: "#C6E7C6", stroke: "#2F855A", fontColor: "#173F25" },
7
+ event: { fill: "#F6B26B", stroke: "#B7791F", fontColor: "#3F2A06" },
8
+ automation: { fill: "#DBDBDB", stroke: "#5F6368", fontColor: "#202124" },
9
+ processor: { fill: "#DBDBDB", stroke: "#5F6368", fontColor: "#202124" },
10
+ saga: { fill: "#DBDBDB", stroke: "#5F6368", fontColor: "#202124" },
11
+ translation: { fill: "#E4D5F7", stroke: "#6B46C1", fontColor: "#2A1A4A" },
12
+ };
13
+ export function styleFor(kind) {
14
+ return STYLES[kind];
15
+ }
16
+ /** Arrow colour keyed on the source element's kind. */
17
+ export function edgeColorFor(kind) {
18
+ switch (kind) {
19
+ case "command":
20
+ return "#2B6CB0";
21
+ case "view":
22
+ return "#2F855A";
23
+ case "event":
24
+ return "#B7791F";
25
+ case "automation":
26
+ case "processor":
27
+ case "saga":
28
+ return "#5F6368";
29
+ case "translation":
30
+ return "#6B46C1";
31
+ default:
32
+ return "#3C4043";
33
+ }
34
+ }
35
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/emit/theme.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,qCAAqC;AAUrC,MAAM,MAAM,GAAmC;IAC7C,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAChE,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACrE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAClE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACnE,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACxE,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACvE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAClE,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;CAC1E,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,IAAiB;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,IAA6B;IACxD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,MAAM;YACT,OAAO,SAAS,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB,KAAK,YAAY,CAAC;QAClB,KAAK,WAAW,CAAC;QACjB,KAAK,MAAM;YACT,OAAO,SAAS,CAAC;QACnB,KAAK,aAAa;YAChB,OAAO,SAAS,CAAC;QACnB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { Element, NormalizedModel } from "../model/model.js";
2
+ export type Band = "header" | "automation" | "ui" | "api" | "event";
3
+ export declare const HEADER_KEY = "__header";
4
+ export interface GridRow {
5
+ key: string;
6
+ label: string;
7
+ band: Band;
8
+ }
9
+ export interface Collision {
10
+ rowKey: string;
11
+ col: number;
12
+ kept: Element;
13
+ dropped: Element;
14
+ }
15
+ export interface Grid {
16
+ rows: GridRow[];
17
+ cols: number;
18
+ /** Slice names, one per column (rendered in the header row). */
19
+ sliceNames: string[];
20
+ /** cells[rowIndex][colIndex] — real element or null (placeholder/header). */
21
+ cells: (Element | null)[][];
22
+ rowIndexByKey: Map<string, number>;
23
+ collisions: Collision[];
24
+ }
25
+ export interface LayoutOptions {
26
+ /** Keep the structural command/view rows even when empty. */
27
+ keepEmptyLanes?: boolean;
28
+ }
29
+ export declare function placeholderId(row: number, col: number): string;
30
+ export declare function headerCellId(col: number): string;
31
+ /** Node id at a grid coordinate (header cell, element, or placeholder). */
32
+ export declare function nodeIdAt(grid: Grid, row: number, col: number): string;
33
+ export declare function layout(model: NormalizedModel, opts?: LayoutOptions): Grid;