@bpmnkit/core 0.1.1 → 0.2.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/README.md +32 -1
- package/dist/bpmn/agentic.d.ts +121 -0
- package/dist/bpmn/agentic.js +97 -0
- package/dist/bpmn/auto-layout.d.ts +5 -5
- package/dist/bpmn/auto-layout.js +592 -36
- package/dist/bpmn/bpmn-builder.d.ts +265 -3
- package/dist/bpmn/bpmn-builder.js +603 -197
- package/dist/bpmn/bpmn-model.d.ts +114 -0
- package/dist/bpmn/bpmn-parser.js +1414 -521
- package/dist/bpmn/bpmn-serializer.js +107 -19
- package/dist/bpmn/compact.d.ts +17 -2
- package/dist/bpmn/compact.js +3 -3
- package/dist/bpmn/full-operations.d.ts +89 -0
- package/dist/bpmn/full-operations.js +478 -0
- package/dist/bpmn/index.d.ts +19 -0
- package/dist/bpmn/index.js +21 -0
- package/dist/bpmn/optimize/agentic.d.ts +10 -0
- package/dist/bpmn/optimize/agentic.js +88 -0
- package/dist/bpmn/optimize/deploy.d.ts +16 -0
- package/dist/bpmn/optimize/deploy.js +143 -0
- package/dist/bpmn/optimize/feel-syntax.d.ts +12 -0
- package/dist/bpmn/optimize/feel-syntax.js +87 -0
- package/dist/bpmn/optimize/feel.js +7 -4
- package/dist/bpmn/optimize/flow.js +22 -2
- package/dist/bpmn/optimize/index.js +20 -9
- package/dist/bpmn/optimize/patterns.js +23 -16
- package/dist/bpmn/optimize/tasks.js +30 -7
- package/dist/bpmn/optimize/types.d.ts +10 -1
- package/dist/bpmn/optimize/utils.js +2 -4
- package/dist/bpmn/optimize/variable-flow.js +58 -67
- package/dist/bpmn/semantic-hash.d.ts +93 -0
- package/dist/bpmn/semantic-hash.js +155 -0
- package/dist/bpmn/sha256.d.ts +17 -0
- package/dist/bpmn/sha256.js +95 -0
- package/dist/bpmn/zeebe-extensions.d.ts +83 -0
- package/dist/bpmn/zeebe-extensions.js +117 -0
- package/dist/bpmn/zeebe-placement.d.ts +12 -0
- package/dist/bpmn/zeebe-placement.js +140 -0
- package/dist/errors.d.ts +40 -1
- package/dist/errors.js +41 -0
- package/dist/index.d.ts +16 -5
- package/dist/index.js +9 -3
- package/dist/layout/annotations.js +36 -1
- package/dist/layout/collaboration/alignment.d.ts +26 -0
- package/dist/layout/collaboration/alignment.js +66 -0
- package/dist/layout/collaboration/ordering.d.ts +21 -0
- package/dist/layout/collaboration/ordering.js +102 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/layout-engine.d.ts +13 -3
- package/dist/layout/layout-engine.js +9 -4
- package/dist/layout/semantic/bands.d.ts +19 -0
- package/dist/layout/semantic/bands.js +324 -0
- package/dist/layout/semantic/graph.d.ts +37 -0
- package/dist/layout/semantic/graph.js +242 -0
- package/dist/layout/semantic/index.d.ts +13 -0
- package/dist/layout/semantic/index.js +181 -0
- package/dist/layout/semantic/place.d.ts +40 -0
- package/dist/layout/semantic/place.js +271 -0
- package/dist/layout/semantic/route.d.ts +14 -0
- package/dist/layout/semantic/route.js +514 -0
- package/dist/layout/types.d.ts +17 -0
- package/dist/node/index.d.ts +10 -0
- package/dist/node/index.js +9 -0
- package/dist/node/write.d.ts +81 -0
- package/dist/node/write.js +167 -0
- package/dist/plan/compile.d.ts +39 -0
- package/dist/plan/compile.js +380 -0
- package/dist/plan/extract.d.ts +31 -0
- package/dist/plan/extract.js +248 -0
- package/dist/plan/index.d.ts +6 -0
- package/dist/plan/index.js +5 -0
- package/dist/plan/merge.d.ts +13 -0
- package/dist/plan/merge.js +80 -0
- package/dist/plan/slug.d.ts +5 -0
- package/dist/plan/slug.js +22 -0
- package/dist/plan/types.d.ts +225 -0
- package/dist/plan/types.js +13 -0
- package/dist/types/id-generator.js +11 -3
- package/dist/xml/index.d.ts +3 -1
- package/dist/xml/index.js +2 -1
- package/dist/xml/xml-parser.d.ts +32 -0
- package/dist/xml/xml-parser.js +394 -143
- package/package.json +9 -2
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { ELEMENT_SIZES } from "../types.js";
|
|
2
|
+
/** Geometry constants. Layout may add space beyond these; it never takes any back. */
|
|
3
|
+
export const H_GAP = 100;
|
|
4
|
+
export const V_GAP = 80;
|
|
5
|
+
export const COMPONENT_GAP = 80;
|
|
6
|
+
export const LANE_PADDING = 40;
|
|
7
|
+
export const BOUNDARY_SIZE = 36;
|
|
8
|
+
const DEFAULT_SIZE = { width: 100, height: 80 };
|
|
9
|
+
export function sizeOf(el, childExtent) {
|
|
10
|
+
if (childExtent)
|
|
11
|
+
return { width: childExtent.width, height: childExtent.height };
|
|
12
|
+
return ELEMENT_SIZES[el.type] ?? DEFAULT_SIZE;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Turn ranks and bands into coordinates.
|
|
16
|
+
*
|
|
17
|
+
* Ranks become columns sized by their widest node, bands become rows sized by
|
|
18
|
+
* their tallest. Nodes are centred in both, so any two nodes sharing a band
|
|
19
|
+
* share a centre line and the spine routes as one straight segment.
|
|
20
|
+
*/
|
|
21
|
+
export function place(graph, bandLayout, sizes, laneSet) {
|
|
22
|
+
const bounds = new Map();
|
|
23
|
+
const componentOf = new Map();
|
|
24
|
+
for (let i = 0; i < graph.components.length; i++) {
|
|
25
|
+
for (const id of graph.components[i] ?? [])
|
|
26
|
+
componentOf.set(id, i);
|
|
27
|
+
}
|
|
28
|
+
// ── Columns: one per rank, shared across components so flow stays aligned ──
|
|
29
|
+
const colWidth = new Map();
|
|
30
|
+
for (const node of graph.nodes) {
|
|
31
|
+
const rank = graph.ranks.get(node.id) ?? 0;
|
|
32
|
+
const size = sizes.get(node.id) ?? DEFAULT_SIZE;
|
|
33
|
+
colWidth.set(rank, Math.max(colWidth.get(rank) ?? 0, size.width));
|
|
34
|
+
}
|
|
35
|
+
const colX = new Map();
|
|
36
|
+
const gutterX = new Map();
|
|
37
|
+
let x = 0;
|
|
38
|
+
for (const rank of [...colWidth.keys()].sort((a, b) => a - b)) {
|
|
39
|
+
colX.set(rank, x);
|
|
40
|
+
gutterX.set(rank, x - H_GAP / 2);
|
|
41
|
+
x += (colWidth.get(rank) ?? 0) + H_GAP;
|
|
42
|
+
}
|
|
43
|
+
// ── Rows: one per band, laid out per component then stacked ──
|
|
44
|
+
let cursorY = 0;
|
|
45
|
+
for (let component = 0; component < graph.components.length; component++) {
|
|
46
|
+
const members = (graph.components[component] ?? []).filter((id) => graph.byId.has(id));
|
|
47
|
+
if (members.length === 0)
|
|
48
|
+
continue;
|
|
49
|
+
const rowHeight = new Map();
|
|
50
|
+
for (const id of members) {
|
|
51
|
+
const band = bandLayout.bands.get(id) ?? 0;
|
|
52
|
+
const size = sizes.get(id) ?? DEFAULT_SIZE;
|
|
53
|
+
rowHeight.set(band, Math.max(rowHeight.get(band) ?? 0, size.height));
|
|
54
|
+
}
|
|
55
|
+
const rowY = new Map();
|
|
56
|
+
let y = cursorY;
|
|
57
|
+
for (const band of [...rowHeight.keys()].sort((a, b) => a - b)) {
|
|
58
|
+
rowY.set(band, y);
|
|
59
|
+
y += (rowHeight.get(band) ?? 0) + V_GAP;
|
|
60
|
+
}
|
|
61
|
+
for (const id of members) {
|
|
62
|
+
const size = sizes.get(id) ?? DEFAULT_SIZE;
|
|
63
|
+
const rank = graph.ranks.get(id) ?? 0;
|
|
64
|
+
const band = bandLayout.bands.get(id) ?? 0;
|
|
65
|
+
const cw = colWidth.get(rank) ?? size.width;
|
|
66
|
+
const rh = rowHeight.get(band) ?? size.height;
|
|
67
|
+
bounds.set(id, {
|
|
68
|
+
x: (colX.get(rank) ?? 0) + (cw - size.width) / 2,
|
|
69
|
+
y: (rowY.get(band) ?? 0) + (rh - size.height) / 2,
|
|
70
|
+
width: size.width,
|
|
71
|
+
height: size.height,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
cursorY = y - V_GAP + COMPONENT_GAP;
|
|
75
|
+
}
|
|
76
|
+
separateSameCell(graph, bandLayout, bounds);
|
|
77
|
+
const lanes = laneSet ? applyLanes(graph, laneSet, bounds) : [];
|
|
78
|
+
dockBoundaryEvents(graph, bounds);
|
|
79
|
+
return { bounds, lanes, gutterX };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Two nodes can land in the same rank and band — a parallel split whose
|
|
83
|
+
* branches were compacted onto one band, for instance. Push the later ones
|
|
84
|
+
* down in declaration order until the cell is clear.
|
|
85
|
+
*/
|
|
86
|
+
function separateSameCell(graph, bandLayout, bounds) {
|
|
87
|
+
const componentOf = new Map();
|
|
88
|
+
for (let i = 0; i < graph.components.length; i++) {
|
|
89
|
+
for (const id of graph.components[i] ?? [])
|
|
90
|
+
componentOf.set(id, i);
|
|
91
|
+
}
|
|
92
|
+
// Scoped per component: components occupy their own vertical space already,
|
|
93
|
+
// so two nodes sharing a rank and band across components do not collide.
|
|
94
|
+
const cells = new Map();
|
|
95
|
+
for (const node of graph.nodes) {
|
|
96
|
+
const rank = graph.ranks.get(node.id) ?? 0;
|
|
97
|
+
const band = bandLayout.bands.get(node.id) ?? 0;
|
|
98
|
+
const key = `${componentOf.get(node.id) ?? 0}:${rank}:${band}`;
|
|
99
|
+
const list = cells.get(key);
|
|
100
|
+
if (list)
|
|
101
|
+
list.push(node.id);
|
|
102
|
+
else
|
|
103
|
+
cells.set(key, [node.id]);
|
|
104
|
+
}
|
|
105
|
+
for (const ids of cells.values()) {
|
|
106
|
+
if (ids.length < 2)
|
|
107
|
+
continue;
|
|
108
|
+
let offset = 0;
|
|
109
|
+
for (const id of ids) {
|
|
110
|
+
const b = bounds.get(id);
|
|
111
|
+
if (!b)
|
|
112
|
+
continue;
|
|
113
|
+
b.y += offset;
|
|
114
|
+
offset += b.height + V_GAP;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Move every node into the lane it belongs to, then size the lanes to their
|
|
120
|
+
* content. Ranks are untouched, so left-to-right flow survives the shift.
|
|
121
|
+
*
|
|
122
|
+
* Nested lane sets keep both levels: leaf lanes take a band each, and an
|
|
123
|
+
* ancestor lane spans the bands of its descendants.
|
|
124
|
+
*/
|
|
125
|
+
function applyLanes(graph, laneSet, bounds) {
|
|
126
|
+
const tree = laneTree(laneSet);
|
|
127
|
+
if (tree.length === 0)
|
|
128
|
+
return [];
|
|
129
|
+
const leaves = [];
|
|
130
|
+
const collectLeaves = (nodes) => {
|
|
131
|
+
for (const node of nodes) {
|
|
132
|
+
if (node.children.length > 0)
|
|
133
|
+
collectLeaves(node.children);
|
|
134
|
+
else
|
|
135
|
+
leaves.push(node);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
collectLeaves(tree);
|
|
139
|
+
const laneOf = new Map();
|
|
140
|
+
for (const leaf of leaves) {
|
|
141
|
+
for (const ref of leaf.refs)
|
|
142
|
+
laneOf.set(ref, leaf);
|
|
143
|
+
}
|
|
144
|
+
const members = new Map();
|
|
145
|
+
for (const leaf of leaves)
|
|
146
|
+
members.set(leaf.id, []);
|
|
147
|
+
const orphans = [];
|
|
148
|
+
for (const node of graph.nodes) {
|
|
149
|
+
const lane = laneOf.get(node.id);
|
|
150
|
+
const list = lane ? members.get(lane.id) : undefined;
|
|
151
|
+
if (list)
|
|
152
|
+
list.push(node.id);
|
|
153
|
+
else
|
|
154
|
+
orphans.push(node.id);
|
|
155
|
+
}
|
|
156
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
157
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
158
|
+
for (const b of bounds.values()) {
|
|
159
|
+
minX = Math.min(minX, b.x);
|
|
160
|
+
maxX = Math.max(maxX, b.x + b.width);
|
|
161
|
+
}
|
|
162
|
+
if (!Number.isFinite(minX))
|
|
163
|
+
return [];
|
|
164
|
+
const width = maxX - minX + 2 * LANE_PADDING;
|
|
165
|
+
const left = minX - LANE_PADDING;
|
|
166
|
+
const span = new Map();
|
|
167
|
+
let top = 0;
|
|
168
|
+
for (const leaf of leaves) {
|
|
169
|
+
const own = (members.get(leaf.id) ?? [])
|
|
170
|
+
.map((n) => bounds.get(n))
|
|
171
|
+
.filter((b) => b !== undefined);
|
|
172
|
+
let height = 2 * LANE_PADDING + BOUNDARY_SIZE;
|
|
173
|
+
if (own.length > 0) {
|
|
174
|
+
const lowest = Math.min(...own.map((b) => b.y));
|
|
175
|
+
height = Math.max(...own.map((b) => b.y + b.height)) - lowest + 2 * LANE_PADDING;
|
|
176
|
+
const shift = top + LANE_PADDING - lowest;
|
|
177
|
+
for (const b of own)
|
|
178
|
+
b.y += shift;
|
|
179
|
+
}
|
|
180
|
+
span.set(leaf.id, { top, bottom: top + height });
|
|
181
|
+
top += height;
|
|
182
|
+
}
|
|
183
|
+
// Unclaimed nodes keep a band of their own rather than being attributed to a
|
|
184
|
+
// lane they are not a member of.
|
|
185
|
+
if (orphans.length > 0) {
|
|
186
|
+
const own = orphans.map((n) => bounds.get(n)).filter((b) => b !== undefined);
|
|
187
|
+
if (own.length > 0) {
|
|
188
|
+
const shift = top + LANE_PADDING - Math.min(...own.map((b) => b.y));
|
|
189
|
+
for (const b of own)
|
|
190
|
+
b.y += shift;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const slots = [];
|
|
194
|
+
const emit = (nodes) => {
|
|
195
|
+
let extent = null;
|
|
196
|
+
for (const node of nodes) {
|
|
197
|
+
const own = node.children.length > 0 ? emit(node.children) : (span.get(node.id) ?? null);
|
|
198
|
+
if (!own)
|
|
199
|
+
continue;
|
|
200
|
+
if (node.children.length > 0)
|
|
201
|
+
span.set(node.id, own);
|
|
202
|
+
slots.push({
|
|
203
|
+
id: node.id,
|
|
204
|
+
bounds: { x: left, y: own.top, width, height: own.bottom - own.top },
|
|
205
|
+
});
|
|
206
|
+
extent = extent
|
|
207
|
+
? { top: Math.min(extent.top, own.top), bottom: Math.max(extent.bottom, own.bottom) }
|
|
208
|
+
: own;
|
|
209
|
+
}
|
|
210
|
+
return extent;
|
|
211
|
+
};
|
|
212
|
+
emit(tree);
|
|
213
|
+
// Outermost first, so a parent lane is drawn behind the lanes it contains.
|
|
214
|
+
return slots.sort((a, b) => b.bounds.height - a.bounds.height || a.bounds.y - b.bounds.y);
|
|
215
|
+
}
|
|
216
|
+
function laneTree(laneSet) {
|
|
217
|
+
return laneSet.lanes.map((lane) => ({
|
|
218
|
+
id: lane.id,
|
|
219
|
+
refs: lane.flowNodeRefs,
|
|
220
|
+
children: lane.childLaneSet ? laneTree(lane.childLaneSet) : [],
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Dock boundary events on their host's edge — escalation on top, everything
|
|
225
|
+
* else on the bottom — spread along it. Events sharing a side are ordered by
|
|
226
|
+
* how far their handler runs, longest first, so long paths get the outer slot.
|
|
227
|
+
*/
|
|
228
|
+
function dockBoundaryEvents(graph, bounds) {
|
|
229
|
+
for (const [hostId, events] of graph.attachers) {
|
|
230
|
+
const host = bounds.get(hostId);
|
|
231
|
+
if (!host)
|
|
232
|
+
continue;
|
|
233
|
+
const sides = new Map();
|
|
234
|
+
for (const event of events) {
|
|
235
|
+
const side = event.eventDefinitions.some((d) => d.type === "escalation") ? "top" : "bottom";
|
|
236
|
+
const list = sides.get(side);
|
|
237
|
+
if (list)
|
|
238
|
+
list.push(event);
|
|
239
|
+
else
|
|
240
|
+
sides.set(side, [event]);
|
|
241
|
+
}
|
|
242
|
+
for (const [side, list] of sides) {
|
|
243
|
+
const ordered = [...list].sort((a, b) => handlerReach(graph, b.id) - handlerReach(graph, a.id));
|
|
244
|
+
const n = ordered.length;
|
|
245
|
+
for (let i = 0; i < n; i++) {
|
|
246
|
+
const event = ordered[i];
|
|
247
|
+
if (!event)
|
|
248
|
+
continue;
|
|
249
|
+
bounds.set(event.id, {
|
|
250
|
+
x: host.x + ((i + 1) * host.width) / (n + 1) - BOUNDARY_SIZE / 2,
|
|
251
|
+
y: (side === "top" ? host.y : host.y + host.height) - BOUNDARY_SIZE / 2,
|
|
252
|
+
width: BOUNDARY_SIZE,
|
|
253
|
+
height: BOUNDARY_SIZE,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/** How far the handler of a boundary event runs, in ranks. */
|
|
260
|
+
function handlerReach(graph, eventId) {
|
|
261
|
+
let reach = 0;
|
|
262
|
+
for (const [, flows] of graph.outgoing) {
|
|
263
|
+
for (const flow of flows) {
|
|
264
|
+
if (flow.sourceRef !== eventId)
|
|
265
|
+
continue;
|
|
266
|
+
reach = Math.max(reach, graph.ranks.get(flow.targetRef) ?? 0);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return reach;
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=place.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BpmnSequenceFlow } from "../../bpmn/bpmn-model.js";
|
|
2
|
+
import type { Bounds, LayoutEdge } from "../types.js";
|
|
3
|
+
import type { BandLayout } from "./bands.js";
|
|
4
|
+
import type { SemanticGraph } from "./graph.js";
|
|
5
|
+
/**
|
|
6
|
+
* Route every sequence flow orthogonally.
|
|
7
|
+
*
|
|
8
|
+
* Each edge proposes candidate polylines from most to least desirable and takes
|
|
9
|
+
* the first one that clears every unrelated shape: a straight run along the
|
|
10
|
+
* spine, a turn out of the source's top or bottom for a branch, and a turn in
|
|
11
|
+
* the empty gutter in front of the target's column as the general fallback.
|
|
12
|
+
*/
|
|
13
|
+
export declare function routeFlows(graph: SemanticGraph, flows: BpmnSequenceFlow[], bounds: Map<string, Bounds>, bandLayout: BandLayout, gutterX: Map<number, number>): LayoutEdge[];
|
|
14
|
+
//# sourceMappingURL=route.d.ts.map
|