@miragon/event-storming-dsl 0.2.2 → 0.3.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/dist/index.cjs +102 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +102 -55
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,12 +5,19 @@ export { parseBoardJSON as boardFromJSON, serializeBoard as boardToJSON, loadBoa
|
|
|
5
5
|
|
|
6
6
|
// src/lexer.ts
|
|
7
7
|
var COORDS_RE = /\[\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\]/;
|
|
8
|
+
var LARGEST_DRAWABLE_NUMBER = 1e38;
|
|
9
|
+
function isDrawable(value) {
|
|
10
|
+
return Math.abs(value) <= LARGEST_DRAWABLE_NUMBER;
|
|
11
|
+
}
|
|
12
|
+
function drawablePair(a, b) {
|
|
13
|
+
return isDrawable(a) && isDrawable(b);
|
|
14
|
+
}
|
|
8
15
|
function parseCoords(line) {
|
|
9
16
|
const m = COORDS_RE.exec(line);
|
|
10
17
|
if (!m) return null;
|
|
11
18
|
const a = Number(m[1]);
|
|
12
19
|
const b = Number(m[2]);
|
|
13
|
-
if (
|
|
20
|
+
if (!drawablePair(a, b)) return null;
|
|
14
21
|
return { a, b };
|
|
15
22
|
}
|
|
16
23
|
function splitAtCoords(line) {
|
|
@@ -18,7 +25,7 @@ function splitAtCoords(line) {
|
|
|
18
25
|
if (!m) return null;
|
|
19
26
|
const a = Number(m[1]);
|
|
20
27
|
const b = Number(m[2]);
|
|
21
|
-
if (
|
|
28
|
+
if (!drawablePair(a, b)) return null;
|
|
22
29
|
return {
|
|
23
30
|
name: line.slice(0, m.index).trim(),
|
|
24
31
|
coords: { a, b },
|
|
@@ -35,7 +42,8 @@ function parseMultiCoords(line) {
|
|
|
35
42
|
while (t = inner.exec(m[1])) {
|
|
36
43
|
const a = Number(t[1]);
|
|
37
44
|
const b = Number(t[2]);
|
|
38
|
-
if (!
|
|
45
|
+
if (!drawablePair(a, b)) return null;
|
|
46
|
+
tuples.push({ a, b });
|
|
39
47
|
}
|
|
40
48
|
if (!tuples.length) return null;
|
|
41
49
|
return { tuples, rest: line.replace(MULTI_COORDS_RE, " ") };
|
|
@@ -97,7 +105,7 @@ function parseSize(line) {
|
|
|
97
105
|
if (m) {
|
|
98
106
|
const width = Number(m[1]);
|
|
99
107
|
const height = Number(m[2]);
|
|
100
|
-
if (width > 0 && height > 0) {
|
|
108
|
+
if (width > 0 && height > 0 && isDrawable(width) && isDrawable(height)) {
|
|
101
109
|
return { size: { width, height }, rest: line.replace(SIZE_RE, " ") };
|
|
102
110
|
}
|
|
103
111
|
}
|
|
@@ -122,6 +130,9 @@ function parseAlign(line) {
|
|
|
122
130
|
if (!p) return { rest: line };
|
|
123
131
|
return { invalid: p[0], rest: line.replace(ALIGN_PRESENT_RE, " ") };
|
|
124
132
|
}
|
|
133
|
+
function decodeName(name) {
|
|
134
|
+
return name.replace(/\\n/g, "\n");
|
|
135
|
+
}
|
|
125
136
|
function keywordOf(line) {
|
|
126
137
|
const m = /^\s*([A-Za-z][\w-]*)/.exec(line);
|
|
127
138
|
return m ? m[1].toLowerCase() : "";
|
|
@@ -130,6 +141,44 @@ function slug(label) {
|
|
|
130
141
|
return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_/, "").replace(/_$/, "") || "x";
|
|
131
142
|
}
|
|
132
143
|
|
|
144
|
+
// src/ids.ts
|
|
145
|
+
var STICKY_ID_PREFIXES = {
|
|
146
|
+
event: "event",
|
|
147
|
+
command: "cmd",
|
|
148
|
+
actor: "actor",
|
|
149
|
+
aggregate: "agg",
|
|
150
|
+
policy: "policy",
|
|
151
|
+
readmodel: "read",
|
|
152
|
+
external: "ext",
|
|
153
|
+
hotspot: "hot"
|
|
154
|
+
};
|
|
155
|
+
var ENGINE_ID_RE = /^(?:event-storming-root$|__implicitroot)/;
|
|
156
|
+
function isEngineId(id) {
|
|
157
|
+
return ENGINE_ID_RE.test(id);
|
|
158
|
+
}
|
|
159
|
+
var IdAllocator = class {
|
|
160
|
+
used = /* @__PURE__ */ new Set();
|
|
161
|
+
/** Explicit `(id …)` — false when the id is taken or reserved (caller reports + allocates). */
|
|
162
|
+
claim(id) {
|
|
163
|
+
if (this.used.has(id) || isEngineId(id)) return false;
|
|
164
|
+
this.used.add(id);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
/** The id `alloc(prefix, label)` would return, without taking it. */
|
|
168
|
+
peek(prefix, label) {
|
|
169
|
+
const base = `${prefix}_${slug(label)}`;
|
|
170
|
+
let id = base;
|
|
171
|
+
let i = 2;
|
|
172
|
+
while (this.used.has(id)) id = `${base}_${i++}`;
|
|
173
|
+
return id;
|
|
174
|
+
}
|
|
175
|
+
alloc(prefix, label) {
|
|
176
|
+
const id = this.peek(prefix, label);
|
|
177
|
+
this.used.add(id);
|
|
178
|
+
return id;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
133
182
|
// src/parser.ts
|
|
134
183
|
function splitArrow(core) {
|
|
135
184
|
const arrow = core.indexOf("->");
|
|
@@ -142,19 +191,6 @@ var KNOWN_STYLES = /* @__PURE__ */ new Set(["classic", "dark"]);
|
|
|
142
191
|
var ATTACHABLE_KINDS = new Set(ATTACHABLE_STICKY_KINDS);
|
|
143
192
|
var HOST_KINDS = new Set(HOST_STICKY_KINDS);
|
|
144
193
|
var KNOWN_LEVELS = /* @__PURE__ */ new Set(["big-picture", "process", "design"]);
|
|
145
|
-
function decodeName(name) {
|
|
146
|
-
return name.replace(/\\n/g, "\n");
|
|
147
|
-
}
|
|
148
|
-
var STICKY_ID_PREFIXES = {
|
|
149
|
-
event: "event",
|
|
150
|
-
command: "cmd",
|
|
151
|
-
actor: "actor",
|
|
152
|
-
aggregate: "agg",
|
|
153
|
-
policy: "policy",
|
|
154
|
-
readmodel: "read",
|
|
155
|
-
external: "ext",
|
|
156
|
-
hotspot: "hot"
|
|
157
|
-
};
|
|
158
194
|
var STICKY_KINDS = new Set(Object.keys(STICKY_ID_PREFIXES));
|
|
159
195
|
function compact(obj) {
|
|
160
196
|
const out = {};
|
|
@@ -169,23 +205,6 @@ function modelAlign(align) {
|
|
|
169
205
|
});
|
|
170
206
|
return Object.keys(out).length ? out : void 0;
|
|
171
207
|
}
|
|
172
|
-
var IdAllocator = class {
|
|
173
|
-
used = /* @__PURE__ */ new Set();
|
|
174
|
-
/** Explicit `(id …)` — false when the id is already taken (caller reports + allocates). */
|
|
175
|
-
claim(id) {
|
|
176
|
-
if (this.used.has(id)) return false;
|
|
177
|
-
this.used.add(id);
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
alloc(prefix, label) {
|
|
181
|
-
const base = `${prefix}_${slug(label)}`;
|
|
182
|
-
let id = base;
|
|
183
|
-
let i = 2;
|
|
184
|
-
while (this.used.has(id)) id = `${base}_${i++}`;
|
|
185
|
-
this.used.add(id);
|
|
186
|
-
return id;
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
208
|
function parseDSL(text) {
|
|
190
209
|
return parseDSLWithDiagnostics(text).board;
|
|
191
210
|
}
|
|
@@ -222,12 +241,13 @@ function parseDSLWithDiagnostics(text) {
|
|
|
222
241
|
const diag = (message, atLine = lineNo, text_ = currentLine) => diagnostics.push({ line: atLine, message, text: text_ });
|
|
223
242
|
const failed = (l) => {
|
|
224
243
|
rawPassthrough.push(l);
|
|
225
|
-
diag("
|
|
244
|
+
diag("Couldn't read this line. It stays in the file unchanged.");
|
|
226
245
|
};
|
|
227
246
|
const pos = (x, y) => ({ x, y });
|
|
228
247
|
const sourceLines = text.split(/\r?\n/);
|
|
229
248
|
for (let i = 0; i < sourceLines.length; i++) {
|
|
230
249
|
const raw = sourceLines[i];
|
|
250
|
+
if (raw === "" && i === sourceLines.length - 1) continue;
|
|
231
251
|
lineNo = i + 1;
|
|
232
252
|
currentLine = raw.trim();
|
|
233
253
|
let working = raw;
|
|
@@ -301,12 +321,14 @@ function parseDSLWithDiagnostics(text) {
|
|
|
301
321
|
}
|
|
302
322
|
if (node.idInvalid) {
|
|
303
323
|
diag(
|
|
304
|
-
`
|
|
324
|
+
`Couldn't read the id \u201C${node.idInvalid.trim()}\u201D. An id has letters, digits, \u201C_\u201D or \u201C-\u201D, as in (id order_1).`
|
|
305
325
|
);
|
|
306
326
|
}
|
|
307
327
|
const explicit = node.id !== void 0 && ids.claim(node.id) ? node.id : void 0;
|
|
308
328
|
if (node.id !== void 0 && explicit === void 0) {
|
|
309
|
-
diag(
|
|
329
|
+
diag(
|
|
330
|
+
isEngineId(node.id) ? `The id \u201C${node.id}\u201D belongs to the board itself, so this sticky got a new id.` : `The id \u201C${node.id}\u201D is already taken, so this sticky got a new id.`
|
|
331
|
+
);
|
|
310
332
|
}
|
|
311
333
|
const id = explicit ?? ids.alloc(STICKY_ID_PREFIXES[kw], node.name);
|
|
312
334
|
elements.push(
|
|
@@ -328,14 +350,14 @@ function parseDSLWithDiagnostics(text) {
|
|
|
328
350
|
lineNo
|
|
329
351
|
});
|
|
330
352
|
} else {
|
|
331
|
-
diag(`
|
|
353
|
+
diag(`A ${kw} can't be pinned. Only an actor, a hotspot or a note takes (on \u2026).`);
|
|
332
354
|
}
|
|
333
355
|
}
|
|
334
356
|
if (node.sizeSuffix) {
|
|
335
|
-
diag(`
|
|
357
|
+
diag(`A ${kw} can't be resized. Only a note takes (size \u2026).`);
|
|
336
358
|
}
|
|
337
359
|
if (node.alignSuffix) {
|
|
338
|
-
diag(`
|
|
360
|
+
diag(`A ${kw} can't be aligned. Only a note takes (align \u2026).`);
|
|
339
361
|
}
|
|
340
362
|
break;
|
|
341
363
|
}
|
|
@@ -352,16 +374,16 @@ function parseDSLWithDiagnostics(text) {
|
|
|
352
374
|
const col = parseColor(idp.rest);
|
|
353
375
|
if (al.invalid) {
|
|
354
376
|
diag(
|
|
355
|
-
`
|
|
377
|
+
`Couldn't read the alignment \u201C${al.invalid.trim()}\u201D. Write (align left|center|right top|middle|bottom).`
|
|
356
378
|
);
|
|
357
379
|
}
|
|
358
380
|
if (sz.invalid) {
|
|
359
381
|
diag(
|
|
360
|
-
`
|
|
382
|
+
`Couldn't read the size \u201C${sz.invalid.trim()}\u201D. Write (size <w>x<h>) with positive numbers.`
|
|
361
383
|
);
|
|
362
384
|
}
|
|
363
385
|
if (idp.id !== void 0 || idp.invalid) {
|
|
364
|
-
diag("
|
|
386
|
+
diag("A note can't have an id. Only a sticky takes (id \u2026).");
|
|
365
387
|
}
|
|
366
388
|
const textPart = decodeName((split ? split.name : col.rest).trim());
|
|
367
389
|
const id = ids.alloc("note", textPart || "note");
|
|
@@ -389,19 +411,19 @@ function parseDSLWithDiagnostics(text) {
|
|
|
389
411
|
}
|
|
390
412
|
const onDrawing = parseOn(multi.rest);
|
|
391
413
|
if (onDrawing.host !== void 0) {
|
|
392
|
-
diag("
|
|
414
|
+
diag("A drawing can't be pinned. Only an actor, a hotspot or a note takes (on \u2026).");
|
|
393
415
|
}
|
|
394
416
|
const szDrawing = parseSize(onDrawing.rest);
|
|
395
417
|
if (szDrawing.size || szDrawing.invalid) {
|
|
396
|
-
diag("
|
|
418
|
+
diag("A drawing can't be resized. Only a note takes (size \u2026).");
|
|
397
419
|
}
|
|
398
420
|
const alDrawing = parseAlign(onDrawing.rest);
|
|
399
421
|
if (alDrawing.align || alDrawing.invalid) {
|
|
400
|
-
diag("
|
|
422
|
+
diag("A drawing can't be aligned. Only a note takes (align \u2026).");
|
|
401
423
|
}
|
|
402
424
|
const idDrawing = parseId(onDrawing.rest);
|
|
403
425
|
if (idDrawing.id !== void 0 || idDrawing.invalid) {
|
|
404
|
-
diag("
|
|
426
|
+
diag("A drawing can't have an id. Only a sticky takes (id \u2026).");
|
|
405
427
|
}
|
|
406
428
|
const flags = onDrawing.rest.toLowerCase();
|
|
407
429
|
const strokeStyle = flags.includes("(dashed)") ? "dashed" : flags.includes("(dotted)") ? "dotted" : void 0;
|
|
@@ -435,12 +457,12 @@ function parseDSLWithDiagnostics(text) {
|
|
|
435
457
|
for (const att of pendingAttachments) {
|
|
436
458
|
const host = resolve(att.host);
|
|
437
459
|
if (!host) {
|
|
438
|
-
diag(`
|
|
460
|
+
diag(`There's no \u201C${att.host}\u201D on the board to pin this to.`, att.lineNo, att.raw);
|
|
439
461
|
continue;
|
|
440
462
|
}
|
|
441
463
|
if (!HOST_KINDS.has(host.elementType)) {
|
|
442
464
|
diag(
|
|
443
|
-
|
|
465
|
+
`\u201C${att.host}\u201D is a ${host.elementType}. An actor, a hotspot or a note is pinned to an event, a command, an aggregate, a policy, a read model or an external system.`,
|
|
444
466
|
att.lineNo,
|
|
445
467
|
att.raw
|
|
446
468
|
);
|
|
@@ -462,7 +484,7 @@ function parseDSLWithDiagnostics(text) {
|
|
|
462
484
|
if (!from || !to) {
|
|
463
485
|
rawPassthrough.push(arrow.raw);
|
|
464
486
|
diag(
|
|
465
|
-
|
|
487
|
+
!from ? `This arrow starts at \u201C${arrow.left}\u201D, which isn't on the board.` : `This arrow points to \u201C${arrow.right}\u201D, which isn't on the board.`,
|
|
466
488
|
arrow.lineNo,
|
|
467
489
|
arrow.raw
|
|
468
490
|
);
|
|
@@ -472,7 +494,7 @@ function parseDSLWithDiagnostics(text) {
|
|
|
472
494
|
if (nonSticky) {
|
|
473
495
|
rawPassthrough.push(arrow.raw);
|
|
474
496
|
diag(
|
|
475
|
-
|
|
497
|
+
`\u201C${nonSticky[0]}\u201D is a ${nonSticky[1].elementType}, and an arrow only connects stickies.`,
|
|
476
498
|
arrow.lineNo,
|
|
477
499
|
arrow.raw
|
|
478
500
|
);
|
|
@@ -593,21 +615,22 @@ function serializeDSL(board) {
|
|
|
593
615
|
const nameOf = (el) => names.get(el.id) ?? el.label;
|
|
594
616
|
const nameCount = /* @__PURE__ */ new Map();
|
|
595
617
|
for (const name of names.values()) nameCount.set(name, (nameCount.get(name) ?? 0) + 1);
|
|
596
|
-
const
|
|
618
|
+
const referencedById = (id) => {
|
|
597
619
|
const name = names.get(id);
|
|
598
620
|
return name !== void 0 && (nameCount.get(name) > 1 || name.startsWith("#"));
|
|
599
621
|
};
|
|
600
622
|
const emitIds = /* @__PURE__ */ new Map();
|
|
601
623
|
const taken = new Set(board.elements.map((e) => e.id));
|
|
602
624
|
for (const el of board.elements) {
|
|
603
|
-
if (!
|
|
625
|
+
if (!referencedById(el.id) || ID_CHARSET_RE.test(el.id)) continue;
|
|
604
626
|
let candidate = slug(el.id);
|
|
605
627
|
for (let i = 2; taken.has(candidate); i++) candidate = `${slug(el.id)}_${i}`;
|
|
606
628
|
taken.add(candidate);
|
|
607
629
|
emitIds.set(el.id, candidate);
|
|
608
630
|
}
|
|
609
631
|
const idOf = (id) => emitIds.get(id) ?? id;
|
|
610
|
-
const
|
|
632
|
+
const declaredIds = idsToDeclare(board, nameOf, referencedById, idOf);
|
|
633
|
+
const ref = (id) => referencedById(id) ? `#${idOf(id)}` : names.get(id) ?? id;
|
|
611
634
|
lines.push(`title ${board.config.title}`);
|
|
612
635
|
if (board.config.style) lines.push(`style ${board.config.style}`);
|
|
613
636
|
if (board.config.level) lines.push(`level ${board.config.level}`);
|
|
@@ -616,7 +639,7 @@ function serializeDSL(board) {
|
|
|
616
639
|
return hostId ? ` (on ${ref(hostId)})` : "";
|
|
617
640
|
};
|
|
618
641
|
for (const el of board.elements) {
|
|
619
|
-
const idSuffix =
|
|
642
|
+
const idSuffix = declaredIds.has(el.id) ? ` (id ${idOf(el.id)})` : "";
|
|
620
643
|
lines.push(elementLine(el, nameOf(el), idSuffix, onSuffix(el)));
|
|
621
644
|
}
|
|
622
645
|
for (const edge of board.edges) {
|
|
@@ -634,6 +657,30 @@ function serializeDSL(board) {
|
|
|
634
657
|
}
|
|
635
658
|
return lines.join("\n") + "\n";
|
|
636
659
|
}
|
|
660
|
+
function idsToDeclare(board, nameOf, referencedById, emittedId) {
|
|
661
|
+
const allocator = new IdAllocator();
|
|
662
|
+
const declared = /* @__PURE__ */ new Set();
|
|
663
|
+
for (const el of board.elements) {
|
|
664
|
+
const prefix = STICKY_ID_PREFIXES[el.elementType];
|
|
665
|
+
if (prefix === void 0) {
|
|
666
|
+
allocator.alloc(el.elementType === "note" ? "note" : "draw", noteOrDrawingName(el));
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const parsedName = decodeName(nameOf(el));
|
|
670
|
+
const declare = referencedById(el.id) || ID_CHARSET_RE.test(el.id) && allocator.peek(prefix, parsedName) !== el.id;
|
|
671
|
+
if (declare) {
|
|
672
|
+
declared.add(el.id);
|
|
673
|
+
allocator.claim(emittedId(el.id));
|
|
674
|
+
} else {
|
|
675
|
+
allocator.alloc(prefix, parsedName);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return declared;
|
|
679
|
+
}
|
|
680
|
+
function noteOrDrawingName(el) {
|
|
681
|
+
if (el.elementType === "drawing") return "line";
|
|
682
|
+
return decodeName(escapeText(el.label)) || "note";
|
|
683
|
+
}
|
|
637
684
|
function elementLine(el, name, idSuffix, attach) {
|
|
638
685
|
const p = el.position;
|
|
639
686
|
switch (el.elementType) {
|