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