@bpmnkit/core 0.0.23 → 0.0.24

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.
@@ -1,4 +1,4 @@
1
- import { ProcessBuilder } from "./bpmn-builder.js";
1
+ import { DiagramBuilder, ProcessBuilder } from "./bpmn-builder.js";
2
2
  import type { BpmnDefinitions } from "./bpmn-model.js";
3
3
  /** A minimal 3-element BPMN diagram useful for first-launch or "New Diagram" defaults. */
4
4
  export declare const SAMPLE_BPMN_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<bpmn:definitions xmlns:bpmn=\"http://www.omg.org/spec/BPMN/20100524/MODEL\"\n xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\"\n xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\"\n xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\"\n id=\"Definitions_1\" targetNamespace=\"http://bpmn.io/schema/bpmn\">\n <bpmn:process id=\"proc\" isExecutable=\"true\">\n <bpmn:startEvent id=\"start\" name=\"Start\">\n <bpmn:outgoing>flow1</bpmn:outgoing>\n </bpmn:startEvent>\n <bpmn:serviceTask id=\"task1\" name=\"Process Order\">\n <bpmn:incoming>flow1</bpmn:incoming>\n <bpmn:outgoing>flow2</bpmn:outgoing>\n </bpmn:serviceTask>\n <bpmn:endEvent id=\"end\" name=\"End\">\n <bpmn:incoming>flow2</bpmn:incoming>\n </bpmn:endEvent>\n <bpmn:sequenceFlow id=\"flow1\" sourceRef=\"start\" targetRef=\"task1\"/>\n <bpmn:sequenceFlow id=\"flow2\" sourceRef=\"task1\" targetRef=\"end\"/>\n </bpmn:process>\n <bpmndi:BPMNDiagram id=\"diagram1\">\n <bpmndi:BPMNPlane id=\"plane1\" bpmnElement=\"proc\">\n <bpmndi:BPMNShape id=\"start_di\" bpmnElement=\"start\">\n <dc:Bounds x=\"152\" y=\"202\" width=\"36\" height=\"36\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNShape id=\"task1_di\" bpmnElement=\"task1\">\n <dc:Bounds x=\"260\" y=\"180\" width=\"100\" height=\"80\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNShape id=\"end_di\" bpmnElement=\"end\">\n <dc:Bounds x=\"432\" y=\"202\" width=\"36\" height=\"36\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNEdge id=\"flow1_di\" bpmnElement=\"flow1\">\n <di:waypoint x=\"188\" y=\"220\"/>\n <di:waypoint x=\"260\" y=\"220\"/>\n </bpmndi:BPMNEdge>\n <bpmndi:BPMNEdge id=\"flow2_di\" bpmnElement=\"flow2\">\n <di:waypoint x=\"360\" y=\"220\"/>\n <di:waypoint x=\"432\" y=\"220\"/>\n </bpmndi:BPMNEdge>\n </bpmndi:BPMNPlane>\n </bpmndi:BPMNDiagram>\n</bpmn:definitions>";
