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