@bpmnkit/core 0.0.26 → 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.
Files changed (48) hide show
  1. package/dist/bpmn/auto-layout.js +71 -139
  2. package/dist/bpmn/bpmn-builder.d.ts +30 -0
  3. package/dist/bpmn/bpmn-builder.js +343 -31
  4. package/dist/bpmn/di-check.d.ts +14 -0
  5. package/dist/bpmn/di-check.js +51 -0
  6. package/dist/bpmn/svg.js +14 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +1 -0
  9. package/dist/layout/annotations.d.ts +27 -0
  10. package/dist/layout/annotations.js +251 -0
  11. package/dist/layout/grid/edge-labels.d.ts +8 -0
  12. package/dist/layout/grid/edge-labels.js +126 -0
  13. package/dist/layout/grid/flow-graph.d.ts +25 -0
  14. package/dist/layout/grid/flow-graph.js +99 -0
  15. package/dist/layout/grid/grid-engine.d.ts +4 -0
  16. package/dist/layout/grid/grid-engine.js +214 -0
  17. package/dist/layout/grid/grid-router.d.ts +36 -0
  18. package/dist/layout/grid/grid-router.js +190 -0
  19. package/dist/layout/grid/grid.d.ts +43 -0
  20. package/dist/layout/grid/grid.js +174 -0
  21. package/dist/layout/grid/walker.d.ts +11 -0
  22. package/dist/layout/grid/walker.js +126 -0
  23. package/dist/layout/index.d.ts +2 -5
  24. package/dist/layout/index.js +1 -4
  25. package/dist/layout/layout-engine.d.ts +5 -15
  26. package/dist/layout/layout-engine.js +8 -478
  27. package/dist/layout/types.d.ts +2 -2
  28. package/dist/layout/types.js +6 -2
  29. package/dist/xml/xml-parser.js +5 -0
  30. package/package.json +1 -1
  31. package/dist/layout/astar.d.ts +0 -15
  32. package/dist/layout/astar.js +0 -191
  33. package/dist/layout/block-builder.d.ts +0 -37
  34. package/dist/layout/block-builder.js +0 -154
  35. package/dist/layout/block-layout.d.ts +0 -9
  36. package/dist/layout/block-layout.js +0 -163
  37. package/dist/layout/coordinates.d.ts +0 -85
  38. package/dist/layout/coordinates.js +0 -1392
  39. package/dist/layout/crossing.d.ts +0 -8
  40. package/dist/layout/crossing.js +0 -60
  41. package/dist/layout/graph.d.ts +0 -28
  42. package/dist/layout/graph.js +0 -126
  43. package/dist/layout/layers.d.ts +0 -13
  44. package/dist/layout/layers.js +0 -49
  45. package/dist/layout/routing.d.ts +0 -33
  46. package/dist/layout/routing.js +0 -622
  47. package/dist/layout/subprocess.d.ts +0 -14
  48. package/dist/layout/subprocess.js +0 -77
@@ -333,6 +333,31 @@ function makeInclusiveGatewayEl(id, options) {
333
333
  }
334
334
  return el;
335
335
  }
336
+ /**
337
+ * `subProcess`/`eventSubProcess`/`adHocSubProcess` all take a required content
338
+ * callback as their 2nd argument and an optional options object as their 3rd.
339
+ * That shape isn't demonstrated anywhere in this SDK's docs/examples, and
340
+ * callers that only see the parameter *names* (not enforced types — e.g.
341
+ * generated code run without type-checking) sometimes guess the common
342
+ * "options before callback" order instead. Detect that by runtime type and
343
+ * swap, so a reversed call still succeeds instead of throwing on `content`
344
+ * not being callable.
345
+ */
346
+ function resolveSubProcessArgs(content, options) {
347
+ if (typeof content === "function") {
348
+ return {
349
+ content: content,
350
+ options: options,
351
+ };
352
+ }
353
+ if (typeof options === "function") {
354
+ return {
355
+ content: options,
356
+ options: content,
357
+ };
358
+ }
359
+ throw new TypeError("subProcess() requires a content callback function");
360
+ }
336
361
  // ---------------------------------------------------------------------------
337
362
  // Shared graph helpers — used by ProcessBuilder and SubProcessContentBuilder
338
363
  // ---------------------------------------------------------------------------
