@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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Turns a normalized model into a rigid R x C grid.
|
|
3
|
+
//
|
|
4
|
+
// Rows (bands), top -> bottom:
|
|
5
|
+
// 0. header (slice names — one visible cell per column)
|
|
6
|
+
// 1. automation (single row, only if the model uses automation elements)
|
|
7
|
+
// 2. ui (one row per persona)
|
|
8
|
+
// 3. api (commands + read models share one row — the system's API)
|
|
9
|
+
// 4. event (one row per context/concept)
|
|
10
|
+
//
|
|
11
|
+
// Columns = slices, in declaration order (time, left -> right).
|
|
12
|
+
//
|
|
13
|
+
// Every (row, col) without a real element becomes an invisible placeholder so
|
|
14
|
+
// that rank chains and weighted column edges keep the grid perfectly aligned.
|
|
15
|
+
export const HEADER_KEY = "__header";
|
|
16
|
+
export function placeholderId(row, col) {
|
|
17
|
+
return `__ph_${row}_${col}`;
|
|
18
|
+
}
|
|
19
|
+
export function headerCellId(col) {
|
|
20
|
+
return `__hdr_${col}`;
|
|
21
|
+
}
|
|
22
|
+
/** Node id at a grid coordinate (header cell, element, or placeholder). */
|
|
23
|
+
export function nodeIdAt(grid, row, col) {
|
|
24
|
+
if (grid.rows[row].band === "header")
|
|
25
|
+
return headerCellId(col);
|
|
26
|
+
const el = grid.cells[row][col];
|
|
27
|
+
return el ? el.id : placeholderId(row, col);
|
|
28
|
+
}
|
|
29
|
+
function rowKeyForElement(el) {
|
|
30
|
+
switch (el.kind) {
|
|
31
|
+
case "ui":
|
|
32
|
+
return `ui:${el.persona}`;
|
|
33
|
+
case "command":
|
|
34
|
+
case "view":
|
|
35
|
+
return "api";
|
|
36
|
+
case "event":
|
|
37
|
+
return `event:${el.context}`;
|
|
38
|
+
case "automation":
|
|
39
|
+
case "processor":
|
|
40
|
+
case "saga":
|
|
41
|
+
case "translation":
|
|
42
|
+
return "automation";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function layout(model, opts = {}) {
|
|
46
|
+
const rows = [];
|
|
47
|
+
rows.push({ key: HEADER_KEY, label: "", band: "header" });
|
|
48
|
+
if (model.hasAutomation) {
|
|
49
|
+
rows.push({ key: "automation", label: "Automation", band: "automation" });
|
|
50
|
+
}
|
|
51
|
+
for (const persona of model.personas) {
|
|
52
|
+
rows.push({ key: `ui:${persona}`, label: persona, band: "ui" });
|
|
53
|
+
}
|
|
54
|
+
rows.push({ key: "api", label: "API", band: "api" });
|
|
55
|
+
for (const context of model.contexts) {
|
|
56
|
+
rows.push({ key: `event:${context}`, label: context, band: "event" });
|
|
57
|
+
}
|
|
58
|
+
const indexOf = new Map();
|
|
59
|
+
rows.forEach((r, i) => indexOf.set(r.key, i));
|
|
60
|
+
const cols = model.slices.length;
|
|
61
|
+
const cells = rows.map(() => new Array(cols).fill(null));
|
|
62
|
+
const collisions = [];
|
|
63
|
+
for (const el of model.elements) {
|
|
64
|
+
const r = indexOf.get(rowKeyForElement(el));
|
|
65
|
+
if (r === undefined)
|
|
66
|
+
continue; // unreachable, rows cover all kinds
|
|
67
|
+
const c = el.sliceIndex;
|
|
68
|
+
if (cells[r][c]) {
|
|
69
|
+
collisions.push({ rowKey: rows[r].key, col: c, kept: cells[r][c], dropped: el });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
cells[r][c] = el;
|
|
73
|
+
}
|
|
74
|
+
// Collapse the API lane only if the model has no commands or read models.
|
|
75
|
+
const keep = rows.map((row, r) => {
|
|
76
|
+
if (opts.keepEmptyLanes)
|
|
77
|
+
return true;
|
|
78
|
+
if (row.band !== "api")
|
|
79
|
+
return true;
|
|
80
|
+
return cells[r].some((c) => c !== null);
|
|
81
|
+
});
|
|
82
|
+
const keptRows = [];
|
|
83
|
+
const keptCells = [];
|
|
84
|
+
rows.forEach((row, r) => {
|
|
85
|
+
if (keep[r]) {
|
|
86
|
+
keptRows.push(row);
|
|
87
|
+
keptCells.push(cells[r]);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const rowIndexByKey = new Map();
|
|
91
|
+
keptRows.forEach((r, i) => rowIndexByKey.set(r.key, i));
|
|
92
|
+
return {
|
|
93
|
+
rows: keptRows,
|
|
94
|
+
cols,
|
|
95
|
+
sliceNames: model.slices.map((s) => s.name),
|
|
96
|
+
cells: keptCells,
|
|
97
|
+
rowIndexByKey,
|
|
98
|
+
collisions,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=grid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grid.js","sourceRoot":"","sources":["../../src/layout/grid.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,oDAAoD;AACpD,EAAE;AACF,+BAA+B;AAC/B,iEAAiE;AACjE,8EAA8E;AAC9E,2CAA2C;AAC3C,+EAA+E;AAC/E,mDAAmD;AACnD,EAAE;AACF,gEAAgE;AAChE,EAAE;AACF,8EAA8E;AAC9E,8EAA8E;AAM9E,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC;AA+BrC,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,GAAW;IACpD,OAAO,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,SAAS,GAAG,EAAE,CAAC;AACxB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,QAAQ,CAAC,IAAU,EAAE,GAAW,EAAE,GAAW;IAC3D,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;IAC/D,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,gBAAgB,CAAC,EAAW;IACnC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;QAChB,KAAK,IAAI;YACP,OAAO,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC;QAC5B,KAAK,SAAS,CAAC;QACf,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf,KAAK,OAAO;YACV,OAAO,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC;QAC/B,KAAK,YAAY,CAAC;QAClB,KAAK,WAAW,CAAC;QACjB,KAAK,MAAM,CAAC;QACZ,KAAK,aAAa;YAChB,OAAO,YAAY,CAAC;IACxB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAsB,EAAE,OAAsB,EAAE;IACrE,MAAM,IAAI,GAAc,EAAE,CAAC;IAE3B,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACxB,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrD,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IAE9C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IACjC,MAAM,KAAK,GAAyB,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAChD,IAAI,KAAK,CAAiB,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAC3C,CAAC;IACF,MAAM,UAAU,GAAgB,EAAE,CAAC;IAEnC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS,CAAC,oCAAoC;QACnE,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC;QACxB,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;YAClF,SAAS;QACX,CAAC;QACD,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IACnB,CAAC;IAED,0EAA0E;IAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QAC/B,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO,IAAI,CAAC;QACrC,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QACpC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAyB,EAAE,CAAC;IAC3C,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;QACtB,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACZ,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnB,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,aAAa,GAAG,IAAI,GAAG,EAAkB,CAAC;IAChD,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IAExD,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,IAAI;QACJ,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3C,KAAK,EAAE,SAAS;QAChB,aAAa;QACb,UAAU;KACX,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NormalizedModel } from "./model.js";
|
|
2
|
+
export interface SemanticEdge {
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
color: string;
|
|
6
|
+
}
|
|
7
|
+
/** Infer pattern arrows from each slice plus cross-slice `from` sources. */
|
|
8
|
+
export declare function semanticEdges(model: NormalizedModel): SemanticEdge[];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// The semantic edges of a model: which arrows exist and their colour. This is
|
|
3
|
+
// the single source of truth for the diagram's arrows, decoupled from how they
|
|
4
|
+
// are drawn (the renderer turns these into SVG paths over the Graphviz grid).
|
|
5
|
+
import { AUTOMATION_KINDS } from "../parser/ast.js";
|
|
6
|
+
import { normalizeName } from "./model.js";
|
|
7
|
+
import { edgeColorFor } from "../emit/theme.js";
|
|
8
|
+
/** Infer pattern arrows from each slice plus cross-slice `from` sources. */
|
|
9
|
+
export function semanticEdges(model) {
|
|
10
|
+
const edges = [];
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
const add = (from, to, kind) => {
|
|
13
|
+
if (from === to)
|
|
14
|
+
return;
|
|
15
|
+
const key = `${from}>${to}`;
|
|
16
|
+
if (seen.has(key))
|
|
17
|
+
return;
|
|
18
|
+
seen.add(key);
|
|
19
|
+
edges.push({ from, to, color: edgeColorFor(kind) });
|
|
20
|
+
};
|
|
21
|
+
model.slices.forEach((slice, i) => {
|
|
22
|
+
const uis = slice.elements.filter((e) => e.kind === "ui");
|
|
23
|
+
const command = slice.elements.find((e) => e.kind === "command");
|
|
24
|
+
const view = slice.elements.find((e) => e.kind === "view");
|
|
25
|
+
const events = slice.elements.filter((e) => e.kind === "event");
|
|
26
|
+
const auto = slice.elements.find((e) => AUTOMATION_KINDS.has(e.kind));
|
|
27
|
+
// Input pattern: UI -> command -> event(s)
|
|
28
|
+
if (command) {
|
|
29
|
+
for (const ui of uis)
|
|
30
|
+
add(ui.id, command.id, "ui");
|
|
31
|
+
for (const ev of events)
|
|
32
|
+
add(command.id, ev.id, "command");
|
|
33
|
+
}
|
|
34
|
+
// Automation/translation: it reads the read model in its own slice, then
|
|
35
|
+
// triggers the command in the next slice.
|
|
36
|
+
if (auto) {
|
|
37
|
+
if (view)
|
|
38
|
+
add(view.id, auto.id, "view");
|
|
39
|
+
const nextCommand = model.slices[i + 1]?.elements.find((e) => e.kind === "command");
|
|
40
|
+
if (nextCommand)
|
|
41
|
+
add(auto.id, nextCommand.id, auto.kind);
|
|
42
|
+
else
|
|
43
|
+
for (const ev of events)
|
|
44
|
+
add(auto.id, ev.id, auto.kind);
|
|
45
|
+
}
|
|
46
|
+
// Output pattern: view -> UI (read model feeds the screen)
|
|
47
|
+
if (view) {
|
|
48
|
+
for (const ui of uis)
|
|
49
|
+
add(view.id, ui.id, "view");
|
|
50
|
+
if ((view.from ?? []).length === 0) {
|
|
51
|
+
for (const ev of events)
|
|
52
|
+
add(ev.id, view.id, "event");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// Cross-slice `from` wiring:
|
|
57
|
+
// view from event(s) -> event -> view
|
|
58
|
+
// automation from read model(s) -> view -> automation
|
|
59
|
+
for (const el of model.elements) {
|
|
60
|
+
for (const name of el.from ?? []) {
|
|
61
|
+
const bucket = model.byName.get(normalizeName(name));
|
|
62
|
+
if (!bucket)
|
|
63
|
+
continue;
|
|
64
|
+
const src = el.kind === "view"
|
|
65
|
+
? bucket.find((x) => x.kind === "event") ?? bucket[0]
|
|
66
|
+
: bucket.find((x) => x.kind === "view") ?? bucket[0];
|
|
67
|
+
if (src)
|
|
68
|
+
add(src.id, el.id, src.kind);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Explicit arrows from the DSL.
|
|
72
|
+
for (const a of model.arrows) {
|
|
73
|
+
if (a.fromId && a.toId)
|
|
74
|
+
add(a.fromId, a.toId, model.byId.get(a.fromId)?.kind);
|
|
75
|
+
}
|
|
76
|
+
return edges;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=edges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edges.js","sourceRoot":"","sources":["../../src/model/edges.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ElementKind, Field, ModelNode } from "../parser/ast.js";
|
|
2
|
+
export declare const DEFAULT_PERSONA = "User";
|
|
3
|
+
export declare const DEFAULT_CONTEXT = "Domain";
|
|
4
|
+
export interface Element {
|
|
5
|
+
id: string;
|
|
6
|
+
kind: ElementKind;
|
|
7
|
+
name: string;
|
|
8
|
+
/** Resolved persona lane (ui only). */
|
|
9
|
+
persona?: string;
|
|
10
|
+
/** Resolved context lane (event only). */
|
|
11
|
+
context?: string;
|
|
12
|
+
/** Source event names (view only). */
|
|
13
|
+
from?: string[];
|
|
14
|
+
/** Markdown file holding this element's notes, relative to the .em file. */
|
|
15
|
+
note?: string;
|
|
16
|
+
/** Data attributes declared on the element. */
|
|
17
|
+
fields?: Field[];
|
|
18
|
+
sliceIndex: number;
|
|
19
|
+
line: number;
|
|
20
|
+
}
|
|
21
|
+
export interface Slice {
|
|
22
|
+
name: string;
|
|
23
|
+
index: number;
|
|
24
|
+
elements: Element[];
|
|
25
|
+
line: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ResolvedArrow {
|
|
28
|
+
from: string;
|
|
29
|
+
to: string;
|
|
30
|
+
fromId?: string;
|
|
31
|
+
toId?: string;
|
|
32
|
+
line: number;
|
|
33
|
+
}
|
|
34
|
+
export interface NormalizedModel {
|
|
35
|
+
name: string;
|
|
36
|
+
personas: string[];
|
|
37
|
+
contexts: string[];
|
|
38
|
+
hasAutomation: boolean;
|
|
39
|
+
slices: Slice[];
|
|
40
|
+
elements: Element[];
|
|
41
|
+
byId: Map<string, Element>;
|
|
42
|
+
/** Normalized display name -> elements with that name (across slices). */
|
|
43
|
+
byName: Map<string, Element[]>;
|
|
44
|
+
arrows: ResolvedArrow[];
|
|
45
|
+
}
|
|
46
|
+
export declare function normalizeName(name: string): string;
|
|
47
|
+
export declare function normalize(ast: ModelNode): NormalizedModel;
|
|
48
|
+
/** Resolve an arrow endpoint (given by display name) to an element id. */
|
|
49
|
+
export declare function resolveByName(byName: Map<string, Element[]>, name: string): string | undefined;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Normalizes a parsed AST into a resolved model: stable ids, resolved
|
|
3
|
+
// persona/context lanes, and lookup indexes used by layout and validation.
|
|
4
|
+
import { AUTOMATION_KINDS } from "../parser/ast.js";
|
|
5
|
+
export const DEFAULT_PERSONA = "User";
|
|
6
|
+
export const DEFAULT_CONTEXT = "Domain";
|
|
7
|
+
export function normalizeName(name) {
|
|
8
|
+
return name.trim().toLowerCase().replace(/\s+/g, " ");
|
|
9
|
+
}
|
|
10
|
+
function slug(name) {
|
|
11
|
+
const s = name
|
|
12
|
+
.trim()
|
|
13
|
+
.toLowerCase()
|
|
14
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
15
|
+
.replace(/^_+|_+$/g, "");
|
|
16
|
+
return s || "n";
|
|
17
|
+
}
|
|
18
|
+
export function normalize(ast) {
|
|
19
|
+
const personas = [...ast.personas];
|
|
20
|
+
const contexts = [...ast.contexts];
|
|
21
|
+
const slices = [];
|
|
22
|
+
const elements = [];
|
|
23
|
+
const byId = new Map();
|
|
24
|
+
const byName = new Map();
|
|
25
|
+
const usedIds = new Set();
|
|
26
|
+
let hasAutomation = false;
|
|
27
|
+
const makeId = (name) => {
|
|
28
|
+
const base = slug(name);
|
|
29
|
+
let id = base;
|
|
30
|
+
let n = 2;
|
|
31
|
+
while (usedIds.has(id))
|
|
32
|
+
id = `${base}_${n++}`;
|
|
33
|
+
usedIds.add(id);
|
|
34
|
+
return id;
|
|
35
|
+
};
|
|
36
|
+
ast.slices.forEach((sliceNode, sliceIndex) => {
|
|
37
|
+
const slice = {
|
|
38
|
+
name: sliceNode.name,
|
|
39
|
+
index: sliceIndex,
|
|
40
|
+
elements: [],
|
|
41
|
+
line: sliceNode.line,
|
|
42
|
+
};
|
|
43
|
+
for (const el of sliceNode.elements) {
|
|
44
|
+
const element = {
|
|
45
|
+
id: makeId(el.name),
|
|
46
|
+
kind: el.kind,
|
|
47
|
+
name: el.name,
|
|
48
|
+
sliceIndex,
|
|
49
|
+
line: el.line,
|
|
50
|
+
from: el.from,
|
|
51
|
+
note: el.note,
|
|
52
|
+
fields: el.fields,
|
|
53
|
+
};
|
|
54
|
+
if (el.kind === "ui") {
|
|
55
|
+
const persona = el.persona ?? personas[0] ?? DEFAULT_PERSONA;
|
|
56
|
+
if (!personas.includes(persona))
|
|
57
|
+
personas.push(persona);
|
|
58
|
+
element.persona = persona;
|
|
59
|
+
}
|
|
60
|
+
else if (el.kind === "event") {
|
|
61
|
+
const context = el.context ?? contexts[0] ?? DEFAULT_CONTEXT;
|
|
62
|
+
if (!contexts.includes(context))
|
|
63
|
+
contexts.push(context);
|
|
64
|
+
element.context = context;
|
|
65
|
+
}
|
|
66
|
+
else if (AUTOMATION_KINDS.has(el.kind)) {
|
|
67
|
+
hasAutomation = true;
|
|
68
|
+
}
|
|
69
|
+
slice.elements.push(element);
|
|
70
|
+
elements.push(element);
|
|
71
|
+
byId.set(element.id, element);
|
|
72
|
+
const key = normalizeName(element.name);
|
|
73
|
+
const bucket = byName.get(key);
|
|
74
|
+
if (bucket)
|
|
75
|
+
bucket.push(element);
|
|
76
|
+
else
|
|
77
|
+
byName.set(key, [element]);
|
|
78
|
+
}
|
|
79
|
+
slices.push(slice);
|
|
80
|
+
});
|
|
81
|
+
if (personas.length === 0)
|
|
82
|
+
personas.push(DEFAULT_PERSONA);
|
|
83
|
+
if (contexts.length === 0)
|
|
84
|
+
contexts.push(DEFAULT_CONTEXT);
|
|
85
|
+
const arrows = ast.arrows.map((a) => ({
|
|
86
|
+
from: a.from,
|
|
87
|
+
to: a.to,
|
|
88
|
+
fromId: resolveByName(byName, a.from),
|
|
89
|
+
toId: resolveByName(byName, a.to),
|
|
90
|
+
line: a.line,
|
|
91
|
+
}));
|
|
92
|
+
return {
|
|
93
|
+
name: ast.name,
|
|
94
|
+
personas,
|
|
95
|
+
contexts,
|
|
96
|
+
hasAutomation,
|
|
97
|
+
slices,
|
|
98
|
+
elements,
|
|
99
|
+
byId,
|
|
100
|
+
byName,
|
|
101
|
+
arrows,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Resolve an arrow endpoint (given by display name) to an element id. */
|
|
105
|
+
export function resolveByName(byName, name) {
|
|
106
|
+
const bucket = byName.get(normalizeName(name));
|
|
107
|
+
return bucket && bucket.length > 0 ? bucket[0].id : undefined;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/model/model.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Grid } from "../layout/grid.js";
|
|
2
|
+
import { Element, NormalizedModel } from "./model.js";
|
|
3
|
+
export type Severity = "error" | "warning";
|
|
4
|
+
export interface Diagnostic {
|
|
5
|
+
severity: Severity;
|
|
6
|
+
message: string;
|
|
7
|
+
line?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function validate(model: NormalizedModel, grid: Grid): Diagnostic[];
|
|
10
|
+
export declare function hasErrors(diags: Diagnostic[]): boolean;
|
|
11
|
+
/** Pretty one-line diagnostic for terminal output. */
|
|
12
|
+
export declare function formatDiagnostic(d: Diagnostic): string;
|
|
13
|
+
export type { Element };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Event-modeling rule checks over the normalized model + grid.
|
|
3
|
+
import { AUTOMATION_KINDS } from "../parser/ast.js";
|
|
4
|
+
import { normalizeName } from "./model.js";
|
|
5
|
+
export function validate(model, grid) {
|
|
6
|
+
const diags = [];
|
|
7
|
+
// Grid collisions: two elements of the same band landed in one slice cell.
|
|
8
|
+
for (const c of grid.collisions) {
|
|
9
|
+
diags.push({
|
|
10
|
+
severity: "error",
|
|
11
|
+
message: `"${c.dropped.name}" collides with "${c.kept.name}" in the same ` +
|
|
12
|
+
`slice/lane (${c.rowKey}); split them into separate slices`,
|
|
13
|
+
line: c.dropped.line,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
for (const slice of model.slices) {
|
|
17
|
+
const command = slice.elements.find((e) => e.kind === "command");
|
|
18
|
+
const events = slice.elements.filter((e) => e.kind === "event");
|
|
19
|
+
const views = slice.elements.filter((e) => e.kind === "view");
|
|
20
|
+
const auto = slice.elements.find((e) => AUTOMATION_KINDS.has(e.kind));
|
|
21
|
+
// An automation/translation slice holds only the read model + the
|
|
22
|
+
// automation; the command it triggers belongs in the next slice.
|
|
23
|
+
if (auto && command) {
|
|
24
|
+
diags.push({
|
|
25
|
+
severity: "warning",
|
|
26
|
+
message: `${auto.kind} "${auto.name}" shares slice "${slice.name}" with command ` +
|
|
27
|
+
`"${command.name}"; put the triggered command in the next slice`,
|
|
28
|
+
line: command.line,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// A command should record at least one event.
|
|
32
|
+
if (command && events.length === 0) {
|
|
33
|
+
diags.push({
|
|
34
|
+
severity: "warning",
|
|
35
|
+
message: `command "${command.name}" produces no event in slice "${slice.name}"`,
|
|
36
|
+
line: command.line,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// A read model must derive from at least one event.
|
|
40
|
+
for (const view of views) {
|
|
41
|
+
const hasFrom = (view.from ?? []).length > 0;
|
|
42
|
+
if (!hasFrom && events.length === 0) {
|
|
43
|
+
diags.push({
|
|
44
|
+
severity: "warning",
|
|
45
|
+
message: `read model "${view.name}" has no source event ` +
|
|
46
|
+
`(add \`from "Event"\` or place it in a slice with an event)`,
|
|
47
|
+
line: view.line,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
for (const src of view.from ?? []) {
|
|
51
|
+
const bucket = model.byName.get(normalizeName(src));
|
|
52
|
+
const evt = bucket?.find((e) => e.kind === "event");
|
|
53
|
+
if (!evt) {
|
|
54
|
+
diags.push({
|
|
55
|
+
severity: "error",
|
|
56
|
+
message: `read model "${view.name}" references unknown event "${src}"`,
|
|
57
|
+
line: view.line,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Automation/translation consumes a read model via `from`.
|
|
64
|
+
for (const el of model.elements) {
|
|
65
|
+
if (!AUTOMATION_KINDS.has(el.kind))
|
|
66
|
+
continue;
|
|
67
|
+
for (const src of el.from ?? []) {
|
|
68
|
+
const bucket = model.byName.get(normalizeName(src));
|
|
69
|
+
if (!bucket?.some((e) => e.kind === "view")) {
|
|
70
|
+
diags.push({
|
|
71
|
+
severity: "error",
|
|
72
|
+
message: `${el.kind} "${el.name}" references unknown read model "${src}"`,
|
|
73
|
+
line: el.line,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Explicit arrow endpoints must resolve.
|
|
79
|
+
for (const a of model.arrows) {
|
|
80
|
+
if (!a.fromId)
|
|
81
|
+
diags.push({
|
|
82
|
+
severity: "error",
|
|
83
|
+
message: `arrow source "${a.from}" does not match any element`,
|
|
84
|
+
line: a.line,
|
|
85
|
+
});
|
|
86
|
+
if (!a.toId)
|
|
87
|
+
diags.push({
|
|
88
|
+
severity: "error",
|
|
89
|
+
message: `arrow target "${a.to}" does not match any element`,
|
|
90
|
+
line: a.line,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Ambiguous names (used by arrows / view sources) get a heads-up.
|
|
94
|
+
for (const [key, els] of model.byName) {
|
|
95
|
+
if (els.length > 1 && isReferenced(model, key)) {
|
|
96
|
+
diags.push({
|
|
97
|
+
severity: "warning",
|
|
98
|
+
message: `name "${els[0].name}" is defined ${els.length} times; ` +
|
|
99
|
+
`references resolve to the first occurrence`,
|
|
100
|
+
line: els[0].line,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return diags;
|
|
105
|
+
}
|
|
106
|
+
function isReferenced(model, key) {
|
|
107
|
+
for (const a of model.arrows) {
|
|
108
|
+
if (normalizeName(a.from) === key || normalizeName(a.to) === key)
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
for (const el of model.elements) {
|
|
112
|
+
for (const f of el.from ?? [])
|
|
113
|
+
if (normalizeName(f) === key)
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
export function hasErrors(diags) {
|
|
119
|
+
return diags.some((d) => d.severity === "error");
|
|
120
|
+
}
|
|
121
|
+
/** Pretty one-line diagnostic for terminal output. */
|
|
122
|
+
export function formatDiagnostic(d) {
|
|
123
|
+
const where = d.line ? `:${d.line}` : "";
|
|
124
|
+
const tag = d.severity === "error" ? "error" : "warn ";
|
|
125
|
+
return ` ${tag}${where} ${d.message}`;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/model/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type ElementKind = "ui" | "command" | "view" | "event" | "automation" | "processor" | "saga" | "translation";
|
|
2
|
+
/** Element keywords that live in the automation band (top). */
|
|
3
|
+
export declare const AUTOMATION_KINDS: ReadonlySet<ElementKind>;
|
|
4
|
+
/** A data attribute on an element, e.g. `total: Money`. */
|
|
5
|
+
export interface Field {
|
|
6
|
+
name: string;
|
|
7
|
+
/** Optional type annotation after a `:`. */
|
|
8
|
+
type?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ElementNode {
|
|
11
|
+
kind: ElementKind;
|
|
12
|
+
name: string;
|
|
13
|
+
/** @Persona tag — only meaningful for `ui`. */
|
|
14
|
+
persona?: string;
|
|
15
|
+
/** @Context tag — only meaningful for `event`. */
|
|
16
|
+
context?: string;
|
|
17
|
+
/** `from "Event", "Event2"` — only meaningful for `view`. */
|
|
18
|
+
from?: string[];
|
|
19
|
+
/** `note "path.md"` — markdown file holding this element's notes. */
|
|
20
|
+
note?: string;
|
|
21
|
+
/** Data attributes declared in a `{ … }` block on the element. */
|
|
22
|
+
fields?: Field[];
|
|
23
|
+
line: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SliceNode {
|
|
26
|
+
name: string;
|
|
27
|
+
elements: ElementNode[];
|
|
28
|
+
line: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ArrowNode {
|
|
31
|
+
from: string;
|
|
32
|
+
to: string;
|
|
33
|
+
line: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ModelNode {
|
|
36
|
+
name: string;
|
|
37
|
+
/** Declared persona lanes, in order. */
|
|
38
|
+
personas: string[];
|
|
39
|
+
/** Declared context/concept lanes, in order. */
|
|
40
|
+
contexts: string[];
|
|
41
|
+
slices: SliceNode[];
|
|
42
|
+
/** Explicit top-level arrows (e.g. read-model feedback to a UI). */
|
|
43
|
+
arrows: ArrowNode[];
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Abstract syntax tree produced by the parser.
|
|
3
|
+
/** Element keywords that live in the automation band (top). */
|
|
4
|
+
export const AUTOMATION_KINDS = new Set([
|
|
5
|
+
"automation",
|
|
6
|
+
"processor",
|
|
7
|
+
"saga",
|
|
8
|
+
"translation",
|
|
9
|
+
]);
|
|
10
|
+
//# sourceMappingURL=ast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/parser/ast.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,+CAA+C;AAY/C,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAA6B,IAAI,GAAG,CAAC;IAChE,YAAY;IACZ,WAAW;IACX,MAAM;IACN,aAAa;CACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Remove a trailing `# comment`, ignoring `#` inside double quotes. */
|
|
2
|
+
export declare function stripComment(line: string): string;
|
|
3
|
+
/** Strip a single pair of surrounding double quotes, if present. */
|
|
4
|
+
export declare function unquote(s: string): string;
|
|
5
|
+
/** Split a comma-separated list, honouring quotes, returning trimmed unquoted items. */
|
|
6
|
+
export declare function splitQuotedList(s: string): string[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Small lexing helpers shared by the line-oriented parser.
|
|
3
|
+
/** Remove a trailing `# comment`, ignoring `#` inside double quotes. */
|
|
4
|
+
export function stripComment(line) {
|
|
5
|
+
let inQuote = false;
|
|
6
|
+
for (let i = 0; i < line.length; i++) {
|
|
7
|
+
const c = line[i];
|
|
8
|
+
if (c === '"')
|
|
9
|
+
inQuote = !inQuote;
|
|
10
|
+
else if (c === "#" && !inQuote)
|
|
11
|
+
return line.slice(0, i);
|
|
12
|
+
}
|
|
13
|
+
return line;
|
|
14
|
+
}
|
|
15
|
+
/** Strip a single pair of surrounding double quotes, if present. */
|
|
16
|
+
export function unquote(s) {
|
|
17
|
+
const t = s.trim();
|
|
18
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
|
|
19
|
+
return t.slice(1, -1);
|
|
20
|
+
}
|
|
21
|
+
return t;
|
|
22
|
+
}
|
|
23
|
+
/** Split a comma-separated list, honouring quotes, returning trimmed unquoted items. */
|
|
24
|
+
export function splitQuotedList(s) {
|
|
25
|
+
const items = [];
|
|
26
|
+
let cur = "";
|
|
27
|
+
let inQuote = false;
|
|
28
|
+
for (let i = 0; i < s.length; i++) {
|
|
29
|
+
const c = s[i];
|
|
30
|
+
if (c === '"') {
|
|
31
|
+
inQuote = !inQuote;
|
|
32
|
+
cur += c;
|
|
33
|
+
}
|
|
34
|
+
else if (c === "," && !inQuote) {
|
|
35
|
+
items.push(unquote(cur));
|
|
36
|
+
cur = "";
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
cur += c;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (cur.trim().length > 0)
|
|
43
|
+
items.push(unquote(cur));
|
|
44
|
+
return items.filter((x) => x.length > 0);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=lexer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexer.js","sourceRoot":"","sources":["../../src/parser/lexer.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,2DAA2D;AAE3D,wEAAwE;AACxE,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,GAAG,CAAC,OAAO,CAAC;aAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACnB,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,eAAe,CAAC,CAAS;IACvC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACf,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,OAAO,GAAG,CAAC,OAAO,CAAC;YACnB,GAAG,IAAI,CAAC,CAAC;QACX,CAAC;aAAM,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,GAAG,GAAG,EAAE,CAAC;QACX,CAAC;aAAM,CAAC;YACN,GAAG,IAAI,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC3C,CAAC"}
|