@bpmnkit/core 0.0.8

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.
Files changed (104) hide show
  1. package/README.md +185 -0
  2. package/dist/bpmn/auto-layout.d.ts +10 -0
  3. package/dist/bpmn/auto-layout.js +203 -0
  4. package/dist/bpmn/bpmn-builder.d.ts +381 -0
  5. package/dist/bpmn/bpmn-builder.js +1223 -0
  6. package/dist/bpmn/bpmn-model.d.ts +403 -0
  7. package/dist/bpmn/bpmn-model.js +2 -0
  8. package/dist/bpmn/bpmn-parser.d.ts +4 -0
  9. package/dist/bpmn/bpmn-parser.js +642 -0
  10. package/dist/bpmn/bpmn-serializer.d.ts +4 -0
  11. package/dist/bpmn/bpmn-serializer.js +515 -0
  12. package/dist/bpmn/compact.d.ts +91 -0
  13. package/dist/bpmn/compact.js +349 -0
  14. package/dist/bpmn/di-color.d.ts +9 -0
  15. package/dist/bpmn/di-color.js +32 -0
  16. package/dist/bpmn/index.d.ts +105 -0
  17. package/dist/bpmn/index.js +176 -0
  18. package/dist/bpmn/optimize/feel.d.ts +4 -0
  19. package/dist/bpmn/optimize/feel.js +186 -0
  20. package/dist/bpmn/optimize/flow.d.ts +4 -0
  21. package/dist/bpmn/optimize/flow.js +323 -0
  22. package/dist/bpmn/optimize/index.d.ts +5 -0
  23. package/dist/bpmn/optimize/index.js +51 -0
  24. package/dist/bpmn/optimize/naming.d.ts +8 -0
  25. package/dist/bpmn/optimize/naming.js +145 -0
  26. package/dist/bpmn/optimize/tasks.d.ts +4 -0
  27. package/dist/bpmn/optimize/tasks.js +273 -0
  28. package/dist/bpmn/optimize/types.d.ts +44 -0
  29. package/dist/bpmn/optimize/types.js +2 -0
  30. package/dist/bpmn/optimize/utils.d.ts +23 -0
  31. package/dist/bpmn/optimize/utils.js +129 -0
  32. package/dist/bpmn/rest-connector.d.ts +48 -0
  33. package/dist/bpmn/rest-connector.js +69 -0
  34. package/dist/bpmn/svg.d.ts +35 -0
  35. package/dist/bpmn/svg.js +557 -0
  36. package/dist/bpmn/type-guards.d.ts +136 -0
  37. package/dist/bpmn/type-guards.js +211 -0
  38. package/dist/bpmn/utils.d.ts +88 -0
  39. package/dist/bpmn/utils.js +207 -0
  40. package/dist/bpmn/zeebe-extensions.d.ts +72 -0
  41. package/dist/bpmn/zeebe-extensions.js +96 -0
  42. package/dist/dmn/compact.d.ts +44 -0
  43. package/dist/dmn/compact.js +125 -0
  44. package/dist/dmn/dmn-builder.d.ts +59 -0
  45. package/dist/dmn/dmn-builder.js +144 -0
  46. package/dist/dmn/dmn-layout.d.ts +57 -0
  47. package/dist/dmn/dmn-layout.js +256 -0
  48. package/dist/dmn/dmn-model.d.ts +158 -0
  49. package/dist/dmn/dmn-model.js +2 -0
  50. package/dist/dmn/dmn-parser.d.ts +4 -0
  51. package/dist/dmn/dmn-parser.js +275 -0
  52. package/dist/dmn/dmn-serializer.d.ts +4 -0
  53. package/dist/dmn/dmn-serializer.js +296 -0
  54. package/dist/dmn/index.d.ts +28 -0
  55. package/dist/dmn/index.js +51 -0
  56. package/dist/errors.d.ts +77 -0
  57. package/dist/errors.js +66 -0
  58. package/dist/form/compact.d.ts +20 -0
  59. package/dist/form/compact.js +146 -0
  60. package/dist/form/form-builder.d.ts +111 -0
  61. package/dist/form/form-builder.js +347 -0
  62. package/dist/form/form-model.d.ts +221 -0
  63. package/dist/form/form-model.js +2 -0
  64. package/dist/form/form-parser.d.ts +4 -0
  65. package/dist/form/form-parser.js +514 -0
  66. package/dist/form/form-serializer.d.ts +4 -0
  67. package/dist/form/form-serializer.js +240 -0
  68. package/dist/form/index.d.ts +27 -0
  69. package/dist/form/index.js +43 -0
  70. package/dist/index.d.ts +37 -0
  71. package/dist/index.js +19 -0
  72. package/dist/layout/bench.d.ts +86 -0
  73. package/dist/layout/bench.js +225 -0
  74. package/dist/layout/coordinates.d.ts +62 -0
  75. package/dist/layout/coordinates.js +868 -0
  76. package/dist/layout/crossing.d.ts +8 -0
  77. package/dist/layout/crossing.js +60 -0
  78. package/dist/layout/graph.d.ts +28 -0
  79. package/dist/layout/graph.js +126 -0
  80. package/dist/layout/index.d.ts +7 -0
  81. package/dist/layout/index.js +5 -0
  82. package/dist/layout/layers.d.ts +13 -0
  83. package/dist/layout/layers.js +49 -0
  84. package/dist/layout/layout-engine.d.ts +21 -0
  85. package/dist/layout/layout-engine.js +191 -0
  86. package/dist/layout/overlap.d.ts +10 -0
  87. package/dist/layout/overlap.js +70 -0
  88. package/dist/layout/routing.d.ts +26 -0
  89. package/dist/layout/routing.js +439 -0
  90. package/dist/layout/subprocess.d.ts +14 -0
  91. package/dist/layout/subprocess.js +77 -0
  92. package/dist/layout/types.d.ts +72 -0
  93. package/dist/layout/types.js +37 -0
  94. package/dist/types/id-generator.d.ts +8 -0
  95. package/dist/types/id-generator.js +31 -0
  96. package/dist/types/index.d.ts +3 -0
  97. package/dist/types/index.js +2 -0
  98. package/dist/types/xml-element.d.ts +12 -0
  99. package/dist/types/xml-element.js +2 -0
  100. package/dist/xml/index.d.ts +2 -0
  101. package/dist/xml/index.js +2 -0
  102. package/dist/xml/xml-parser.d.ts +13 -0
  103. package/dist/xml/xml-parser.js +271 -0
  104. package/package.json +38 -0