@@ -712,6 +737,120 @@ export class BranchBuilder {
712
737
  this.openBranchEnds = savedOpenEnds;
713
738
  return this;
714
739
  }
740
+ // ---- Sub-process ----
741
+ /** Add a sub-process (mirrors `ProcessBuilder.subProcess`). */
742
+ subProcess(id, content, options) {
743
+ const resolved = resolveSubProcessArgs(content, options);
744
+ const sub = new SubProcessContentBuilder(this.rootMessages);
745
+ resolved.content(sub);
746
+ insertJoinGateways(sub._elements, sub._flows);
747
+ recomputeIncomingOutgoing(sub._elements, sub._flows);
748
+ const element = makeFlowElement(id, "subProcess", resolved.options);
749
+ if (element.type === "subProcess") {
750
+ element.flowElements = sub._elements;
751
+ element.sequenceFlows = sub._flows;
752
+ element.textAnnotations = sub._textAnnotations;
753
+ element.associations = sub._associations;
754
+ if (resolved.options?.multiInstance) {
755
+ element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
756
+ }
757
+ }
758
+ return this.addElement(element);
759
+ }
760
+ /** Add an ad-hoc sub-process (mirrors `ProcessBuilder.adHocSubProcess`). */
761
+ adHocSubProcess(id, content, options) {
762
+ const resolved = resolveSubProcessArgs(content, options);
763
+ const sub = new SubProcessContentBuilder(this.rootMessages);
764
+ resolved.content(sub);
765
+ insertJoinGateways(sub._elements, sub._flows);
766
+ recomputeIncomingOutgoing(sub._elements, sub._flows);
767
+ const zeebeExt = {};
768
+ if (resolved.options?.taskDefinition) {
769
+ zeebeExt.taskDefinition = resolved.options.taskDefinition;
770
+ }
771
+ if (resolved.options?.ioMapping) {
772
+ zeebeExt.ioMapping = {
773
+ inputs: resolved.options.ioMapping.inputs ?? [],
774
+ outputs: resolved.options.ioMapping.outputs ?? [],
775
+ };
776
+ }
777
+ if (resolved.options?.taskHeaders) {
778
+ zeebeExt.taskHeaders = {
779
+ headers: Object.entries(resolved.options.taskHeaders).map(([key, value]) => ({
780
+ key,
781
+ value,
782
+ })),
783
+ };
784
+ }
785
+ const extensionElements = zeebeExtensionsToXmlElements(zeebeExt);
786
+ const adHocAttrs = {};
787
+ if (resolved.options?.activeElementsCollection) {
788
+ adHocAttrs.activeElementsCollection = resolved.options.activeElementsCollection;
789
+ }
790
+ if (resolved.options?.outputCollection) {
791
+ adHocAttrs.outputCollection = resolved.options.outputCollection;
792
+ }
793
+ if (resolved.options?.outputElement) {
794
+ adHocAttrs.outputElement = resolved.options.outputElement;
795
+ }
796
+ if (Object.keys(adHocAttrs).length > 0) {
797
+ extensionElements.push({
798
+ name: "zeebe:adHoc",
799
+ attributes: adHocAttrs,
800
+ children: [],
801
+ });
802
+ }
803
+ const element = makeFlowElement(id, "adHocSubProcess", {
804
+ name: resolved.options?.name,
805
+ extensionElements,
806
+ });
807
+ if (resolved.options?.modelerTemplate) {
808
+ element.unknownAttributes["zeebe:modelerTemplate"] = resolved.options.modelerTemplate;
809
+ }
810
+ if (resolved.options?.modelerTemplateVersion) {
811
+ element.unknownAttributes["zeebe:modelerTemplateVersion"] =
812
+ resolved.options.modelerTemplateVersion;
813
+ }
814
+ if (resolved.options?.modelerTemplateIcon) {
815
+ element.unknownAttributes["zeebe:modelerTemplateIcon"] = resolved.options.modelerTemplateIcon;
816
+ }
817
+ if (element.type === "adHocSubProcess") {
818
+ element.flowElements = sub._elements;
819
+ element.sequenceFlows = sub._flows;
820
+ element.textAnnotations = sub._textAnnotations;
821
+ element.associations = sub._associations;
822
+ if (resolved.options?.loopCharacteristics) {
823
+ element.loopCharacteristics = buildAdHocLoopCharacteristics(resolved.options.loopCharacteristics);
824
+ }
825
+ else if (resolved.options?.multiInstance) {
826
+ element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
827
+ }
828
+ }
829
+ return this.addElement(element);
830
+ }
831
+ /** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
832
+ eventSubProcess(id, content, options) {
833
+ const resolved = resolveSubProcessArgs(content, options);
834
+ const sub = new SubProcessContentBuilder(this.rootMessages);
835
+ resolved.content(sub);
836
+ insertJoinGateways(sub._elements, sub._flows);
837
+ recomputeIncomingOutgoing(sub._elements, sub._flows);
838
+ const element = makeFlowElement(id, "subProcess", resolved.options);
839
+ if (element.type === "subProcess") {
840
+ element.triggeredByEvent = true;
841
+ element.flowElements = sub._elements;
842
+ element.sequenceFlows = sub._flows;
843
+ element.textAnnotations = sub._textAnnotations;
844
+ element.associations = sub._associations;
845
+ }
846
+ // Event sub-processes have no incoming/outgoing sequence flows and must not
847
+ // advance the branch cursor — mirrors ProcessBuilder.eventSubProcess.
848
+ if (this._elements.some((n) => n.id === element.id)) {
849
+ throw new Error(`Duplicate element ID "${element.id}"`);
850
+ }
851
+ this._elements.push(element);
852
+ return this;
853
+ }
715
854
  /**
716
855
  * Create a named branch from the last gateway added inside this branch.
717
856
  *
@@ -976,6 +1115,172 @@ export class SubProcessContentBuilder {
976
1115
  this.currentGatewayId = undefined;
977
1116
  return this;
978
1117
  }
1118
+ // ---- Boundary events ----
1119
+ /**
1120
+ * Add a boundary event attached to an existing activity in this sub-process.
1121
+ *
1122
+ * The boundary event is NOT connected by a sequence flow — it attaches via
1123
+ * `attachedToRef`. The builder cursor advances to the boundary event so
1124
+ * subsequent elements chain from it. Use `withBoundary()` if you want the
1125
+ * cursor to return to the task afterward.
1126
+ */
1127
+ boundaryEvent(id, options) {
1128
+ const element = makeFlowElement(id, "boundaryEvent", options);
1129
+ if (element.type === "boundaryEvent") {
1130
+ element.attachedToRef = options.attachedTo;
1131
+ element.cancelActivity = options.cancelActivity;
1132
+ element.eventDefinitions = buildEventDefinitions(options);
1133
+ }
1134
+ // Push directly — no sequence flow, boundary events attach via attachedToRef
1135
+ this._elements.push(element);
1136
+ this.lastNodeId = element.id;
1137
+ return this;
1138
+ }
1139
+ /**
1140
+ * Attach a boundary event to the preceding task and build its outgoing path,
1141
+ * then restore the builder cursor to the preceding task so the main flow continues.
1142
+ *
1143
+ * @param id - ID for the boundary event element.
1144
+ * @param options - Boundary event options (without `attachedTo` — inferred from cursor).
1145
+ * @param handler - Callback that chains elements from the boundary event.
1146
+ */
1147
+ withBoundary(id, options, handler) {
1148
+ const attachedTo = this.lastNodeId;
1149
+ if (!attachedTo) {
1150
+ throw new Error("withBoundary() must follow a task element inside the sub-process. Current builder position has no active task.");
1151
+ }
1152
+ const attachedEl = this._elements.find((n) => n.id === attachedTo);
1153
+ if (attachedEl?.type === "boundaryEvent") {
1154
+ throw new Error("withBoundary() cannot attach to a boundary event. It must follow a task or activity element.");
1155
+ }
1156
+ const savedLast = this.lastNodeId;
1157
+ const savedGateway = this.currentGatewayId;
1158
+ const savedOpenEnds = [...this.openBranchEnds];
1159
+ this.openBranchEnds = [];
1160
+ // Create and push the boundary event (no sequence flow)
1161
+ this.boundaryEvent(id, { ...options, attachedTo });
1162
+ // Build the boundary event's outgoing path
1163
+ handler(this);
1164
+ // Restore cursor to the task so the sub-process main flow continues
1165
+ this.lastNodeId = savedLast;
1166
+ this.currentGatewayId = savedGateway;
1167
+ this.openBranchEnds = savedOpenEnds;
1168
+ return this;
1169
+ }
1170
+ // ---- Sub-process ----
1171
+ /** Add a sub-process nested inside this sub-process's content. */
1172
+ subProcess(id, content, options) {
1173
+ const resolved = resolveSubProcessArgs(content, options);
1174
+ const sub = new SubProcessContentBuilder(this.rootMessages);
1175
+ resolved.content(sub);
1176
+ insertJoinGateways(sub._elements, sub._flows);
1177
+ recomputeIncomingOutgoing(sub._elements, sub._flows);
1178
+ const element = makeFlowElement(id, "subProcess", resolved.options);
1179
+ if (element.type === "subProcess") {
1180
+ element.flowElements = sub._elements;
1181
+ element.sequenceFlows = sub._flows;
1182
+ element.textAnnotations = sub._textAnnotations;
1183
+ element.associations = sub._associations;
1184
+ if (resolved.options?.multiInstance) {
1185
+ element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
1186
+ }
1187
+ }
1188
+ return this.addElement(element);
1189
+ }
1190
+ /** Add an ad-hoc sub-process nested inside this sub-process's content. */
1191
+ adHocSubProcess(id, content, options) {
1192
+ const resolved = resolveSubProcessArgs(content, options);
1193
+ const sub = new SubProcessContentBuilder(this.rootMessages);
1194
+ resolved.content(sub);
1195
+ insertJoinGateways(sub._elements, sub._flows);
1196
+ recomputeIncomingOutgoing(sub._elements, sub._flows);
1197
+ const zeebeExt = {};
1198
+ if (resolved.options?.taskDefinition) {
1199
+ zeebeExt.taskDefinition = resolved.options.taskDefinition;
1200
+ }
1201
+ if (resolved.options?.ioMapping) {
1202
+ zeebeExt.ioMapping = {
1203
+ inputs: resolved.options.ioMapping.inputs ?? [],
1204
+ outputs: resolved.options.ioMapping.outputs ?? [],
1205
+ };
1206
+ }
1207
+ if (resolved.options?.taskHeaders) {
1208
+ zeebeExt.taskHeaders = {
1209
+ headers: Object.entries(resolved.options.taskHeaders).map(([key, value]) => ({
1210
+ key,
1211
+ value,
1212
+ })),
1213
+ };
1214
+ }
1215
+ const extensionElements = zeebeExtensionsToXmlElements(zeebeExt);
1216
+ const adHocAttrs = {};
1217
+ if (resolved.options?.activeElementsCollection) {
1218
+ adHocAttrs.activeElementsCollection = resolved.options.activeElementsCollection;
1219
+ }
1220
+ if (resolved.options?.outputCollection) {
1221
+ adHocAttrs.outputCollection = resolved.options.outputCollection;
1222
+ }
1223
+ if (resolved.options?.outputElement) {
1224
+ adHocAttrs.outputElement = resolved.options.outputElement;
1225
+ }
1226
+ if (Object.keys(adHocAttrs).length > 0) {
1227
+ extensionElements.push({
1228
+ name: "zeebe:adHoc",
1229
+ attributes: adHocAttrs,
1230
+ children: [],
1231
+ });
1232
+ }
1233
+ const element = makeFlowElement(id, "adHocSubProcess", {
1234
+ name: resolved.options?.name,
1235
+ extensionElements,
1236
+ });
1237
+ if (resolved.options?.modelerTemplate) {
1238
+ element.unknownAttributes["zeebe:modelerTemplate"] = resolved.options.modelerTemplate;
1239
+ }
1240
+ if (resolved.options?.modelerTemplateVersion) {
1241
+ element.unknownAttributes["zeebe:modelerTemplateVersion"] =
1242
+ resolved.options.modelerTemplateVersion;
1243
+ }
1244
+ if (resolved.options?.modelerTemplateIcon) {
1245
+ element.unknownAttributes["zeebe:modelerTemplateIcon"] = resolved.options.modelerTemplateIcon;
1246
+ }
1247
+ if (element.type === "adHocSubProcess") {
1248
+ element.flowElements = sub._elements;
1249
+ element.sequenceFlows = sub._flows;
1250
+ element.textAnnotations = sub._textAnnotations;
1251
+ element.associations = sub._associations;
1252
+ if (resolved.options?.loopCharacteristics) {
1253
+ element.loopCharacteristics = buildAdHocLoopCharacteristics(resolved.options.loopCharacteristics);
1254
+ }
1255
+ else if (resolved.options?.multiInstance) {
1256
+ element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
1257
+ }
1258
+ }
1259
+ return this.addElement(element);
1260
+ }
1261
+ /** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
1262
+ eventSubProcess(id, content, options) {
1263
+ const resolved = resolveSubProcessArgs(content, options);
1264
+ const sub = new SubProcessContentBuilder(this.rootMessages);
1265
+ resolved.content(sub);
1266
+ insertJoinGateways(sub._elements, sub._flows);
1267
+ recomputeIncomingOutgoing(sub._elements, sub._flows);
1268
+ const element = makeFlowElement(id, "subProcess", resolved.options);
1269
+ if (element.type === "subProcess") {
1270
+ element.triggeredByEvent = true;
1271
+ element.flowElements = sub._elements;
1272
+ element.sequenceFlows = sub._flows;
1273
+ element.textAnnotations = sub._textAnnotations;
1274
+ element.associations = sub._associations;
1275
+ }
1276
+ // Event sub-processes have no incoming/outgoing sequence flows and must not
1277
+ // advance the cursor — mirrors ProcessBuilder.eventSubProcess.
1278
+ if (this._elements.some((n) => n.id === element.id)) {
1279
+ throw new Error(`Duplicate element ID "${element.id}" in sub-process`);
1280
+ }
1281
+ this._elements.push(element);
1282
+ return this;
1283
+ }
979
1284
  }
980
1285
  // ---------------------------------------------------------------------------
981
1286
  // Process builder (top-level entry point)
@@ -1374,36 +1679,40 @@ export class ProcessBuilder {
1374
1679
  // ---- Sub-processes ----
1375
1680
  /** Add an ad-hoc sub-process with optional AI agent or multi-instance configuration. */