@@ -41,6 +41,20 @@ export declare const Bpmn: {
41
41
  * ```
42
42
  */
43
43
  readonly createProcess: (processId: string) => ProcessBuilder;
44
+ /**
45
+ * Create a multi-process BPMN definitions document using the fluent builder API.
46
+ *
47
+ * @param id - Unique identifier for the definitions element (defaults to `"Definitions_1"`).
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const defs = Bpmn.createDiagram("OrderSystem")
52
+ * .process("caller", (p) => p.startEvent("s").callActivity("call", { processId: "callee" }).endEvent("e"))
53
+ * .process("callee", (p) => p.startEvent("s2").serviceTask("work", { name: "Work", taskType: "work" }).endEvent("e2"))
54
+ * .build()
55
+ * ```
56
+ */
57
+ readonly createDiagram: (id?: string) => DiagramBuilder;
44
58
  /**
45
59
  * Parse a BPMN 2.0 XML string into a typed {@link BpmnDefinitions} model.
46
60
  *
@@ -1,5 +1,5 @@
1
1
  import { applyAutoLayout } from "./auto-layout.js";
2
- import { ProcessBuilder } from "./bpmn-builder.js";
2
+ import { DiagramBuilder, ProcessBuilder } from "./bpmn-builder.js";
3
3
  import { parseBpmn } from "./bpmn-parser.js";
4
4
  import { serializeBpmn } from "./bpmn-serializer.js";
5
5
  /** A minimal 3-element BPMN diagram useful for first-launch or "New Diagram" defaults. */
@@ -86,6 +86,22 @@ export const Bpmn = {
86
86
  createProcess(processId) {
87
87
  return new ProcessBuilder(processId);
88
88
  },
89
+ /**
90
+ * Create a multi-process BPMN definitions document using the fluent builder API.
91
+ *
92
+ * @param id - Unique identifier for the definitions element (defaults to `"Definitions_1"`).
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const defs = Bpmn.createDiagram("OrderSystem")
97
+ * .process("caller", (p) => p.startEvent("s").callActivity("call", { processId: "callee" }).endEvent("e"))
98
+ * .process("callee", (p) => p.startEvent("s2").serviceTask("work", { name: "Work", taskType: "work" }).endEvent("e2"))
99
+ * .build()
100
+ * ```
101
+ */
102
+ createDiagram(id = "Definitions_1") {
103
+ return new DiagramBuilder(id);
104
+ },
89
105
  /**
90
106
  * Parse a BPMN 2.0 XML string into a typed {@link BpmnDefinitions} model.
91
107
  *
@@ -64,6 +64,8 @@ export interface ZeebeExtensions {
64
64
  formDefinition?: ZeebeFormDefinition;
65
65
  /** DMN decision invoked by a business rule task (zeebe:calledDecision). */
66
66
  calledDecision?: ZeebeCalledDecision;
67
+ /** Marks this as a Camunda 8 native user task (zeebe:userTask). */
68
+ userTask?: true;
67
69
  /** Unrecognized extension elements preserved for roundtrip. */
68
70
  unknownElements?: XmlElement[];
69
71
  }
@@ -71,6 +71,9 @@ export function zeebeExtensionsToXmlElements(extensions) {
71
71
  attrs.activeElementsCollection = activeElementsCollection;
72
72
  elements.push({ name: "zeebe:adHoc", attributes: attrs, children: [] });
73
73
  }
74
+ if (extensions.userTask) {
75
+ elements.push({ name: "zeebe:userTask", attributes: {}, children: [] });
76
+ }
74
77
  if (extensions.formDefinition) {
75
78
  elements.push({
76
79
  name: "zeebe:formDefinition",
package/dist/index.d.ts CHANGED
@@ -4,7 +4,8 @@ export { isBpmnActivity, isBpmnAdHocSubProcess, isBpmnBoundaryEvent, isBpmnBusin
4
4
  export { findElement, findElementInProcess, findProcess, findSequenceFlow, getAllElements, getElementType, getZeebeExtensions, } from "./bpmn/utils.js";
5
5
  export { Bpmn, SAMPLE_BPMN_XML } from "./bpmn/index.js";
6
6
  export { applyAutoLayout } from "./bpmn/auto-layout.js";
7
- export type { ProcessBuilder, BranchBuilder, SubProcessContentBuilder, ServiceTaskOptions, ScriptTaskOptions, UserTaskOptions, CallActivityOptions, BusinessRuleTaskOptions, ElementOptions, GatewayOptions, MultiInstanceOptions, SubProcessOptions, StartEventOptions, IntermediateCatchEventOptions, IntermediateThrowEventOptions, BoundaryEventOptions, AdHocSubProcessOptions, } from "./bpmn/bpmn-builder.js";
7
+ export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
8
+ export type { ProcessBuilder, BranchBuilder, SubProcessContentBuilder, ServiceTaskOptions, ScriptTaskOptions, UserTaskOptions, CallActivityOptions, BusinessRuleTaskOptions, ElementOptions, GatewayOptions, MultiInstanceOptions, SubProcessOptions, StartEventOptions, IntermediateCatchEventOptions, IntermediateThrowEventOptions, EndEventOptions, BoundaryEventOptions, AdHocSubProcessOptions, } from "./bpmn/bpmn-builder.js";
8
9
  export type { BpmnDefinitions, BpmnProcess, BpmnFlowNode, BpmnFlowElement, BpmnSequenceFlow, BpmnBoundaryEvent, BpmnElementType, BpmnStartEvent, BpmnEndEvent, BpmnIntermediateCatchEvent, BpmnIntermediateThrowEvent, BpmnTask, BpmnServiceTask, BpmnScriptTask, BpmnUserTask, BpmnSendTask, BpmnReceiveTask, BpmnBusinessRuleTask, BpmnManualTask, BpmnCallActivity, BpmnSubProcess, BpmnAdHocSubProcess, BpmnEventSubProcess, BpmnTransaction, BpmnExclusiveGateway, BpmnParallelGateway, BpmnInclusiveGateway, BpmnEventBasedGateway, BpmnComplexGateway, BpmnCollaboration, BpmnParticipant, BpmnMessageFlow, BpmnLane, BpmnLaneSet, BpmnError, BpmnEscalation, BpmnMessage, BpmnSignal, BpmnTextAnnotation, BpmnAssociation, BpmnConditionExpression, BpmnEventDefinition, BpmnTimerEventDefinition, BpmnErrorEventDefinition, BpmnEscalationEventDefinition, BpmnMessageEventDefinition, BpmnSignalEventDefinition, BpmnConditionalEventDefinition, BpmnLinkEventDefinition, BpmnCancelEventDefinition, BpmnTerminateEventDefinition, BpmnCompensateEventDefinition, BpmnMultiInstanceLoopCharacteristics, BpmnDiagram, BpmnDiPlane, BpmnDiShape, BpmnDiEdge, BpmnDiLabel, BpmnBounds, BpmnWaypoint, } from "./bpmn/bpmn-model.js";
9
10
  export type { RestConnectorConfig, RestAuthentication, HttpMethod, } from "./bpmn/rest-connector.js";
10
11
  export type { ZeebeExtensions, ZeebeTaskDefinition, ZeebeIoMapping, ZeebeIoMappingEntry, ZeebeTaskHeaders, ZeebeTaskHeaderEntry, ZeebeProperties, ZeebePropertyEntry, ZeebeFormDefinition, ZeebeCalledDecision, } from "./bpmn/zeebe-extensions.js";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { isBpmnActivity, isBpmnAdHocSubProcess, isBpmnBoundaryEvent, isBpmnBusin
3
3
  export { findElement, findElementInProcess, findProcess, findSequenceFlow, getAllElements, getElementType, getZeebeExtensions, } from "./bpmn/utils.js";
4
4
  export { Bpmn, SAMPLE_BPMN_XML } from "./bpmn/index.js";
5
5
  export { applyAutoLayout } from "./bpmn/auto-layout.js";
6
+ export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
6
7
  export { zeebeExtensionsToXmlElements } from "./bpmn/zeebe-extensions.js";
7
8
  export { Dmn, layoutDmn, benchmarkDmnLayout, compactifyDmn, expandDmn } from "./dmn/index.js";
8
9
  export { Form, compactifyForm, expandForm } from "./form/index.js";
@@ -10,7 +10,8 @@ import type { LayoutResult } from "./types.js";
10
10
  * 4. Coordinate assignment — Fixed element sizes with spacing
11
11
  * 5. Sub-process layout — Recursive nested passes
12
12
  * 6. Edge routing — Orthogonal waypoints
13
- * 7. Overlap assertionPost-condition validation
13
+ * 7. Boundary event repositioning place events on host border
14
+ * 8. Overlap assertion — Post-condition validation
14
15
  */
15
16
  export declare function layoutProcess(process: BpmnProcess): LayoutResult;
16
17
  /**
@@ -7,6 +7,133 @@ import { assignLayers, groupByLayer } from "./layers.js";
7
7
  import { assertNoOverlap } from "./overlap.js";
8
8
  import { routeEdges } from "./routing.js";
9
9
  import { layoutSubProcesses } from "./subprocess.js";
10
+ const CHAIN_GAP = 30;
11
+ const CHAIN_V_GAP = 20;
12
+ /**
13
+ * Reposition boundary events to the bottom edge of their host task, then walk
14
+ * each boundary event's exclusive downstream chain and place those nodes
15
+ * horizontally to the right of the host task. Re-routes all affected edges.
16
+ */
17
+ function repositionBoundaryEvents(flowElements, result) {
18
+ const boundaryMap = new Map();
19
+ for (const el of flowElements) {
20
+ if (el.type !== "boundaryEvent")
21
+ continue;
22
+ const list = boundaryMap.get(el.attachedToRef) ?? [];
23
+ list.push(el.id);
24
+ boundaryMap.set(el.attachedToRef, list);
25
+ }
26
+ if (boundaryMap.size === 0)
27
+ return;
28
+ const nodeById = new Map(result.nodes.map((n) => [n.id, n]));
29
+ const succIds = new Map();
30
+ const predIds = new Map();
31
+ for (const edge of result.edges) {
32
+ const se = succIds.get(edge.sourceRef) ?? [];
33
+ se.push(edge.targetRef);
34
+ succIds.set(edge.sourceRef, se);
35
+ const ps = predIds.get(edge.targetRef) ?? new Set();
36
+ ps.add(edge.sourceRef);
37
+ predIds.set(edge.targetRef, ps);
38
+ }
39
+ for (const [hostId, beIds] of boundaryMap) {
40
+ const hostNode = nodeById.get(hostId);
41
+ if (!hostNode)
42
+ continue;
43
+ for (let i = 0; i < beIds.length; i++) {
44
+ const beId = beIds[i];
45
+ if (!beId)
46
+ continue;
47
+ const beNode = nodeById.get(beId);
48
+ if (!beNode)
49
+ continue;
50
+ const bW = beNode.bounds.width;
51
+ const bH = beNode.bounds.height;
52
+ // Place boundary event on the bottom edge of the host task, stacking leftward
53
+ const rightEdge = hostNode.bounds.x + hostNode.bounds.width;
54
+ beNode.bounds.x = Math.round(rightEdge - bW / 2 - i * (bW + 4));
55
+ beNode.bounds.y = Math.round(hostNode.bounds.y + hostNode.bounds.height - bH / 2);
56
+ if (beNode.labelBounds) {
57
+ beNode.labelBounds.x = beNode.bounds.x + Math.round(bW / 2 - beNode.labelBounds.width / 2);
58
+ beNode.labelBounds.y = beNode.bounds.y + bH + 4;
59
+ }
60
+ // Collect nodes exclusively reachable from this boundary event (BFS)
61
+ const chainSet = new Set([beId]);
62
+ const chainOrder = [];
63
+ const queue = [...(succIds.get(beId) ?? [])];
64
+ while (queue.length > 0) {
65
+ const id = queue.shift();
66
+ if (!id || chainSet.has(id))
67
+ continue;
68
+ const preds = predIds.get(id) ?? new Set();
69
+ if ([...preds].every((p) => chainSet.has(p))) {
70
+ chainSet.add(id);
71
+ chainOrder.push(id);
72
+ queue.push(...(succIds.get(id) ?? []));
73
+ }
74
+ }
75
+ // Each boundary event's chain gets its own vertical lane
76
+ let maxChainH = 0;
77
+ for (const id of chainOrder) {
78
+ const n = nodeById.get(id);
79
+ if (n)
80
+ maxChainH = Math.max(maxChainH, n.bounds.height);
81
+ }
82
+ const laneOffset = i * (maxChainH + CHAIN_V_GAP + 10);
83
+ const chainCenterY = Math.round(beNode.bounds.y + bH + CHAIN_V_GAP + maxChainH / 2 + laneOffset);
84
+ const chainStartX = Math.max(Math.round(beNode.bounds.x + bW / 2) + CHAIN_GAP, hostNode.bounds.x + hostNode.bounds.width + CHAIN_GAP);
85
+ let curX = chainStartX;
86
+ for (const id of chainOrder) {
87
+ const n = nodeById.get(id);
88
+ if (!n)
89
+ continue;
90
+ n.bounds.x = curX;
91
+ n.bounds.y = chainCenterY - Math.round(n.bounds.height / 2);
92
+ if (n.labelBounds) {
93
+ // Center label on node, but clamp so it never extends left of the node —
94
+ // wide event labels would otherwise overlap the preceding chain element.
95
+ const labelX = n.bounds.x + Math.round(n.bounds.width / 2 - n.labelBounds.width / 2);
96
+ n.labelBounds.x = Math.max(labelX, n.bounds.x);
97
+ n.labelBounds.y = n.bounds.y + n.bounds.height + 4;
98
+ }
99
+ // Advance past the node AND its label so the next element doesn't overlap.
100
+ const nodeRight = n.bounds.x + n.bounds.width;
101
+ const labelRight = n.labelBounds ? n.labelBounds.x + n.labelBounds.width : 0;
102
+ curX = Math.max(nodeRight, labelRight) + CHAIN_GAP;
103
+ }
104
+ // Re-route edges touching the boundary event or its chain
105
+ for (const edge of result.edges) {
106
+ if (!chainSet.has(edge.sourceRef))
107
+ continue;
108
+ const src = nodeById.get(edge.sourceRef);
109
+ const tgt = nodeById.get(edge.targetRef);
110
+ if (!src || !tgt)
111
+ continue;
112
+ if (edge.sourceRef === beId) {
113
+ const srcX = Math.round(src.bounds.x + bW / 2);
114
+ const srcY = Math.round(src.bounds.y + bH);
115
+ const tgtX = Math.round(tgt.bounds.x);
116
+ const tgtY = Math.round(tgt.bounds.y + tgt.bounds.height / 2);
117
+ edge.waypoints = [
118
+ { x: srcX, y: srcY },
119
+ { x: srcX, y: tgtY },
120
+ { x: tgtX, y: tgtY },
121
+ ];
122
+ }
123
+ else {
124
+ const srcX = Math.round(src.bounds.x + src.bounds.width);
125
+ const srcY = Math.round(src.bounds.y + src.bounds.height / 2);
126
+ const tgtX = Math.round(tgt.bounds.x);
127
+ const tgtY = Math.round(tgt.bounds.y + tgt.bounds.height / 2);
128
+ edge.waypoints = [
129
+ { x: srcX, y: srcY },
130
+ { x: tgtX, y: tgtY },
131
+ ];
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
10
137
  /**
11
138
  * Auto-layout a BPMN process using the Sugiyama/layered algorithm.
12
139
  *
@@ -17,10 +144,12 @@ import { layoutSubProcesses } from "./subprocess.js";
17
144
  * 4. Coordinate assignment — Fixed element sizes with spacing
18
145
  * 5. Sub-process layout — Recursive nested passes
19
146
  * 6. Edge routing — Orthogonal waypoints
20
- * 7. Overlap assertionPost-condition validation
147
+ * 7. Boundary event repositioning place events on host border
148
+ * 8. Overlap assertion — Post-condition validation
21
149
  */
22
150
  export function layoutProcess(process) {
23
151
  const result = layoutFlowNodes(process.flowElements, process.sequenceFlows);
152
+ repositionBoundaryEvents(process.flowElements, result);
24
153
  assertNoOverlap(result);
25
154
  return result;
26
155
  }
@@ -8,6 +8,9 @@
8
8
  export function assertNoOverlap(result) {
9
9
  const allBounds = [];
10
10
  for (const node of result.nodes) {
11
+ // Boundary events intentionally overlap their host activity — skip them.
12
+ if (node.type === "boundaryEvent")
13
+ continue;
11
14
  allBounds.push({ id: node.id, kind: "element", bounds: node.bounds });
12
15
  if (node.labelBounds) {
13
16
  allBounds.push({ id: `${node.id}-label`, kind: "label", bounds: node.labelBounds });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmnkit/core",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "dist/**/*.d.ts"
18
18
  ],
19
19
  "dependencies": {
20
- "@bpmnkit/feel": "0.0.18"
20
+ "@bpmnkit/feel": "0.0.19"
21
21
  },
22
22
  "description": "TypeScript-first BPMN 2.0 SDK — parse, build, layout, and optimize diagrams",
23
23
  "keywords": [