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