@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.
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +160 -0
- package/dist/cli.js.map +1 -0
- package/dist/emit/dot.d.ts +3 -0
- package/dist/emit/dot.js +150 -0
- package/dist/emit/dot.js.map +1 -0
- package/dist/emit/theme.d.ts +9 -0
- package/dist/emit/theme.js +35 -0
- package/dist/emit/theme.js.map +1 -0
- package/dist/layout/grid.d.ts +33 -0
- package/dist/layout/grid.js +101 -0
- package/dist/layout/grid.js.map +1 -0
- package/dist/model/edges.d.ts +8 -0
- package/dist/model/edges.js +78 -0
- package/dist/model/edges.js.map +1 -0
- package/dist/model/model.d.ts +49 -0
- package/dist/model/model.js +109 -0
- package/dist/model/model.js.map +1 -0
- package/dist/model/validate.d.ts +13 -0
- package/dist/model/validate.js +127 -0
- package/dist/model/validate.js.map +1 -0
- package/dist/parser/ast.d.ts +44 -0
- package/dist/parser/ast.js +10 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/lexer.d.ts +6 -0
- package/dist/parser/lexer.js +46 -0
- package/dist/parser/lexer.js.map +1 -0
- package/dist/parser/parser.d.ts +6 -0
- package/dist/parser/parser.js +207 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/pipeline.d.ts +11 -0
- package/dist/pipeline.js +16 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/render/drawEdges.d.ts +7 -0
- package/dist/render/drawEdges.js +83 -0
- package/dist/render/drawEdges.js.map +1 -0
- package/dist/render/drawNotes.d.ts +13 -0
- package/dist/render/drawNotes.js +117 -0
- package/dist/render/drawNotes.js.map +1 -0
- package/dist/render/render.d.ts +9 -0
- package/dist/render/render.js +106 -0
- package/dist/render/render.js.map +1 -0
- package/dist/render/svgGeometry.d.ts +10 -0
- package/dist/render/svgGeometry.js +47 -0
- package/dist/render/svgGeometry.js.map +1 -0
- package/dist/render/watch.d.ts +2 -0
- package/dist/render/watch.js +18 -0
- package/dist/render/watch.js.map +1 -0
- package/dist/templates.d.ts +1 -0
- package/dist/templates.js +53 -0
- package/dist/templates.js.map +1 -0
- 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
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
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/emit/dot.js
ADDED
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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;
|