@@ -0,0 +1,225 @@
1
+ import { Bpmn } from "../bpmn/index.js";
2
+ import { layoutProcess } from "./layout-engine.js";
3
+ // ── Reference extraction ──────────────────────────────────────────────────────
4
+ /**
5
+ * Extracts element positions from the BPMN DI data (the hand-crafted or
6
+ * tool-generated layout stored in the XML file).
7
+ */
8
+ export function parseReferenceLayout(defs) {
9
+ const process = defs.processes[0];
10
+ const diagram = defs.diagrams[0];
11
+ if (!process || !diagram)
12
+ return [];
13
+ // Build a name lookup from flow elements
14
+ const names = new Map();
15
+ const types = new Map();
16
+ const collectNames = (elements) => {
17
+ for (const el of elements) {
18
+ names.set(el.id, "name" in el ? el.name : undefined);
19
+ types.set(el.id, el.type);
20
+ if (el.type === "subProcess" ||
21
+ el.type === "adHocSubProcess" ||
22
+ el.type === "eventSubProcess" ||
23
+ el.type === "transaction") {
24
+ collectNames(el.flowElements);
25
+ }
26
+ }
27
+ };
28
+ collectNames(process.flowElements);
29
+ const positions = [];
30
+ for (const shape of diagram.plane.shapes) {
31
+ const b = shape.bounds;
32
+ positions.push({
33
+ id: shape.bpmnElement,
34
+ type: types.get(shape.bpmnElement) ?? "unknown",
35
+ name: names.get(shape.bpmnElement),
36
+ cx: b.x + b.width / 2,
37
+ cy: b.y + b.height / 2,
38
+ x: b.x,
39
+ y: b.y,
40
+ width: b.width,
41
+ height: b.height,
42
+ });
43
+ }
44
+ return positions;
45
+ }
46
+ // ── Auto-layout extraction ────────────────────────────────────────────────────
47
+ /**
48
+ * Runs the auto-layout engine on the process and returns element positions.
49
+ */
50
+ export function generateAutoLayout(defs) {
51
+ const process = defs.processes[0];
52
+ if (!process)
53
+ return [];
54
+ const result = layoutProcess(process);
55
+ return result.nodes.map((n) => ({
56
+ id: n.id,
57
+ type: n.type,
58
+ name: n.label,
59
+ cx: n.bounds.x + n.bounds.width / 2,
60
+ cy: n.bounds.y + n.bounds.height / 2,
61
+ x: n.bounds.x,
62
+ y: n.bounds.y,
63
+ width: n.bounds.width,
64
+ height: n.bounds.height,
65
+ }));
66
+ }
67
+ // ── Comparison ────────────────────────────────────────────────────────────────
68
+ function boundingBox(positions) {
69
+ if (positions.length === 0)
70
+ return { x: 0, y: 0, width: 0, height: 0 };
71
+ let minX = Number.POSITIVE_INFINITY;
72
+ let minY = Number.POSITIVE_INFINITY;
73
+ let maxX = Number.NEGATIVE_INFINITY;
74
+ let maxY = Number.NEGATIVE_INFINITY;
75
+ for (const p of positions) {
76
+ minX = Math.min(minX, p.x);
77
+ minY = Math.min(minY, p.y);
78
+ maxX = Math.max(maxX, p.x + p.width);
79
+ maxY = Math.max(maxY, p.y + p.height);
80
+ }
81
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
82
+ }
83
+ function percentile(sorted, p) {
84
+ if (sorted.length === 0)
85
+ return 0;
86
+ const idx = Math.ceil((p / 100) * sorted.length) - 1;
87
+ return sorted[Math.max(0, Math.min(idx, sorted.length - 1))] ?? 0;
88
+ }
89
+ /**
90
+ * Checks whether each sequence flow A→B has A to the left of B in both the
91
+ * reference and the auto-layout. Mismatches indicate the auto-layout placed
92
+ * elements in the wrong order.
93
+ */
94
+ function detectOrderViolations(defs, refMap, autoMap) {
95
+ const process = defs.processes[0];
96
+ if (!process)
97
+ return [];
98
+ const violations = [];
99
+ const checkFlows = (flows) => {
100
+ for (const flow of flows) {
101
+ const refSrc = refMap.get(flow.sourceRef);
102
+ const refTgt = refMap.get(flow.targetRef);
103
+ const autoSrc = autoMap.get(flow.sourceRef);
104
+ const autoTgt = autoMap.get(flow.targetRef);
105
+ if (!refSrc || !refTgt || !autoSrc || !autoTgt)
106
+ continue;
107
+ // In the reference, determine primary direction of this flow.
108
+ const refDx = refTgt.cx - refSrc.cx;
109
+ const refDy = refTgt.cy - refSrc.cy;
110
+ // Only check edges that are predominantly horizontal in the reference
111
+ // (|dx| > |dy|), as those represent clear left-to-right ordering.
112
+ if (Math.abs(refDx) <= Math.abs(refDy))
113
+ continue;
114
+ const refGoesRight = refDx > 0;
115
+ const autoDx = autoTgt.cx - autoSrc.cx;
116
+ const autoGoesRight = autoDx > 0;
117
+ if (refGoesRight !== autoGoesRight) {
118
+ const srcLabel = refSrc.name ? `"${refSrc.name}"` : refSrc.id;
119
+ const tgtLabel = refTgt.name ? `"${refTgt.name}"` : refTgt.id;
120
+ violations.push({
121
+ sourceId: flow.sourceRef,
122
+ targetId: flow.targetRef,
123
+ sourceName: refSrc.name,
124
+ targetName: refTgt.name,
125
+ description: `Flow ${srcLabel} → ${tgtLabel}: ` +
126
+ `reference goes ${refGoesRight ? "right" : "left"} (Δx=${Math.round(refDx)}), ` +
127
+ `auto-layout goes ${autoGoesRight ? "right" : "left"} (Δx=${Math.round(autoDx)})`,
128
+ });
129
+ }
130
+ }
131
+ };
132
+ checkFlows(process.sequenceFlows);
133
+ return violations;
134
+ }
135
+ /**
136
+ * Compares a reference layout (from XML DI) with an auto-generated layout.
137
+ */
138
+ export function compareLayouts(defs, ref, auto, fileName = "") {
139
+ const refMap = new Map(ref.map((p) => [p.id, p]));
140
+ const autoMap = new Map(auto.map((p) => [p.id, p]));
141
+ const comparisons = [];
142
+ for (const refEl of ref) {
143
+ const autoEl = autoMap.get(refEl.id);
144
+ if (!autoEl)
145
+ continue;
146
+ const dx = autoEl.cx - refEl.cx;
147
+ const dy = autoEl.cy - refEl.cy;
148
+ comparisons.push({
149
+ id: refEl.id,
150
+ type: refEl.type,
151
+ name: refEl.name,
152
+ ref: { cx: refEl.cx, cy: refEl.cy },
153
+ auto: { cx: autoEl.cx, cy: autoEl.cy },
154
+ delta: { dx, dy },
155
+ distance: Math.hypot(dx, dy),
156
+ });
157
+ }
158
+ const distances = comparisons.map((c) => c.distance).sort((a, b) => a - b);
159
+ const avgDistance = distances.length > 0 ? distances.reduce((s, d) => s + d, 0) / distances.length : 0;
160
+ const maxDistance = distances[distances.length - 1] ?? 0;
161
+ const p90Distance = percentile(distances, 90);
162
+ const refBbox = boundingBox(ref);
163
+ const autoBbox = boundingBox(auto);
164
+ const orderViolations = detectOrderViolations(defs, refMap, autoMap);
165
+ const process = defs.processes[0];
166
+ const flowCount = process?.sequenceFlows.length ?? 0;
167
+ return {
168
+ fileName,
169
+ elementCount: ref.length,
170
+ flowCount,
171
+ matchedCount: comparisons.length,
172
+ elements: comparisons.sort((a, b) => b.distance - a.distance),
173
+ orderViolations,
174
+ avgDistance,
175
+ maxDistance,
176
+ p90Distance,
177
+ ref: refBbox,
178
+ auto: autoBbox,
179
+ widthRatio: refBbox.width > 0 ? autoBbox.width / refBbox.width : 1,
180
+ heightRatio: refBbox.height > 0 ? autoBbox.height / refBbox.height : 1,
181
+ };
182
+ }
183
+ // ── Main entry point ──────────────────────────────────────────────────────────
184
+ /**
185
+ * Full benchmark pipeline: parse → extract reference layout → auto-layout → compare.
186
+ */
187
+ export function benchmarkLayout(xml, fileName = "") {
188
+ const defs = Bpmn.parse(xml);
189
+ const ref = parseReferenceLayout(defs);
190
+ const auto = generateAutoLayout(defs);
191
+ return compareLayouts(defs, ref, auto, fileName);
192
+ }
193
+ // ── Formatting helpers ────────────────────────────────────────────────────────
194
+ /** Format a BenchmarkResult as a human-readable report string. */
195
+ export function formatBenchmarkResult(r) {
196
+ const lines = [];
197
+ const w = (s, n) => s.padEnd(n);
198
+ const rn = (n) => String(Math.round(n));
199
+ const rf = (n, d = 1) => n.toFixed(d);
200
+ lines.push(` Elements: ${r.elementCount} Flows: ${r.flowCount} Matched: ${r.matchedCount}/${r.elementCount}`);
201
+ lines.push(` Avg dist: ${rf(r.avgDistance)}px P90: ${rf(r.p90Distance)}px Max: ${rf(r.maxDistance)}px`);
202
+ lines.push(` Size: ref ${rn(r.ref.width)}×${rn(r.ref.height)} auto ${rn(r.auto.width)}×${rn(r.auto.height)}` +
203
+ ` (${rf(r.widthRatio)}× wide, ${rf(r.heightRatio)}× tall)`);
204
+ if (r.orderViolations.length > 0) {
205
+ lines.push(` Order violations: ${r.orderViolations.length}`);
206
+ for (const v of r.orderViolations) {
207
+ lines.push(` ✗ ${v.description}`);
208
+ }
209
+ }
210
+ else {
211
+ lines.push(" Order violations: 0 ✓");
212
+ }
213
+ const topN = r.elements.slice(0, 5);
214
+ if (topN.length > 0) {
215
+ lines.push(" Top deviations:");
216
+ for (const el of topN) {
217
+ const label = el.name ? `"${el.name}"` : el.id;
218
+ const sign = (n) => (n >= 0 ? `+${rn(n)}` : rn(n));
219
+ lines.push(` ${w(`${el.type} ${label}`, 40)} ` +
220
+ `Δ(${sign(el.delta.dx)}, ${sign(el.delta.dy)}) dist=${rf(el.distance)}px`);
221
+ }
222
+ }
223
+ return lines.join("\n");
224
+ }
225
+ //# sourceMappingURL=bench.js.map
@@ -0,0 +1,62 @@
1
+ import type { BpmnElementType, BpmnFlowElement } from "../bpmn/bpmn-model.js";
2
+ import type { BackEdge, DirectedGraph } from "./graph.js";
3
+ import type { LayoutNode } from "./types.js";
4
+ /** Get the fixed size for a BPMN element type. */
5
+ export declare function getElementSize(type: BpmnElementType): {
6
+ width: number;
7
+ height: number;
8
+ };
9
+ /**
10
+ * Assign x,y coordinates to all nodes based on a virtual grid.
11
+ * Each grid cell is GRID_CELL_WIDTH × GRID_CELL_HEIGHT.
12
+ * Elements are centered within their grid cell.
13
+ * If an element is larger than a single cell, adjacent cells are merged.
14
+ */
15
+ export declare function assignCoordinates(orderedLayers: string[][], nodeIndex: Map<string, BpmnFlowElement>): LayoutNode[];
16
+ /**
17
+ * Re-assign x-coordinates after sub-process expansion.
18
+ * Walks layers left-to-right, shifting each layer to avoid overlap with the previous one.
19
+ */
20
+ export declare function reassignXCoordinates(layoutNodes: LayoutNode[], orderedLayers: string[][]): void;
21
+ /**
22
+ * Align nodes in linear sequences to a common y-baseline.
23
+ * A "linear" node has ≤1 predecessor and ≤1 successor, and is not a gateway.
24
+ * Walks forward from each chain root, setting successors to the same center-y.
25
+ */
26
+ export declare function alignBranchBaselines(layoutNodes: LayoutNode[], dag: DirectedGraph): void;
27
+ /**
28
+ * Align split/join gateway pairs to the same y-coordinate.
29
+ * A split gateway fans out to multiple successors; the corresponding join gateway
30
+ * is the nearest downstream gateway where all branches reconverge.
31
+ */
32
+ export declare function alignSplitJoinPairs(layoutNodes: LayoutNode[], dag: DirectedGraph, backEdges?: BackEdge[]): void;
33
+ /**
34
+ * Ensure early-return branches (shorter paths from split to join) are never on the baseline.
35
+ * The baseline is the split gateway's center-y. If the shortest branch sits on the baseline,
36
+ * swap it with a longer branch.
37
+ */
38
+ export declare function ensureEarlyReturnOffBaseline(layoutNodes: LayoutNode[], dag: DirectedGraph, backEdges?: BackEdge[]): void;
39
+ /**
40
+ * Find the baseline path — the "spine" of the process that all paths share.
41
+ * At split gateways, jumps directly to the corresponding join gateway.
42
+ * Returns the ordered list of node IDs on the baseline.
43
+ */
44
+ export declare function findBaselinePath(layoutNodes: LayoutNode[], dag: DirectedGraph, backEdges?: BackEdge[]): string[];
45
+ /**
46
+ * Align all nodes on the baseline path to the same center-Y.
47
+ * Uses the first node's (start event) center-Y as the baseline.
48
+ */
49
+ export declare function alignBaselinePath(layoutNodes: LayoutNode[], dag: DirectedGraph, backEdges?: BackEdge[]): void;
50
+ /**
51
+ * Distribute branches of split gateways symmetrically around the gateway center Y.
52
+ * Pass 1: multi-branch gateways (2+) — symmetric distribution.
53
+ * Pass 2: single-branch gateways — placed one full grid row away, with peer-aware gap enforcement.
54
+ */
55
+ export declare function distributeSplitBranches(layoutNodes: LayoutNode[], dag: DirectedGraph, backEdges?: BackEdge[]): void;
56
+ /**
57
+ * Resolve overlaps within each layer by pushing nodes apart.
58
+ * Sorts nodes by Y within each layer and ensures minimum gap.
59
+ * Also normalizes coordinates so no node has negative Y.
60
+ */
61
+ export declare function resolveLayerOverlaps(layoutNodes: LayoutNode[]): void;
62
+ //# sourceMappingURL=coordinates.d.ts.map