@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.
@@ -4,136 +4,9 @@ const POOL_HEADER = 30;
4
4
  const LANE_HEADER = 30;
5
5
  const PADDING = 20;
6
6
  const POOL_GAP = 30;
7
- const CHAIN_GAP = 30;
8
- const CHAIN_V_GAP = 20;
9
7
  const ANN_H = 50;
10
8
  const ANN_GAP = 60;
11
9
  const ANN_PADDING = 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
- // Collect boundary events grouped by host task id
19
- const boundaryMap = new Map();
20
- for (const el of flowElements) {
21
- if (el.type !== "boundaryEvent")
22
- continue;
23
- const list = boundaryMap.get(el.attachedToRef) ?? [];
24
- list.push(el.id);
25
- boundaryMap.set(el.attachedToRef, list);
26
- }
27
- if (boundaryMap.size === 0)
28
- return;
29
- const nodeById = new Map(result.nodes.map((n) => [n.id, n]));
30
- // Build successor / predecessor maps from edges for chain walking
31
- const succIds = new Map();
32
- const predIds = new Map();
33
- for (const edge of result.edges) {
34
- const se = succIds.get(edge.sourceRef) ?? [];
35
- se.push(edge.targetRef);
36
- succIds.set(edge.sourceRef, se);
37
- const ps = predIds.get(edge.targetRef) ?? new Set();
38
- ps.add(edge.sourceRef);
39
- predIds.set(edge.targetRef, ps);
40
- }
41
- for (const [hostId, beIds] of boundaryMap) {
42
- const hostNode = nodeById.get(hostId);
43
- if (!hostNode)
44
- continue;
45
- for (let i = 0; i < beIds.length; i++) {
46
- const beId = beIds[i];
47
- if (!beId)
48
- continue;
49
- const beNode = nodeById.get(beId);
50
- if (!beNode)
51
- continue;
52
- const bW = beNode.bounds.width;
53
- const bH = beNode.bounds.height;
54
- // Place boundary event at bottom-right of host task, stacking leftward
55
- const rightEdge = hostNode.bounds.x + hostNode.bounds.width;
56
- beNode.bounds.x = Math.round(rightEdge - bW / 2 - i * (bW + 4));
57
- beNode.bounds.y = Math.round(hostNode.bounds.y + hostNode.bounds.height - bH / 2);
58
- if (beNode.labelBounds) {
59
- beNode.labelBounds.x = beNode.bounds.x + Math.round(bW / 2 - beNode.labelBounds.width / 2);
60
- beNode.labelBounds.y = beNode.bounds.y + bH + 4;
61
- }
62
- // Collect nodes exclusively reachable from this boundary event (in BFS order)
63
- const chainSet = new Set([beId]);
64
- const chainOrder = [];
65
- const queue = [...(succIds.get(beId) ?? [])];
66
- while (queue.length > 0) {
67
- const id = queue.shift();
68
- if (!id || chainSet.has(id))
69
- continue;
70
- // Include only if every predecessor is already in the chain
71
- const preds = predIds.get(id) ?? new Set();
72
- if ([...preds].every((p) => chainSet.has(p))) {
73
- chainSet.add(id);
74
- chainOrder.push(id);
75
- queue.push(...(succIds.get(id) ?? []));
76
- }
77
- }
78
- // Find tallest chain element to compute center Y below boundary event.
79
- // Each boundary event's chain gets its own vertical lane to avoid overlaps.
80
- let maxChainH = 0;
81
- for (const id of chainOrder) {
82
- const n = nodeById.get(id);
83
- if (n)
84
- maxChainH = Math.max(maxChainH, n.bounds.height);
85
- }
86
- const laneOffset = i * (maxChainH + CHAIN_V_GAP + 10);
87
- const chainCenterY = Math.round(beNode.bounds.y + bH + CHAIN_V_GAP + maxChainH / 2 + laneOffset);
88
- const chainStartX = Math.max(Math.round(beNode.bounds.x + bW / 2) + CHAIN_GAP, hostNode.bounds.x + hostNode.bounds.width + CHAIN_GAP);
89
- let curX = chainStartX;
90
- for (const id of chainOrder) {
91
- const n = nodeById.get(id);
92
- if (!n)
93
- continue;
94
- n.bounds.x = curX;
95
- n.bounds.y = chainCenterY - Math.round(n.bounds.height / 2);
96
- if (n.labelBounds) {
97
- n.labelBounds.x = n.bounds.x + Math.round(n.bounds.width / 2 - n.labelBounds.width / 2);
98
- n.labelBounds.y = n.bounds.y + n.bounds.height + 4;
99
- }
100
- curX += n.bounds.width + CHAIN_GAP;
101
- }
102
- // Re-route all edges touching the boundary event or its chain
103
- for (const edge of result.edges) {
104
- if (!chainSet.has(edge.sourceRef))
105
- continue;
106
- const src = nodeById.get(edge.sourceRef);
107
- const tgt = nodeById.get(edge.targetRef);
108
- if (!src || !tgt)
109
- continue;
110
- if (edge.sourceRef === beId) {
111
- // Boundary event → first chain node: route down then right
112
- const srcX = Math.round(src.bounds.x + bW / 2);
113
- const srcY = Math.round(src.bounds.y + bH);
114
- const tgtX = Math.round(tgt.bounds.x);
115
- const tgtY = Math.round(tgt.bounds.y + tgt.bounds.height / 2);
116
- edge.waypoints = [
117
- { x: srcX, y: srcY },
118
- { x: srcX, y: tgtY },
119
- { x: tgtX, y: tgtY },
120
- ];
121
- }
122
- else {
123
- // Within chain: straight horizontal edge
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
- }
137
10
  function contentBbox(nodes, extra) {
138
11
  let minX = Number.POSITIVE_INFINITY;
139
12
  let minY = Number.POSITIVE_INFINITY;
@@ -432,11 +305,8 @@ export function applyAutoLayout(defs) {
432
305
  const participantId = processToParticipant.get(process.id);
433
306
  const lanes = process.laneSet?.lanes ?? [];
434
307
  const hasLanes = lanes.length > 0;
308
+ // layoutProcess now handles boundary event repositioning internally.
435
309
  const result = layoutProcess(process);
436
- // Post-process boundary events: reposition each boundary event to the bottom
437
- // of its host task, then walk its exclusive downstream chain and reposition
438
- // those nodes horizontally to the right of the host task.
439
- repositionBoundaryEvents(process.flowElements, result);
440
310
  // Re-resolve edge crossings after boundary events moved shapes
441
311
  const nodeMap = new Map(result.nodes.map((n) => [n.id, n]));
442
312
  resolveEdgeCrossings(result.edges, nodeMap);
@@ -1,4 +1,4 @@
1
- import type { BpmnDefinitions, BpmnFlowElement, BpmnSequenceFlow } from "./bpmn-model.js";
1
+ import type { BpmnAssociation, BpmnDefinitions, BpmnFlowElement, BpmnSequenceFlow, BpmnTextAnnotation } from "./bpmn-model.js";
2
2
  import type { RestConnectorConfig } from "./rest-connector.js";
3
3
  /** Options shared by all element methods. */
4
4
  export interface ElementOptions {
@@ -69,6 +69,8 @@ export interface UserTaskOptions {
69
69
  name?: string;
70
70
  /** Form key or form reference. */
71
71
  formId?: string;
72
+ /** Emit <zeebe:userTask /> to mark as a Camunda 8 native user task. */
73
+ zeebeUserTask?: boolean;
72
74
  }
73
75
  /** Options for creating a call activity. */
74
76
  export interface CallActivityOptions {
@@ -117,6 +119,19 @@ export interface IntermediateThrowEventOptions extends ElementOptions {
117
119
  /** Escalation code — creates an escalation throw event (aspirational). */
118
120
  escalationCode?: string;
119
121
  }
122
+ /** Options for an end event. */
123
+ export interface EndEventOptions extends ElementOptions {
124
+ /** Error code — creates an error end event. */
125
+ errorCode?: string;
126
+ /** Error reference ID. */
127
+ errorRef?: string;
128
+ /** Message name — creates a message end event. */
129
+ messageName?: string;
130
+ /** Signal name — creates a signal end event. */
131
+ signalName?: string;
132
+ /** Escalation code — creates an escalation end event. */
133
+ escalationCode?: string;
134
+ }
120
135
  /** Options for a boundary event. */
121
136
  export interface BoundaryEventOptions extends ElementOptions {
122
137
  /** ID of the activity this boundary event is attached to. */
@@ -212,6 +227,11 @@ export declare class BranchBuilder {
212
227
  /** @internal – true once connectTo() has been called, meaning the branch end is already wired */
213
228
  _connected: boolean;
214
229
  /** @internal */
230
+ readonly _textAnnotations: BpmnTextAnnotation[];
231
+ /** @internal */
232
+ readonly _associations: BpmnAssociation[];
233
+ private readonly _annCounters;
234
+ /** @internal */
215
235
  constructor(gatewayId: string, branchName: string);
216
236
  /** Set a FEEL condition expression on this branch's outgoing sequence flow. */
217
237
  condition(expression: string): this;
@@ -225,6 +245,10 @@ export declare class BranchBuilder {
225
245
  connectTo(targetId: string): this;
226
246
  /** @internal – ID of the last element added (or the gateway if branch is empty) */
227
247
  get _lastNodeId(): string;
248
+ /** Attach a text annotation to the element at the current cursor position. */
249
+ textAnnotation(text: string): this;
250
+ /** Attach a text annotation to an element by explicit ID. */
251
+ annotate(elementId: string, text: string): this;
228
252
  serviceTask(id: string, options: ServiceTaskOptions): this;
229
253
  userTask(id: string, options?: UserTaskOptions): this;
230
254
  scriptTask(id: string, options: ScriptTaskOptions): this;
@@ -233,7 +257,7 @@ export declare class BranchBuilder {
233
257
  businessRuleTask(id: string, options?: BusinessRuleTaskOptions): this;
234
258
  callActivity(id: string, options: CallActivityOptions): this;
235
259
  startEvent(id?: string, options?: StartEventOptions): this;
236
- endEvent(id?: string, options?: ElementOptions): this;
260
+ endEvent(id?: string, options?: EndEventOptions): this;
237
261
  intermediateThrowEvent(id?: string, options?: IntermediateThrowEventOptions): this;
238
262
  intermediateCatchEvent(id?: string, options?: IntermediateCatchEventOptions): this;
239
263
  exclusiveGateway(id: string, options?: GatewayOptions): this;
@@ -247,16 +271,37 @@ export declare class SubProcessContentBuilder {
247
271
  readonly _elements: BpmnFlowElement[];
248
272
  /** @internal */
249
273
  readonly _flows: BpmnSequenceFlow[];
274
+ /** @internal */
275
+ readonly _textAnnotations: BpmnTextAnnotation[];
276
+ /** @internal */
277
+ readonly _associations: BpmnAssociation[];
278
+ private readonly _annCounters;
250
279
  private lastNodeId;
280
+ private currentGatewayId;
281
+ private openBranchEnds;
251
282
  private addElement;
252
283
  startEvent(id?: string, options?: StartEventOptions): this;
253
- endEvent(id?: string, options?: ElementOptions): this;
284
+ endEvent(id?: string, options?: EndEventOptions): this;
285
+ intermediateThrowEvent(id?: string, options?: IntermediateThrowEventOptions): this;
286
+ intermediateCatchEvent(id?: string, options?: IntermediateCatchEventOptions): this;
254
287
  serviceTask(id: string, options: ServiceTaskOptions): this;
255
- userTask(id: string, options?: UserTaskOptions): this;
256
288
  scriptTask(id: string, options: ScriptTaskOptions): this;
289
+ userTask(id: string, options?: UserTaskOptions): this;
290
+ businessRuleTask(id: string, options?: BusinessRuleTaskOptions): this;
257
291
  callActivity(id: string, options: CallActivityOptions): this;
258
- intermediateThrowEvent(id?: string, options?: IntermediateThrowEventOptions): this;
259
- intermediateCatchEvent(id?: string, options?: IntermediateCatchEventOptions): this;
292
+ sendTask(id: string, options?: ElementOptions): this;
293
+ receiveTask(id: string, options?: ElementOptions): this;
294
+ exclusiveGateway(id: string, options?: GatewayOptions): this;
295
+ parallelGateway(id: string, options?: ElementOptions): this;
296
+ inclusiveGateway(id: string, options?: GatewayOptions): this;
297
+ eventBasedGateway(id: string, options?: ElementOptions): this;
298
+ /** Attach a text annotation to the element at the current cursor position. */
299
+ textAnnotation(text: string): this;
300
+ /** Attach a text annotation to an element by explicit ID. */
301
+ annotate(elementId: string, text: string): this;
302
+ branch(name: string, callback: (b: BranchBuilder) => void): this;
303
+ connectTo(targetId: string): this;
304
+ element(elementId: string): this;
260
305
  }
261
306
  /** Fluent builder for constructing BPMN processes. */
262
307
  export declare class ProcessBuilder {
@@ -268,13 +313,26 @@ export declare class ProcessBuilder {
268
313
  private readonly sequenceFlows;
269
314
  private readonly rootErrors;
270
315
  private readonly rootMessages;
316
+ private readonly _textAnnotations;
317
+ private readonly _associations;
318
+ private readonly _annCounters;
271
319
  private lastNodeId;
272
320
  private currentGatewayId;
273
321
  private openBranchEnds;
274
322
  private _autoLayout;
323
+ private _executionPlatformVersion;
324
+ private _serviceTaskDefaults;
275
325
  constructor(processId: string);
276
326
  /** Enable auto-layout: `build()` will run the layout engine and populate diagram interchange data. */
277
327
  withAutoLayout(): this;
328
+ /** Set the Camunda execution platform version stamped into the BPMN definitions. Defaults to `"8.9.0"`. */
329
+ executionPlatformVersion(version: string): this;
330
+ /** Set process-wide defaults applied to subsequently added elements. */
331
+ defaults(options: {
332
+ serviceTask?: {
333
+ retries?: string;
334
+ };
335
+ }): this;
278
336
  /** Set the display name for this process. */
279
337
  name(name: string): this;
280
338
  /** Set whether this process is executable. */
@@ -291,8 +349,14 @@ export declare class ProcessBuilder {
291
349
  * is completely disconnected.
292
350
  */
293
351
  addStartEvent(id?: string, options?: StartEventOptions): this;
352
+ /**
353
+ * Alias for `addStartEvent()` — begins a new disconnected parallel path.
354
+ *
355
+ * Use this for readability when modeling processes with multiple independent paths.
356
+ */
357
+ disconnectedStartEvent(id?: string, options?: StartEventOptions): this;
294
358
  /** Add an end event. */
295
- endEvent(id?: string, options?: ElementOptions): this;
359
+ endEvent(id?: string, options?: EndEventOptions): this;
296
360
  /** Add an intermediate throw event (none, message, signal, escalation). */
297
361
  intermediateThrowEvent(id?: string, options?: IntermediateThrowEventOptions): this;
298
362
  /** Add an intermediate catch event (timer, message, signal). */
@@ -304,6 +368,15 @@ export declare class ProcessBuilder {
304
368
  * They start a new outgoing chain from the boundary event itself.
305
369
  */
306
370
  boundaryEvent(id: string, options: BoundaryEventOptions): this;
371
+ /**
372
+ * Attach a boundary event to the preceding task and build its outgoing path,
373
+ * then restore the builder cursor to the preceding task so the main flow continues.
374
+ *
375
+ * @param id - ID for the boundary event element.
376
+ * @param options - Boundary event options (without `attachedTo` — inferred from cursor).
377
+ * @param handler - Callback that chains elements from the boundary event.
378
+ */
379
+ withBoundary(id: string, options: Omit<BoundaryEventOptions, "attachedTo">, handler: (b: ProcessBuilder) => void): this;
307
380
  /** Add a service task with Zeebe task definition and optional IO mappings. */
308
381
  serviceTask(id: string, options: ServiceTaskOptions): this;
309
382
  /** Add a REST connector task — syntactic sugar over `serviceTask()`. */
@@ -361,21 +434,36 @@ export declare class ProcessBuilder {
361
434
  subProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: SubProcessOptions): this;
362
435
  /** Add an event sub-process (aspirational). */
363
436
  eventSubProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: ElementOptions): this;
437
+ /** Attach a text annotation to the element at the current cursor position. */
438
+ textAnnotation(text: string): this;
439
+ /** Attach a text annotation to any flow element by explicit ID. */
440
+ annotate(elementId: string, text: string): this;
364
441
  /**
365
442
  * Build the complete BPMN definitions model.
366
443
  *
367
444
  * Resolves all forward-referenced `incoming` / `outgoing` arrays and wraps
368
445
  * the process in a {@link BpmnDefinitions} ready for XML serialization.
369
446
  */
370
- build(): BpmnDefinitions;
371
- private buildDiagram;
372
- /**
373
- * Insert matching join gateways where split-gateway branches converge
374
- * on a non-gateway target. BPMN best practice: every split has a join.
375
- */
376
- private insertJoinGateways;
377
- /** Trace backward from a node to find which split gateway it belongs to. */
378
- private traceBackToSplit;
447
+ build(options?: {
448
+ strict?: boolean;
449
+ }): BpmnDefinitions;
450
+ private validate;
379
451
  private addFlowElement;
380
452
  }
453
+ /**
454
+ * Builder for a complete BPMN definitions document containing one or more processes.
455
+ * Use `Bpmn.createDiagram(id?)` to obtain an instance.
456
+ */
457
+ export declare class DiagramBuilder {
458
+ private readonly _id;
459
+ private readonly _processes;
460
+ private readonly _errors;
461
+ private readonly _messages;
462
+ private _executionPlatformVersion;
463
+ constructor(id: string);
464
+ /** Set the Camunda execution platform version stamped into the BPMN definitions. Defaults to `"8.9.0"`. */
465
+ executionPlatformVersion(version: string): this;
466
+ process(id: string, callback: (b: ProcessBuilder) => void): this;
467
+ build(): BpmnDefinitions;
468
+ }
381
469
  //# sourceMappingURL=bpmn-builder.d.ts.map