1376
1681
  adHocSubProcess(id, content, options) {
1682
+ const resolved = resolveSubProcessArgs(content, options);
1377
1683
  const sub = new SubProcessContentBuilder(this.rootMessages);
1378
- content(sub);
1684
+ resolved.content(sub);
1379
1685
  insertJoinGateways(sub._elements, sub._flows);
1380
1686
  recomputeIncomingOutgoing(sub._elements, sub._flows);
1381
1687
  const zeebeExt = {};
1382
- if (options?.taskDefinition) {
1383
- zeebeExt.taskDefinition = options.taskDefinition;
1688
+ if (resolved.options?.taskDefinition) {
1689
+ zeebeExt.taskDefinition = resolved.options.taskDefinition;
1384
1690
  }
1385
- if (options?.ioMapping) {
1691
+ if (resolved.options?.ioMapping) {
1386
1692
  zeebeExt.ioMapping = {
1387
- inputs: options.ioMapping.inputs ?? [],
1388
- outputs: options.ioMapping.outputs ?? [],
1693
+ inputs: resolved.options.ioMapping.inputs ?? [],
1694
+ outputs: resolved.options.ioMapping.outputs ?? [],
1389
1695
  };
1390
1696
  }
1391
- if (options?.taskHeaders) {
1697
+ if (resolved.options?.taskHeaders) {
1392
1698
  zeebeExt.taskHeaders = {
1393
- headers: Object.entries(options.taskHeaders).map(([key, value]) => ({ key, value })),
1699
+ headers: Object.entries(resolved.options.taskHeaders).map(([key, value]) => ({
1700
+ key,
1701
+ value,
1702
+ })),
1394
1703
  };
1395
1704
  }
1396
1705
  const extensionElements = zeebeExtensionsToXmlElements(zeebeExt);
1397
1706
  // zeebe:adHoc element
1398
1707
  const adHocAttrs = {};
1399
- if (options?.activeElementsCollection) {
1400
- adHocAttrs.activeElementsCollection = options.activeElementsCollection;
1708
+ if (resolved.options?.activeElementsCollection) {
1709
+ adHocAttrs.activeElementsCollection = resolved.options.activeElementsCollection;
1401
1710
  }
1402
- if (options?.outputCollection) {
1403
- adHocAttrs.outputCollection = options.outputCollection;
1711
+ if (resolved.options?.outputCollection) {
1712
+ adHocAttrs.outputCollection = resolved.options.outputCollection;
1404
1713
  }
1405
- if (options?.outputElement) {
1406
- adHocAttrs.outputElement = options.outputElement;
1714
+ if (resolved.options?.outputElement) {
1715
+ adHocAttrs.outputElement = resolved.options.outputElement;
1407
1716
  }
1408
1717
  if (Object.keys(adHocAttrs).length > 0) {
1409
1718
  extensionElements.push({
@@ -1413,28 +1722,29 @@ export class ProcessBuilder {
1413
1722
  });
1414
1723
  }
1415
1724
  const element = makeFlowElement(id, "adHocSubProcess", {
1416
- name: options?.name,
1725
+ name: resolved.options?.name,
1417
1726
  extensionElements,
1418
1727
  });
1419
- if (options?.modelerTemplate) {
1420
- element.unknownAttributes["zeebe:modelerTemplate"] = options.modelerTemplate;
1728
+ if (resolved.options?.modelerTemplate) {
1729
+ element.unknownAttributes["zeebe:modelerTemplate"] = resolved.options.modelerTemplate;
1421
1730
  }
1422
- if (options?.modelerTemplateVersion) {
1423
- element.unknownAttributes["zeebe:modelerTemplateVersion"] = options.modelerTemplateVersion;
1731
+ if (resolved.options?.modelerTemplateVersion) {
1732
+ element.unknownAttributes["zeebe:modelerTemplateVersion"] =
1733
+ resolved.options.modelerTemplateVersion;
1424
1734
  }
1425
- if (options?.modelerTemplateIcon) {
1426
- element.unknownAttributes["zeebe:modelerTemplateIcon"] = options.modelerTemplateIcon;
1735
+ if (resolved.options?.modelerTemplateIcon) {
1736
+ element.unknownAttributes["zeebe:modelerTemplateIcon"] = resolved.options.modelerTemplateIcon;
1427
1737
  }
1428
1738
  if (element.type === "adHocSubProcess") {
1429
1739
  element.flowElements = sub._elements;
1430
1740
  element.sequenceFlows = sub._flows;
1431
1741
  element.textAnnotations = sub._textAnnotations;
1432
1742
  element.associations = sub._associations;
1433
- if (options?.loopCharacteristics) {
1434
- element.loopCharacteristics = buildAdHocLoopCharacteristics(options.loopCharacteristics);
1743
+ if (resolved.options?.loopCharacteristics) {
1744
+ element.loopCharacteristics = buildAdHocLoopCharacteristics(resolved.options.loopCharacteristics);
1435
1745
  }
1436
- else if (options?.multiInstance) {
1437
- element.loopCharacteristics = buildMultiInstance(options.multiInstance);
1746
+ else if (resolved.options?.multiInstance) {
1747
+ element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
1438
1748
  }
1439
1749
  }
1440
1750
  this.addFlowElement(element);
@@ -1442,18 +1752,19 @@ export class ProcessBuilder {
1442
1752
  }
1443
1753
  /** Add a sub-process (aspirational). */
1444
1754
  subProcess(id, content, options) {
1755
+ const resolved = resolveSubProcessArgs(content, options);
1445
1756
  const sub = new SubProcessContentBuilder(this.rootMessages);
1446
- content(sub);
1757
+ resolved.content(sub);
1447
1758
  insertJoinGateways(sub._elements, sub._flows);
1448
1759
  recomputeIncomingOutgoing(sub._elements, sub._flows);
1449
- const element = makeFlowElement(id, "subProcess", options);
1760
+ const element = makeFlowElement(id, "subProcess", resolved.options);
1450
1761
  if (element.type === "subProcess") {
1451
1762
  element.flowElements = sub._elements;
1452
1763
  element.sequenceFlows = sub._flows;
1453
1764
  element.textAnnotations = sub._textAnnotations;
1454
1765
  element.associations = sub._associations;
1455
- if (options?.multiInstance) {
1456
- element.loopCharacteristics = buildMultiInstance(options.multiInstance);
1766
+ if (resolved.options?.multiInstance) {
1767
+ element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
1457
1768
  }
1458
1769
  }
1459
1770
  this.addFlowElement(element);
@@ -1461,11 +1772,12 @@ export class ProcessBuilder {
1461
1772
  }
1462
1773
  /** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
1463
1774
  eventSubProcess(id, content, options) {
1775
+ const resolved = resolveSubProcessArgs(content, options);
1464
1776
  const sub = new SubProcessContentBuilder(this.rootMessages);
1465
- content(sub);
1777
+ resolved.content(sub);
1466
1778
  insertJoinGateways(sub._elements, sub._flows);
1467
1779
  recomputeIncomingOutgoing(sub._elements, sub._flows);
1468
- const element = makeFlowElement(id, "subProcess", options);
1780
+ const element = makeFlowElement(id, "subProcess", resolved.options);
1469
1781
  if (element.type === "subProcess") {
1470
1782
  element.triggeredByEvent = true;
1471
1783
  element.flowElements = sub._elements;
@@ -0,0 +1,14 @@
1
+ import type { BpmnDefinitions } from "./bpmn-model.js";
2
+ export interface DiCompleteness {
3
+ missingShapes: string[];
4
+ missingEdges: string[];
5
+ }
6
+ /**
7
+ * Assert the diagram DI is complete: every flow node (recursively, incl.
8
+ * subprocess children and boundary events) and text annotation has a
9
+ * BPMNShape; every sequence flow, association and message flow has a
10
+ * BPMNEdge. Lanes are ignored (no reliable lane DI — matches the
11
+ * tmp/02-di-check.cjs rule this ports).
12
+ */
13
+ export declare function checkDiCompleteness(defs: BpmnDefinitions): DiCompleteness;
14
+ //# sourceMappingURL=di-check.d.ts.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Assert the diagram DI is complete: every flow node (recursively, incl.
3
+ * subprocess children and boundary events) and text annotation has a
4
+ * BPMNShape; every sequence flow, association and message flow has a
5
+ * BPMNEdge. Lanes are ignored (no reliable lane DI — matches the
6
+ * tmp/02-di-check.cjs rule this ports).
7
+ */
8
+ export function checkDiCompleteness(defs) {
9
+ const shapes = new Set();
10
+ const edges = new Set();
11
+ for (const d of defs.diagrams) {
12
+ for (const s of d.plane.shapes)
13
+ shapes.add(s.bpmnElement);
14
+ for (const e of d.plane.edges)
15
+ edges.add(e.bpmnElement);
16
+ }
17
+ const missingShapes = [];
18
+ const missingEdges = [];
19
+ function walkElements(els, flows) {
20
+ for (const el of els) {
21
+ if (!shapes.has(el.id))
22
+ missingShapes.push(el.id);
23
+ const sub = el;
24
+ if (sub.flowElements?.length)
25
+ walkElements(sub.flowElements, sub.sequenceFlows ?? []);
26
+ }
27
+ for (const f of flows) {
28
+ if (!edges.has(f.id))
29
+ missingEdges.push(f.id);
30
+ }
31
+ }
32
+ for (const p of defs.processes) {
33
+ walkElements(p.flowElements, p.sequenceFlows);
34
+ for (const ta of p.textAnnotations)
35
+ if (!shapes.has(ta.id))
36
+ missingShapes.push(ta.id);
37
+ for (const a of p.associations)
38
+ if (!edges.has(a.id))
39
+ missingEdges.push(a.id);
40
+ }
41
+ for (const c of defs.collaborations) {
42
+ for (const part of c.participants)
43
+ if (!shapes.has(part.id))
44
+ missingShapes.push(part.id);
45
+ for (const mf of c.messageFlows)
46
+ if (!edges.has(mf.id))
47
+ missingEdges.push(mf.id);
48
+ }
49
+ return { missingShapes, missingEdges };
50
+ }
51
+ //# sourceMappingURL=di-check.js.map
package/dist/bpmn/svg.js CHANGED
@@ -119,6 +119,9 @@ export function exportSvg(defs, options) {
119
119
  }
120
120
  }
121
121
  // ── Shapes ────────────────────────────────────────────────────────────────
122
+ // Pool/lane backgrounds are opaque and must render first, or they paint over
123
+ // the flow-node shapes and edges nested inside them.
124
+ const containerParts = [];
122
125
  const shapeParts = [];
123
126
  const labelParts = [];
124
127
  for (const shape of plane.shapes) {
@@ -126,6 +129,7 @@ export function exportSvg(defs, options) {
126
129
  const el = idx.elements.get(shape.bpmnElement);
127
130
  const type = el?.type ?? "";
128
131
  let inner;
132
+ let isContainer = false;
129
133
  if (isEvent(type)) {
130
134
  inner = renderEvent(el, width, height, t);
131
135
  }
@@ -140,9 +144,11 @@ export function exportSvg(defs, options) {
140
144
  }
141
145
  else if (idx.participants.has(shape.bpmnElement)) {
142
146
  inner = renderPool(idx.participants.get(shape.bpmnElement), width, height, t);
147
+ isContainer = true;
143
148
  }
144
149
  else if (idx.lanes.has(shape.bpmnElement)) {
145
150
  inner = renderLane(idx.lanes.get(shape.bpmnElement), width, height, t);
151
+ isContainer = true;
146
152
  }
147
153
  else {
148
154
  inner = "";
@@ -152,7 +158,13 @@ export function exportSvg(defs, options) {
152
158
  inner = renderTask(el, width, height, t);
153
159
  }
154
160
  if (inner) {
155
- shapeParts.push(`<g transform="translate(${x} ${y})">${inner}</g>`);
161
+ const g = `<g transform="translate(${x} ${y})">${inner}</g>`;
162
+ if (isContainer) {
163
+ containerParts.push(g);
164
+ }
165
+ else {
166
+ shapeParts.push(g);
167
+ }
156
168
  }
157
169
  // External labels for events and gateways
158
170
  const isExtLabel = isEvent(type) || isGateway(type);
@@ -177,6 +189,7 @@ export function exportSvg(defs, options) {
177
189
  ` <path d="M0,0 L8,3 L0,6 Z" fill="${arrowFill}"/>`,
178
190
  " </marker>",
179
191
  "</defs>",
192
+ ...containerParts,
180
193
  ...edgeParts,
181
194
  ...shapeParts,
182
195
  ...labelParts,
package/dist/index.d.ts CHANGED
@@ -4,6 +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 { checkDiCompleteness } from "./bpmn/di-check.js";
8
+ export type { DiCompleteness } from "./bpmn/di-check.js";
7
9
  export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
8
10
  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";
9
11
  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";
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 { checkDiCompleteness } from "./bpmn/di-check.js";
6
7
  export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
7
8
  export { zeebeExtensionsToXmlElements } from "./bpmn/zeebe-extensions.js";
8
9
  export { Dmn, layoutDmn, benchmarkDmnLayout, compactifyDmn, expandDmn } from "./dmn/index.js";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Text-annotation packer. Port of the layout phase of
3
+ * tmp/01-annotation-layouting.cjs (lines 96-389), adapted to operate on
4
+ * already-parsed LayoutNode[] / Bounds instead of regex-parsed BPMN DI
5
+ * shapes. Constants, formulas and control flow match the reference exactly.
6
+ */
7
+ import type { BpmnProcess } from "../bpmn/bpmn-model.js";
8
+ import type { Bounds, LayoutNode, Waypoint } from "./types.js";
9
+ /**
10
+ * Sizes and packs text annotations around their linked elements without
11
+ * overlapping each other or any other layout node (incl. node labels).
12
+ * Returns final Bounds per annotation id; annotations with no resolvable
13
+ * association target (no association at all, or the linked element isn't
14
+ * in `layoutNodes`) still get an entry — they're placed at a fixed fallback
15
+ * origin and pushed clear of everything else already placed, mirroring the
16
+ * pre-port fallback in auto-layout.ts's computeAnnotationLocalBounds.
17
+ */
18
+ export declare function packAnnotations(process: BpmnProcess, layoutNodes: LayoutNode[]): Map<string, Bounds>;
19
+ /**
20
+ * Edge-to-edge, clamped association waypoints between a linked element and
21
+ * its annotation. Port of `chooseWaypoints` (tmp/01-annotation-layouting.cjs:365-389).
22
+ */
23
+ export declare function associationWaypoints(elementBounds: Bounds, annotationBounds: Bounds): {
24
+ pElem: Waypoint;
25
+ pAnn: Waypoint;
26
+ };
27
+ //# sourceMappingURL=annotations.d.ts.map