@miragon/event-storming-dsl 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/dist/index.cjs ADDED
@@ -0,0 +1,682 @@
1
+ 'use strict';
2
+
3
+ var eventStormingSchemaModel = require('@miragon/event-storming-schema-model');
4
+
5
+ // src/parser.ts
6
+
7
+ // src/lexer.ts
8
+ var COORDS_RE = /\[\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\]/;
9
+ function parseCoords(line) {
10
+ const m = COORDS_RE.exec(line);
11
+ if (!m) return null;
12
+ const a = Number(m[1]);
13
+ const b = Number(m[2]);
14
+ if (Number.isNaN(a) || Number.isNaN(b)) return null;
15
+ return { a, b };
16
+ }
17
+ function splitAtCoords(line) {
18
+ const m = COORDS_RE.exec(line);
19
+ if (!m) return null;
20
+ const a = Number(m[1]);
21
+ const b = Number(m[2]);
22
+ if (Number.isNaN(a) || Number.isNaN(b)) return null;
23
+ return {
24
+ name: line.slice(0, m.index).trim(),
25
+ coords: { a, b },
26
+ suffix: line.slice(m.index + m[0].length)
27
+ };
28
+ }
29
+ var MULTI_COORDS_RE = /\[\s{0,8}(\[[^[\]]*\](?:\s{0,8},\s{0,8}\[[^[\]]*\])+)\s{0,8}\]/;
30
+ function parseMultiCoords(line) {
31
+ const m = MULTI_COORDS_RE.exec(line);
32
+ if (!m) return null;
33
+ const tuples = [];
34
+ const inner = /\[\s{0,8}([-\d.]+)\s{0,8},\s{0,8}([-\d.]+)\s{0,8}\]/g;
35
+ let t;
36
+ while (t = inner.exec(m[1])) {
37
+ const a = Number(t[1]);
38
+ const b = Number(t[2]);
39
+ if (!Number.isNaN(a) && !Number.isNaN(b)) tuples.push({ a, b });
40
+ }
41
+ if (!tuples.length) return null;
42
+ return { tuples, rest: line.replace(MULTI_COORDS_RE, " ") };
43
+ }
44
+ function splitLineComment(line) {
45
+ let inQuote = false;
46
+ for (let i = 0; i < line.length - 1; i++) {
47
+ const ch = line[i];
48
+ if (ch === "'") inQuote = !inQuote;
49
+ else if (!inQuote && ch === "/" && line[i + 1] === "/" && line[i - 1] !== ":") {
50
+ return { code: line.slice(0, i), comment: line.slice(i) };
51
+ }
52
+ }
53
+ return { code: line, comment: null };
54
+ }
55
+ function indexOfOutsideQuotes(line, needle) {
56
+ let inQuote = false;
57
+ for (let i = 0; i + needle.length <= line.length; i++) {
58
+ const ch = line[i];
59
+ if (ch === "'") {
60
+ inQuote = !inQuote;
61
+ continue;
62
+ }
63
+ if (!inQuote && line.startsWith(needle, i)) return i;
64
+ }
65
+ return -1;
66
+ }
67
+ var COLOR_RE = /\(\s*color\s+(#[0-9a-fA-F]{3,8}|[a-zA-Z][\w-]*)\s*\)/i;
68
+ function parseColor(line) {
69
+ const m = COLOR_RE.exec(line);
70
+ if (!m || !m[1]) return { rest: line };
71
+ return { color: m[1], rest: line.replace(COLOR_RE, " ") };
72
+ }
73
+ var ON_RE = /\(\s*on\s+/i;
74
+ function parseOn(line) {
75
+ const m = ON_RE.exec(line);
76
+ if (!m) return { rest: line };
77
+ const start = m.index + m[0].length;
78
+ const close = line.lastIndexOf(")");
79
+ if (close < start) return { rest: line };
80
+ const host = line.slice(start, close).trim();
81
+ if (!host) return { rest: line };
82
+ return { host, rest: `${line.slice(0, m.index)} ${line.slice(close + 1)}` };
83
+ }
84
+ var ID_CHARSET_RE = /^[A-Za-z0-9_-]+$/;
85
+ var ID_RE = /\(\s*id\s+([A-Za-z0-9_-]+)\s*\)/i;
86
+ var ID_PRESENT_RE = /\(\s*id\b[^)]*\)/i;
87
+ function parseId(line) {
88
+ const m = ID_RE.exec(line);
89
+ if (m?.[1]) return { id: m[1], rest: line.replace(ID_RE, " ") };
90
+ const p = ID_PRESENT_RE.exec(line);
91
+ if (!p) return { rest: line };
92
+ return { invalid: p[0], rest: line.replace(ID_PRESENT_RE, " ") };
93
+ }
94
+ var SIZE_RE = /\(\s*size\s+([\d.]+)\s*x\s*([\d.]+)\s*\)/i;
95
+ var SIZE_PRESENT_RE = /\(\s*size\b[^)]*\)/i;
96
+ function parseSize(line) {
97
+ const m = SIZE_RE.exec(line);
98
+ if (m) {
99
+ const width = Number(m[1]);
100
+ const height = Number(m[2]);
101
+ if (width > 0 && height > 0) {
102
+ return { size: { width, height }, rest: line.replace(SIZE_RE, " ") };
103
+ }
104
+ }
105
+ const p = SIZE_PRESENT_RE.exec(line);
106
+ if (!p) return { rest: line };
107
+ return { invalid: p[0], rest: line.replace(SIZE_PRESENT_RE, " ") };
108
+ }
109
+ var ALIGN_RE = /\(\s*align\s+(left|center|right)\s+(top|middle|bottom)\s*\)/i;
110
+ var ALIGN_PRESENT_RE = /\(\s*align\b[^)]*\)/i;
111
+ function parseAlign(line) {
112
+ const m = ALIGN_RE.exec(line);
113
+ if (m) {
114
+ return {
115
+ align: {
116
+ horizontal: m[1].toLowerCase(),
117
+ vertical: m[2].toLowerCase()
118
+ },
119
+ rest: line.replace(ALIGN_RE, " ")
120
+ };
121
+ }
122
+ const p = ALIGN_PRESENT_RE.exec(line);
123
+ if (!p) return { rest: line };
124
+ return { invalid: p[0], rest: line.replace(ALIGN_PRESENT_RE, " ") };
125
+ }
126
+ function keywordOf(line) {
127
+ const m = /^\s*([A-Za-z][\w-]*)/.exec(line);
128
+ return m ? m[1].toLowerCase() : "";
129
+ }
130
+ function slug(label) {
131
+ return label.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_/, "").replace(/_$/, "") || "x";
132
+ }
133
+
134
+ // src/parser.ts
135
+ function splitArrow(core) {
136
+ const arrow = core.indexOf("->");
137
+ if (arrow <= 0) return null;
138
+ const left = core.slice(0, arrow).trim();
139
+ const right = core.slice(arrow + 2).trim();
140
+ return left && right ? { left, right } : null;
141
+ }
142
+ var KNOWN_STYLES = /* @__PURE__ */ new Set(["classic", "dark"]);
143
+ var ATTACHABLE_KINDS = new Set(eventStormingSchemaModel.ATTACHABLE_STICKY_KINDS);
144
+ var HOST_KINDS = new Set(eventStormingSchemaModel.HOST_STICKY_KINDS);
145
+ 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
+ var STICKY_KINDS = new Set(Object.keys(STICKY_ID_PREFIXES));
160
+ function compact(obj) {
161
+ const out = {};
162
+ for (const [k, v] of Object.entries(obj)) if (v !== void 0) out[k] = v;
163
+ return out;
164
+ }
165
+ function modelAlign(align) {
166
+ if (!align) return void 0;
167
+ const out = compact({
168
+ horizontal: align.horizontal === "left" ? void 0 : align.horizontal,
169
+ vertical: align.vertical === "top" ? void 0 : align.vertical
170
+ });
171
+ return Object.keys(out).length ? out : void 0;
172
+ }
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
+ function parseDSL(text) {
191
+ return parseDSLWithDiagnostics(text).board;
192
+ }
193
+ function parseDSLWithDiagnostics(text) {
194
+ const diagnostics = [];
195
+ const ids = new IdAllocator();
196
+ const nameToId = /* @__PURE__ */ new Map();
197
+ const elements = [];
198
+ const rawPassthrough = [];
199
+ const pendingArrows = [];
200
+ const pendingAttachments = [];
201
+ let config = { title: "Untitled Board" };
202
+ let inBlockComment = false;
203
+ const register = (name, id) => {
204
+ if (!nameToId.has(name)) nameToId.set(name, id);
205
+ };
206
+ let lineNo = 0;
207
+ let currentLine = "";
208
+ const pushArrow = (line) => {
209
+ const semi = line.indexOf(";");
210
+ const core = semi >= 0 ? line.slice(0, semi).trim() : line;
211
+ const arrowLabel = semi >= 0 ? line.slice(semi + 1).trim() : "";
212
+ const arrow = splitArrow(core);
213
+ if (!arrow) return false;
214
+ pendingArrows.push({
215
+ left: arrow.left,
216
+ right: arrow.right,
217
+ ...arrowLabel ? { label: arrowLabel } : {},
218
+ raw: line,
219
+ lineNo
220
+ });
221
+ return true;
222
+ };
223
+ const diag = (message, atLine = lineNo, text_ = currentLine) => diagnostics.push({ line: atLine, message, text: text_ });
224
+ const failed = (l) => {
225
+ rawPassthrough.push(l);
226
+ diag("Line could not be interpreted (kept losslessly in rawPassthrough)");
227
+ };
228
+ const pos = (x, y) => ({ x, y });
229
+ const sourceLines = text.split(/\r?\n/);
230
+ for (let i = 0; i < sourceLines.length; i++) {
231
+ const raw = sourceLines[i];
232
+ lineNo = i + 1;
233
+ currentLine = raw.trim();
234
+ let working = raw;
235
+ if (inBlockComment) {
236
+ const close = working.indexOf("*/");
237
+ if (close < 0) {
238
+ rawPassthrough.push(raw);
239
+ continue;
240
+ }
241
+ rawPassthrough.push(working.slice(0, close + 2));
242
+ working = working.slice(close + 2);
243
+ inBlockComment = false;
244
+ }
245
+ let open = indexOfOutsideQuotes(working, "/*");
246
+ while (open >= 0) {
247
+ const close = working.indexOf("*/", open + 2);
248
+ if (close < 0) {
249
+ rawPassthrough.push(working.slice(open));
250
+ working = working.slice(0, open);
251
+ inBlockComment = true;
252
+ break;
253
+ }
254
+ rawPassthrough.push(working.slice(open, close + 2));
255
+ working = `${working.slice(0, open)} ${working.slice(close + 2)}`;
256
+ open = indexOfOutsideQuotes(working, "/*");
257
+ }
258
+ const { code, comment } = splitLineComment(working);
259
+ if (comment !== null) rawPassthrough.push(comment);
260
+ working = code;
261
+ const line = working.trim();
262
+ if (!line) continue;
263
+ const kw = keywordOf(line);
264
+ const after = line.slice(kw.length).trim();
265
+ const semi = line.indexOf(";");
266
+ const beforeAnnotation = semi >= 0 ? line.slice(0, semi) : line;
267
+ const titleAsArrow = () => {
268
+ const arrow = splitArrow(beforeAnnotation.trim());
269
+ return arrow !== null && nameToId.has(decodeName(arrow.left));
270
+ };
271
+ if ((kw !== "title" || titleAsArrow()) && !parseCoords(beforeAnnotation) && pushArrow(line)) {
272
+ continue;
273
+ }
274
+ switch (kw) {
275
+ case "title":
276
+ config = { ...config, title: after };
277
+ break;
278
+ case "style": {
279
+ const s = after.toLowerCase();
280
+ if (KNOWN_STYLES.has(s)) config = { ...config, style: s };
281
+ else failed(line);
282
+ break;
283
+ }
284
+ case "level": {
285
+ const l = after.toLowerCase();
286
+ if (KNOWN_LEVELS.has(l)) config = { ...config, level: l };
287
+ else failed(line);
288
+ break;
289
+ }
290
+ case "event":
291
+ case "command":
292
+ case "actor":
293
+ case "aggregate":
294
+ case "policy":
295
+ case "readmodel":
296
+ case "external":
297
+ case "hotspot": {
298
+ const node = parseSticky(after);
299
+ if (!node) {
300
+ failed(line);
301
+ break;
302
+ }
303
+ if (node.idInvalid) {
304
+ diag(
305
+ `Id: could not read "${node.idInvalid.trim()}" \u2014 expected (id <id>) with letters, digits, '_' or '-'`
306
+ );
307
+ }
308
+ const explicit = node.id !== void 0 && ids.claim(node.id) ? node.id : void 0;
309
+ if (node.id !== void 0 && explicit === void 0) {
310
+ diag(`Id: "${node.id}" is already taken \u2014 the element got a fresh id`);
311
+ }
312
+ const id = explicit ?? ids.alloc(STICKY_ID_PREFIXES[kw], node.name);
313
+ elements.push(
314
+ compact({
315
+ id,
316
+ elementType: kw,
317
+ label: node.name,
318
+ position: pos(node.coords.x, node.coords.y),
319
+ color: node.color
320
+ })
321
+ );
322
+ register(node.name, id);
323
+ if (node.host !== void 0) {
324
+ if (ATTACHABLE_KINDS.has(kw)) {
325
+ pendingAttachments.push({
326
+ index: elements.length - 1,
327
+ host: node.host,
328
+ raw: line,
329
+ lineNo
330
+ });
331
+ } else {
332
+ diag(`Attachment: a ${kw} cannot be pinned \u2014 only actor/hotspot/note support (on \u2026)`);
333
+ }
334
+ }
335
+ if (node.sizeSuffix) {
336
+ diag(`Size: a ${kw} cannot be resized \u2014 only notes support (size \u2026)`);
337
+ }
338
+ if (node.alignSuffix) {
339
+ diag(`Align: a ${kw} cannot be aligned \u2014 only notes support (align \u2026)`);
340
+ }
341
+ break;
342
+ }
343
+ case "note": {
344
+ const split = splitAtCoords(after);
345
+ if (!split && after.includes("[")) {
346
+ failed(line);
347
+ break;
348
+ }
349
+ const on = parseOn(split ? split.suffix : after);
350
+ const al = parseAlign(on.rest);
351
+ const sz = parseSize(al.rest);
352
+ const idp = parseId(sz.rest);
353
+ const col = parseColor(idp.rest);
354
+ if (al.invalid) {
355
+ diag(
356
+ `Align: could not read "${al.invalid.trim()}" \u2014 expected (align left|center|right top|middle|bottom)`
357
+ );
358
+ }
359
+ if (sz.invalid) {
360
+ diag(
361
+ `Size: could not read "${sz.invalid.trim()}" \u2014 expected (size <w>x<h>) with positive numbers`
362
+ );
363
+ }
364
+ if (idp.id !== void 0 || idp.invalid) {
365
+ diag("Id: a note cannot be referenced \u2014 only sticky kinds support (id \u2026)");
366
+ }
367
+ const textPart = decodeName((split ? split.name : col.rest).trim());
368
+ const id = ids.alloc("note", textPart || "note");
369
+ const note = compact({
370
+ id,
371
+ elementType: "note",
372
+ label: textPart,
373
+ position: split ? pos(split.coords.a, split.coords.b) : pos(0, 0),
374
+ color: col.color,
375
+ size: sz.size,
376
+ align: modelAlign(al.align)
377
+ });
378
+ elements.push(note);
379
+ if (on.host !== void 0) {
380
+ pendingAttachments.push({ index: elements.length - 1, host: on.host, raw: line, lineNo });
381
+ }
382
+ break;
383
+ }
384
+ case "line": {
385
+ const col = parseColor(after);
386
+ const multi = parseMultiCoords(col.rest);
387
+ if (!multi || multi.tuples.length < 2) {
388
+ failed(line);
389
+ break;
390
+ }
391
+ const onDrawing = parseOn(multi.rest);
392
+ if (onDrawing.host !== void 0) {
393
+ diag("Attachment: a drawing cannot be pinned \u2014 only actor/hotspot/note support (on \u2026)");
394
+ }
395
+ const szDrawing = parseSize(onDrawing.rest);
396
+ if (szDrawing.size || szDrawing.invalid) {
397
+ diag("Size: a drawing cannot be resized \u2014 only notes support (size \u2026)");
398
+ }
399
+ const alDrawing = parseAlign(onDrawing.rest);
400
+ if (alDrawing.align || alDrawing.invalid) {
401
+ diag("Align: a drawing cannot be aligned \u2014 only notes support (align \u2026)");
402
+ }
403
+ const idDrawing = parseId(onDrawing.rest);
404
+ if (idDrawing.id !== void 0 || idDrawing.invalid) {
405
+ diag("Id: a drawing cannot be referenced \u2014 only sticky kinds support (id \u2026)");
406
+ }
407
+ const flags = onDrawing.rest.toLowerCase();
408
+ const strokeStyle = flags.includes("(dashed)") ? "dashed" : flags.includes("(dotted)") ? "dotted" : void 0;
409
+ const points = multi.tuples.map((t) => pos(t.a, t.b));
410
+ const drawing = {
411
+ id: ids.alloc("draw", "line"),
412
+ elementType: "drawing",
413
+ label: "",
414
+ position: points[0],
415
+ points,
416
+ ...flags.includes("(closed)") ? { closed: true } : {},
417
+ ...strokeStyle ? { strokeStyle } : {},
418
+ ...col.color ? { color: col.color } : {}
419
+ };
420
+ elements.push(drawing);
421
+ break;
422
+ }
423
+ default: {
424
+ if (!pushArrow(line)) rawPassthrough.push(line);
425
+ }
426
+ }
427
+ }
428
+ const resolve = (token) => {
429
+ if (token.startsWith("#")) {
430
+ const id2 = token.slice(1);
431
+ return elements.find((e) => e.id === id2);
432
+ }
433
+ const id = nameToId.get(decodeName(token));
434
+ return id !== void 0 ? elements.find((e) => e.id === id) : void 0;
435
+ };
436
+ for (const att of pendingAttachments) {
437
+ const host = resolve(att.host);
438
+ if (!host) {
439
+ diag(`Attachment: "${att.host}" not found`, att.lineNo, att.raw);
440
+ continue;
441
+ }
442
+ if (!HOST_KINDS.has(host.elementType)) {
443
+ diag(
444
+ `Attachment: "${att.host}" is a ${host.elementType} \u2014 actors/hotspots/notes may only attach to host stickies`,
445
+ att.lineNo,
446
+ att.raw
447
+ );
448
+ continue;
449
+ }
450
+ elements[att.index] = { ...elements[att.index], attachedTo: host.id };
451
+ }
452
+ let arrowN = 0;
453
+ const elementIds = new Set(elements.map((e) => e.id));
454
+ const nextArrowId = () => {
455
+ let id = `arrow_${++arrowN}`;
456
+ while (elementIds.has(id)) id = `arrow_${++arrowN}`;
457
+ return id;
458
+ };
459
+ const edges = [];
460
+ for (const arrow of pendingArrows) {
461
+ const from = resolve(arrow.left);
462
+ const to = resolve(arrow.right);
463
+ if (!from || !to) {
464
+ rawPassthrough.push(arrow.raw);
465
+ diag(
466
+ `Arrow: ${!from ? `"${arrow.left}"` : `"${arrow.right}"`} not found`,
467
+ arrow.lineNo,
468
+ arrow.raw
469
+ );
470
+ continue;
471
+ }
472
+ const nonSticky = !STICKY_KINDS.has(from.elementType) ? [arrow.left, from] : !STICKY_KINDS.has(to.elementType) ? [arrow.right, to] : void 0;
473
+ if (nonSticky) {
474
+ rawPassthrough.push(arrow.raw);
475
+ diag(
476
+ `Arrow: "${nonSticky[0]}" is a ${nonSticky[1].elementType} \u2014 arrows may only connect stickies`,
477
+ arrow.lineNo,
478
+ arrow.raw
479
+ );
480
+ continue;
481
+ }
482
+ edges.push(
483
+ compact({
484
+ id: nextArrowId(),
485
+ edgeType: "arrow",
486
+ from: from.id,
487
+ to: to.id,
488
+ label: arrow.label
489
+ })
490
+ );
491
+ }
492
+ const board = compact({
493
+ schemaVersion: eventStormingSchemaModel.CURRENT_SCHEMA_VERSION,
494
+ config,
495
+ elements,
496
+ edges,
497
+ rawPassthrough: rawPassthrough.length ? rawPassthrough : void 0
498
+ });
499
+ return { board: eventStormingSchemaModel.validateBoard(board), diagnostics };
500
+ }
501
+ function parseSticky(after) {
502
+ const split = splitAtCoords(after);
503
+ if (split) {
504
+ if (!split.name) return null;
505
+ const on2 = parseOn(split.suffix);
506
+ const idp2 = parseId(on2.rest);
507
+ const al2 = parseAlign(idp2.rest);
508
+ const sz2 = parseSize(al2.rest);
509
+ const col2 = parseColor(sz2.rest);
510
+ return compact({
511
+ name: decodeName(split.name),
512
+ coords: { x: split.coords.a, y: split.coords.b },
513
+ color: col2.color ?? void 0,
514
+ host: on2.host,
515
+ id: idp2.id,
516
+ idInvalid: idp2.invalid,
517
+ sizeSuffix: sz2.size || sz2.invalid ? true : void 0,
518
+ alignSuffix: al2.align || al2.invalid ? true : void 0
519
+ });
520
+ }
521
+ if (after.includes("[")) return null;
522
+ const on = parseOn(after);
523
+ const idp = parseId(on.rest);
524
+ const al = parseAlign(idp.rest);
525
+ const sz = parseSize(al.rest);
526
+ const col = parseColor(sz.rest);
527
+ const name = col.rest.trim();
528
+ if (!name) return null;
529
+ return compact({
530
+ name: decodeName(name),
531
+ coords: { x: 0, y: 0 },
532
+ color: col.color ?? void 0,
533
+ host: on.host,
534
+ id: idp.id,
535
+ idInvalid: idp.invalid,
536
+ sizeSuffix: sz.size || sz.invalid ? true : void 0,
537
+ alignSuffix: al.align || al.invalid ? true : void 0
538
+ });
539
+ }
540
+
541
+ // src/serializer.ts
542
+ function r(n) {
543
+ const v = Math.abs(n) < 1e21 ? Math.round(n * 1e3) / 1e3 : n;
544
+ return Math.abs(v) < 1e21 ? String(v) : BigInt(v).toString();
545
+ }
546
+ function escapeText(label) {
547
+ return label.trim().replace(/\n/g, "\\n").replace(/(?<!:)\/\//g, "\u2215\u2215").replace(/\/\*/g, "\u2215*");
548
+ }
549
+ function escapeName(label) {
550
+ return escapeText(label).replace(/->/g, "\u2192");
551
+ }
552
+ var NAMED_TYPES = /* @__PURE__ */ new Set([
553
+ "event",
554
+ "command",
555
+ "actor",
556
+ "aggregate",
557
+ "policy",
558
+ "readmodel",
559
+ "external",
560
+ "hotspot"
561
+ ]);
562
+ var DEFAULT_NAMES = {
563
+ event: "Domain Event",
564
+ command: "Command",
565
+ actor: "Actor",
566
+ aggregate: "Aggregate",
567
+ policy: "Policy",
568
+ readmodel: "Read Model",
569
+ external: "External System",
570
+ hotspot: "Hotspot"
571
+ };
572
+ function defaultName(type) {
573
+ return DEFAULT_NAMES[type] ?? "Sticky";
574
+ }
575
+ function serializedNames(board) {
576
+ const byId = /* @__PURE__ */ new Map();
577
+ for (const el of board.elements) {
578
+ if (!NAMED_TYPES.has(el.elementType)) continue;
579
+ byId.set(el.id, escapeName(el.label) || defaultName(el.elementType));
580
+ }
581
+ return byId;
582
+ }
583
+ function colorSuffix(el) {
584
+ return el.color ? ` (color ${el.color})` : "";
585
+ }
586
+ function alignSuffix(el) {
587
+ const horizontal = el.align?.horizontal ?? "left";
588
+ const vertical = el.align?.vertical ?? "top";
589
+ return horizontal === "left" && vertical === "top" ? "" : ` (align ${horizontal} ${vertical})`;
590
+ }
591
+ function serializeDSL(board) {
592
+ const lines = [];
593
+ const names = serializedNames(board);
594
+ const nameOf = (el) => names.get(el.id) ?? el.label;
595
+ const nameCount = /* @__PURE__ */ new Map();
596
+ for (const name of names.values()) nameCount.set(name, (nameCount.get(name) ?? 0) + 1);
597
+ const needsId = (id) => {
598
+ const name = names.get(id);
599
+ return name !== void 0 && (nameCount.get(name) > 1 || name.startsWith("#"));
600
+ };
601
+ const emitIds = /* @__PURE__ */ new Map();
602
+ const taken = new Set(board.elements.map((e) => e.id));
603
+ for (const el of board.elements) {
604
+ if (!needsId(el.id) || ID_CHARSET_RE.test(el.id)) continue;
605
+ let candidate = slug(el.id);
606
+ for (let i = 2; taken.has(candidate); i++) candidate = `${slug(el.id)}_${i}`;
607
+ taken.add(candidate);
608
+ emitIds.set(el.id, candidate);
609
+ }
610
+ const idOf = (id) => emitIds.get(id) ?? id;
611
+ const ref = (id) => needsId(id) ? `#${idOf(id)}` : names.get(id) ?? id;
612
+ lines.push(`title ${board.config.title}`);
613
+ if (board.config.style) lines.push(`style ${board.config.style}`);
614
+ if (board.config.level) lines.push(`level ${board.config.level}`);
615
+ const onSuffix = (el) => {
616
+ const hostId = el.elementType === "actor" || el.elementType === "hotspot" || el.elementType === "note" ? el.attachedTo : void 0;
617
+ return hostId ? ` (on ${ref(hostId)})` : "";
618
+ };
619
+ for (const el of board.elements) {
620
+ const idSuffix = needsId(el.id) ? ` (id ${idOf(el.id)})` : "";
621
+ lines.push(elementLine(el, nameOf(el), idSuffix, onSuffix(el)));
622
+ }
623
+ for (const edge of board.edges) {
624
+ const annotation = edge.label ? `; ${edge.label}` : "";
625
+ lines.push(`${ref(edge.from)} -> ${ref(edge.to)}${annotation}`);
626
+ }
627
+ if (board.rawPassthrough) {
628
+ const emitted = /* @__PURE__ */ new Set(["title"]);
629
+ if (board.config.style) emitted.add("style");
630
+ if (board.config.level) emitted.add("level");
631
+ for (const raw of board.rawPassthrough) {
632
+ if (emitted.has(raw.trim().split(/\s+/)[0])) continue;
633
+ lines.push(raw);
634
+ }
635
+ }
636
+ return lines.join("\n") + "\n";
637
+ }
638
+ function elementLine(el, name, idSuffix, attach) {
639
+ const p = el.position;
640
+ switch (el.elementType) {
641
+ case "event":
642
+ case "command":
643
+ case "actor":
644
+ case "aggregate":
645
+ case "policy":
646
+ case "readmodel":
647
+ case "external":
648
+ case "hotspot":
649
+ return `${el.elementType} ${name} [${r(p.x)}, ${r(p.y)}]${colorSuffix(el)}${idSuffix}${attach}`;
650
+ case "note": {
651
+ const size = el.size ? ` (size ${r(el.size.width)}x${r(el.size.height)})` : "";
652
+ return `note ${escapeText(name)} [${r(p.x)}, ${r(p.y)}]${colorSuffix(el)}${size}${alignSuffix(el)}${attach}`;
653
+ }
654
+ case "drawing": {
655
+ const pts = el.points.map((q) => `[${r(q.x)}, ${r(q.y)}]`).join(", ");
656
+ const closed = el.closed ? " (closed)" : "";
657
+ const stroke = el.strokeStyle && el.strokeStyle !== "solid" ? ` (${el.strokeStyle})` : "";
658
+ return `line [${pts}]${closed}${stroke}${colorSuffix(el)}`;
659
+ }
660
+ }
661
+ }
662
+
663
+ Object.defineProperty(exports, "boardFromJSON", {
664
+ enumerable: true,
665
+ get: function () { return eventStormingSchemaModel.parseBoardJSON; }
666
+ });
667
+ Object.defineProperty(exports, "boardToJSON", {
668
+ enumerable: true,
669
+ get: function () { return eventStormingSchemaModel.serializeBoard; }
670
+ });
671
+ Object.defineProperty(exports, "loadBoard", {
672
+ enumerable: true,
673
+ get: function () { return eventStormingSchemaModel.loadBoard; }
674
+ });
675
+ exports.keywordOf = keywordOf;
676
+ exports.parseCoords = parseCoords;
677
+ exports.parseDSL = parseDSL;
678
+ exports.parseDSLWithDiagnostics = parseDSLWithDiagnostics;
679
+ exports.serializeDSL = serializeDSL;
680
+ exports.slug = slug;
681
+ //# sourceMappingURL=index.cjs.map
682
+ //# sourceMappingURL=index.cjs.map