@bpmnkit/core 0.1.2 → 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.
Files changed (42) hide show
  1. package/README.md +30 -1
  2. package/dist/bpmn/bpmn-builder.d.ts +209 -3
  3. package/dist/bpmn/bpmn-builder.js +456 -16
  4. package/dist/bpmn/bpmn-model.d.ts +110 -0
  5. package/dist/bpmn/bpmn-parser.js +1413 -528
  6. package/dist/bpmn/bpmn-serializer.js +101 -19
  7. package/dist/bpmn/compact.d.ts +17 -2
  8. package/dist/bpmn/compact.js +3 -3
  9. package/dist/bpmn/full-operations.d.ts +89 -0
  10. package/dist/bpmn/full-operations.js +478 -0
  11. package/dist/bpmn/index.d.ts +19 -0
  12. package/dist/bpmn/index.js +21 -0
  13. package/dist/bpmn/optimize/feel.js +2 -2
  14. package/dist/bpmn/optimize/patterns.js +23 -16
  15. package/dist/bpmn/optimize/tasks.js +30 -7
  16. package/dist/bpmn/optimize/utils.js +2 -4
  17. package/dist/bpmn/optimize/variable-flow.js +58 -67
  18. package/dist/bpmn/semantic-hash.d.ts +93 -0
  19. package/dist/bpmn/semantic-hash.js +155 -0
  20. package/dist/bpmn/sha256.d.ts +17 -0
  21. package/dist/bpmn/sha256.js +95 -0
  22. package/dist/bpmn/zeebe-extensions.d.ts +56 -0
  23. package/dist/bpmn/zeebe-extensions.js +79 -0
  24. package/dist/bpmn/zeebe-placement.d.ts +12 -0
  25. package/dist/bpmn/zeebe-placement.js +140 -0
  26. package/dist/errors.d.ts +40 -1
  27. package/dist/errors.js +41 -0
  28. package/dist/index.d.ts +10 -4
  29. package/dist/index.js +7 -3
  30. package/dist/layout/semantic/graph.d.ts +9 -1
  31. package/dist/layout/semantic/graph.js +42 -17
  32. package/dist/layout/semantic/route.js +102 -42
  33. package/dist/node/index.d.ts +10 -0
  34. package/dist/node/index.js +9 -0
  35. package/dist/node/write.d.ts +81 -0
  36. package/dist/node/write.js +167 -0
  37. package/dist/types/id-generator.js +11 -3
  38. package/dist/xml/index.d.ts +3 -1
  39. package/dist/xml/index.js +2 -1
  40. package/dist/xml/xml-parser.d.ts +32 -0
  41. package/dist/xml/xml-parser.js +394 -143
  42. package/package.json +8 -1
@@ -131,6 +131,42 @@ function serializeExtensionElements(extensions, bp) {
131
131
  return [];
132
132
  return [el(`${bp}:extensionElements`, {}, extensions)];
133
133
  }
134
+ /**
135
+ * The `documentation` and `extensionElements` children shared by every BPMN
136
+ * base element, in the order the schema declares them.
137
+ */
138
+ function serializeBaseChildren(owner, bp) {
139
+ const children = [];
140
+ if (owner.documentation !== undefined) {
141
+ children.push(el(`${bp}:documentation`, {}, [], owner.documentation));
142
+ }
143
+ children.push(...serializeExtensionElements(owner.extensionElements ?? [], bp));
144
+ return children;
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // Data associations
148
+ // ---------------------------------------------------------------------------
149
+ function serializeProperty(property, bp) {
150
+ const attrs = { ...property.unknownAttributes };
151
+ if (property.id !== undefined)
152
+ attrs.id = property.id;
153
+ if (property.name !== undefined)
154
+ attrs.name = property.name;
155
+ if (property.itemSubjectRef !== undefined)
156
+ attrs.itemSubjectRef = property.itemSubjectRef;
157
+ return el(`${bp}:property`, attrs, []);
158
+ }
159
+ function serializeDataAssociation(association, tag, bp) {
160
+ const attrs = { ...association.unknownAttributes };
161
+ if (association.id !== undefined)
162
+ attrs.id = association.id;
163
+ const children = association.sourceRefs.map((ref) => el(`${bp}:sourceRef`, {}, [], ref));
164
+ if (association.targetRef !== undefined) {
165
+ children.push(el(`${bp}:targetRef`, {}, [], association.targetRef));
166
+ }
167
+ children.push(...(association.unknownChildren ?? []));
168
+ return el(`${bp}:${tag}`, attrs, children);
169
+ }
134
170
  // ---------------------------------------------------------------------------
135
171
  // Multi-instance loop
136
172
  // ---------------------------------------------------------------------------
@@ -140,11 +176,15 @@ function serializeLoopCharacteristics(lc, bp) {
140
176
  const attrs = {};
141
177
  if (lc.isSequential)
142
178
  attrs.isSequential = "true";
143
- return [
144
- el(`${bp}:multiInstanceLoopCharacteristics`, attrs, [
145
- ...serializeExtensionElements(lc.extensionElements, bp),
146
- ]),
147
- ];
179
+ const children = [...serializeExtensionElements(lc.extensionElements, bp)];
180
+ if (lc.loopCardinality) {
181
+ children.push(el(`${bp}:loopCardinality`, lc.loopCardinality.attributes, [], lc.loopCardinality.text));
182
+ }
183
+ if (lc.completionCondition) {
184
+ children.push(el(`${bp}:completionCondition`, lc.completionCondition.attributes, [], lc.completionCondition.text));
185
+ }
186
+ children.push(...(lc.unknownChildren ?? []));
187
+ return [el(`${bp}:multiInstanceLoopCharacteristics`, attrs, children)];
148
188
  }
149
189
  // ---------------------------------------------------------------------------
150
190
  // Flow elements
@@ -169,6 +209,17 @@ function serializeFlowElement(fe, ns) {
169
209
  // Incoming / outgoing
170
210
  children.push(...flowRefs(fe.incoming, "incoming", bp));
171
211
  children.push(...flowRefs(fe.outgoing, "outgoing", bp));
212
+ // Data wiring
213
+ for (const property of fe.properties ?? []) {
214
+ children.push(serializeProperty(property, bp));
215
+ }
216
+ for (const association of fe.dataInputAssociations ?? []) {
217
+ children.push(serializeDataAssociation(association, "dataInputAssociation", bp));
218
+ }
219
+ for (const association of fe.dataOutputAssociations ?? []) {
220
+ children.push(serializeDataAssociation(association, "dataOutputAssociation", bp));
221
+ }
222
+ children.push(...(fe.unknownChildren ?? []));
172
223
  switch (fe.type) {
173
224
  case "startEvent":
174
225
  if (fe.isInterrupting === false)
@@ -278,8 +329,22 @@ function serializeSequenceFlow(sf, bp) {
278
329
  // ---------------------------------------------------------------------------
279
330
  // Annotations
280
331
  // ---------------------------------------------------------------------------
332
+ function serializeCategory(category, bp) {
333
+ const attrs = { ...category.unknownAttributes };
334
+ if (category.id !== undefined)
335
+ attrs.id = category.id;
336
+ if (category.name !== undefined)
337
+ attrs.name = category.name;
338
+ const children = category.categoryValues.map((value) => {
339
+ const valueAttrs = { id: value.id, ...value.unknownAttributes };
340
+ if (value.value !== undefined)
341
+ valueAttrs.value = value.value;
342
+ return el(`${bp}:categoryValue`, valueAttrs, []);
343
+ });
344
+ return el(`${bp}:category`, attrs, children);
345
+ }
281
346
  function serializeTextAnnotation(ta, bp) {
282
- const children = [];
347
+ const children = serializeBaseChildren(ta, bp);
283
348
  if (ta.text !== undefined) {
284
349
  children.push(el(`${bp}:text`, {}, [], ta.text));
285
350
  }
@@ -294,13 +359,13 @@ function serializeAssociation(a, bp) {
294
359
  };
295
360
  if (a.associationDirection !== undefined)
296
361
  attrs.associationDirection = a.associationDirection;
297
- return el(`${bp}:association`, attrs, []);
362
+ return el(`${bp}:association`, attrs, serializeBaseChildren(a, bp));
298
363
  }
299
364
  function serializeGroup(g, bp) {
300
365
  const attrs = { id: g.id, ...g.unknownAttributes };
301
366
  if (g.categoryValueRef !== undefined)
302
367
  attrs.categoryValueRef = g.categoryValueRef;
303
- return el(`${bp}:group`, attrs, []);
368
+ return el(`${bp}:group`, attrs, serializeBaseChildren(g, bp));
304
369
  }
305
370
  // ---------------------------------------------------------------------------
306
371
  // Process contents
@@ -333,16 +398,19 @@ function serializeLane(lane, bp) {
333
398
  const attrs = { id: lane.id, ...lane.unknownAttributes };
334
399
  if (lane.name !== undefined)
335
400
  attrs.name = lane.name;
336
- const children = lane.flowNodeRefs.map((ref) => el(`${bp}:flowNodeRef`, {}, [], ref));
401
+ const children = serializeBaseChildren(lane, bp);
402
+ children.push(...lane.flowNodeRefs.map((ref) => el(`${bp}:flowNodeRef`, {}, [], ref)));
337
403
  if (lane.childLaneSet) {
338
404
  children.push(serializeLaneSet(lane.childLaneSet, bp));
339
405
  }
340
406
  return el(`${bp}:lane`, attrs, children);
341
407
  }
342
408
  function serializeLaneSet(laneSet, bp) {
343
- const attrs = {};
409
+ const attrs = { ...laneSet.unknownAttributes };
344
410
  if (laneSet.id)
345
411
  attrs.id = laneSet.id;
412
+ if (laneSet.name !== undefined)
413
+ attrs.name = laneSet.name;
346
414
  return el(`${bp}:laneSet`, attrs, laneSet.lanes.map((l) => serializeLane(l, bp)));
347
415
  }
348
416
  // ---------------------------------------------------------------------------
@@ -355,12 +423,12 @@ function serializeProcess(process, ns) {
355
423
  attrs.name = process.name;
356
424
  if (process.isExecutable)
357
425
  attrs.isExecutable = "true";
358
- const children = [];
359
- children.push(...serializeExtensionElements(process.extensionElements, bp));
426
+ const children = serializeBaseChildren({ documentation: process.documentation, extensionElements: process.extensionElements }, bp);
360
427
  if (process.laneSet) {
361
428
  children.push(serializeLaneSet(process.laneSet, bp));
362
429
  }
363
430
  children.push(...serializeProcessContents(process, ns));
431
+ children.push(...(process.unknownChildren ?? []));
364
432
  return el(`${bp}:process`, attrs, children);
365
433
  }
366
434
  // ---------------------------------------------------------------------------
@@ -372,7 +440,7 @@ function serializeParticipant(p, bp) {
372
440
  attrs.name = p.name;
373
441
  if (p.processRef !== undefined)
374
442
  attrs.processRef = p.processRef;
375
- return el(`${bp}:participant`, attrs, []);
443
+ return el(`${bp}:participant`, attrs, serializeBaseChildren(p, bp));
376
444
  }
377
445
  function serializeMessageFlow(mf, bp) {
378
446
  const attrs = {
@@ -383,11 +451,13 @@ function serializeMessageFlow(mf, bp) {
383
451
  };
384
452
  if (mf.name !== undefined)
385
453
  attrs.name = mf.name;
386
- return el(`${bp}:messageFlow`, attrs, []);
454
+ if (mf.messageRef !== undefined)
455
+ attrs.messageRef = mf.messageRef;
456
+ return el(`${bp}:messageFlow`, attrs, serializeBaseChildren(mf, bp));
387
457
  }
388
458
  function serializeCollaboration(c, ns) {
389
459
  const bp = bpmnPrefix(ns);
390
- const children = [];
460
+ const children = serializeBaseChildren(c, bp);
391
461
  for (const p of c.participants) {
392
462
  children.push(serializeParticipant(p, bp));
393
463
  }
@@ -403,6 +473,7 @@ function serializeCollaboration(c, ns) {
403
473
  for (const g of c.groups ?? []) {
404
474
  children.push(serializeGroup(g, bp));
405
475
  }
476
+ children.push(...(c.unknownChildren ?? []));
406
477
  return el(`${bp}:collaboration`, { id: c.id, ...c.unknownAttributes }, children);
407
478
  }
408
479
  // ---------------------------------------------------------------------------
@@ -414,7 +485,7 @@ function serializeError(e, bp) {
414
485
  attrs.name = e.name;
415
486
  if (e.errorCode !== undefined)
416
487
  attrs.errorCode = e.errorCode;
417
- return el(`${bp}:error`, attrs, []);
488
+ return el(`${bp}:error`, { ...e.unknownAttributes, ...attrs }, serializeBaseChildren(e, bp));
418
489
  }
419
490
  function serializeEscalation(e, bp) {
420
491
  const attrs = { id: e.id };
@@ -422,19 +493,19 @@ function serializeEscalation(e, bp) {
422
493
  attrs.name = e.name;
423
494
  if (e.escalationCode !== undefined)
424
495
  attrs.escalationCode = e.escalationCode;
425
- return el(`${bp}:escalation`, attrs, []);
496
+ return el(`${bp}:escalation`, { ...e.unknownAttributes, ...attrs }, serializeBaseChildren(e, bp));
426
497
  }
427
498
  function serializeMessage(m, bp) {
428
499
  const attrs = { id: m.id, ...m.unknownAttributes };
429
500
  if (m.name !== undefined)
430
501
  attrs.name = m.name;
431
- return el(`${bp}:message`, attrs, []);
502
+ return el(`${bp}:message`, attrs, serializeBaseChildren(m, bp));
432
503
  }
433
504
  function serializeSignal(s, bp) {
434
505
  const attrs = { id: s.id };
435
506
  if (s.name !== undefined)
436
507
  attrs.name = s.name;
437
- return el(`${bp}:signal`, attrs, []);
508
+ return el(`${bp}:signal`, { ...s.unknownAttributes, ...attrs }, serializeBaseChildren(s, bp));
438
509
  }
439
510
  // ---------------------------------------------------------------------------
440
511
  // Diagram interchange
@@ -539,6 +610,17 @@ export function serializeBpmn(definitions) {
539
610
  attrs[key] = value;
540
611
  }
541
612
  const children = [];
613
+ if (definitions.documentation !== undefined) {
614
+ children.push(el(`${bp}:documentation`, {}, [], definitions.documentation));
615
+ }
616
+ // Categories supply the labels groups reference
617
+ for (const category of definitions.categories ?? []) {
618
+ children.push(serializeCategory(category, bp));
619
+ }
620
+ // Unmodelled children first: `import` and `extension` precede the root
621
+ // elements in the schema, and the rest are root elements themselves, so they
622
+ // sit legally alongside the modelled ones.
623
+ children.push(...(definitions.unknownChildren ?? []));
542
624
  // Root elements: errors, escalations, messages, signals first
543
625
  for (const e of definitions.escalations) {
544
626
  children.push(serializeEscalation(e, bp));
@@ -1,4 +1,5 @@
1
- import type { BpmnDefinitions, BpmnElementType } from "./bpmn-model.js";
1
+ import type { XmlElement } from "../types/xml-element.js";
2
+ import type { BpmnDefinitions, BpmnElementType, BpmnEventDefinition, BpmnFlowElement } from "./bpmn-model.js";
2
3
  /** A single BPMN flow node in compact form. */
3
4
  export interface CompactElement {
4
5
  id: string;
@@ -54,7 +55,18 @@ export interface CompactProcess {
54
55
  }
55
56
  /**
56
57
  * Token-efficient representation of a {@link BpmnDefinitions} document.
57
- * Produced by {@link compactify}; restored to full form by {@link expand}.
58
+ *
59
+ * **A read-only view, not a model.** It carries topology, names and the common
60
+ * Zeebe bindings and drops everything else — collaborations, participants,
61
+ * message flows, lanes, data stores, artifacts, root-level messages and errors,
62
+ * multi-instance loop characteristics, full `zeebe:ioMapping` entries and the
63
+ * diagram. Use it to show a model to a language model, and
64
+ * {@link expand} to build a *new* model from one.
65
+ *
66
+ * Do not use it as an edit path for a file you need to keep:
67
+ * `expand(compactify(defs))` is not `defs`. Edit the {@link BpmnDefinitions}
68
+ * instead — `applyBpmnOperations` applies the same operation vocabulary to the
69
+ * full model without going through this projection.
58
70
  */
59
71
  export interface CompactDiagram {
60
72
  id: string;
@@ -80,6 +92,9 @@ export interface CompactDiagram {
80
92
  * ```
81
93
  */
82
94
  export declare function compactify(defs: BpmnDefinitions): CompactDiagram;
95
+ export declare function makeEventDef(eventType: string): BpmnEventDefinition | undefined;
96
+ export declare function makeExtensions(el: CompactElement): XmlElement[];
97
+ export declare function buildFlowElement(el: CompactElement, incoming: string[], outgoing: string[]): BpmnFlowElement;
83
98
  /**
84
99
  * Restores a {@link CompactDiagram} (produced by {@link compactify} or an AI
85
100
  * model) back to a full {@link BpmnDefinitions} with auto-generated layout.
@@ -112,7 +112,7 @@ export function compactify(defs) {
112
112
  };
113
113
  }
114
114
  // ── Expand ───────────────────────────────────────────────────────────────────
115
- function makeEventDef(eventType) {
115
+ export function makeEventDef(eventType) {
116
116
  switch (eventType) {
117
117
  case "timer":
118
118
  return { type: "timer" };
@@ -138,7 +138,7 @@ function makeEventDef(eventType) {
138
138
  return undefined;
139
139
  }
140
140
  }
141
- function makeExtensions(el) {
141
+ export function makeExtensions(el) {
142
142
  const ext = [];
143
143
  if (el.jobType) {
144
144
  ext.push({ name: "zeebe:taskDefinition", attributes: { type: el.jobType }, children: [] });
@@ -226,7 +226,7 @@ function buildSubContent(children) {
226
226
  groups: [],
227
227
  };
228
228
  }
229
- function buildFlowElement(el, incoming, outgoing) {
229
+ export function buildFlowElement(el, incoming, outgoing) {
230
230
  const base = {
231
231
  id: el.id,
232
232
  name: el.name,
@@ -0,0 +1,89 @@
1
+ import type { BpmnDefinitions } from "./bpmn-model.js";
2
+ import { type CompactDiagram } from "./compact.js";
3
+ import type { BpmnOperation } from "./operations.js";
4
+ /**
5
+ * Applies the {@link BpmnOperation} vocabulary directly to a
6
+ * {@link BpmnDefinitions}.
7
+ *
8
+ * The operations were originally applied to a `CompactDiagram`, which meant
9
+ * every edit round-tripped the model through a projection that models about
10
+ * fifteen properties — so editing a task's name silently discarded the
11
+ * document's pools, lanes, data associations and `zeebe:ioMapping` detail.
12
+ * Applying the same operations here touches only what an operation names and
13
+ * leaves the rest of the element untouched.
14
+ *
15
+ * Unresolved ids are reported rather than ignored. The original implementation
16
+ * skipped any operation whose target did not exist, so a patch with a
17
+ * misspelled id reported success and changed nothing.
18
+ */
19
+ /** An operation that could not be applied, and why. */
20
+ export interface OperationProblem {
21
+ /** Position in the operation list, so a caller can point at the offender. */
22
+ index: number;
23
+ /** The operation as given. */
24
+ operation: BpmnOperation;
25
+ /** What went wrong, in a form worth showing a user. */
26
+ reason: string;
27
+ }
28
+ export interface ApplyBpmnOperationsResult {
29
+ /** The edited model. The input is never mutated. */
30
+ definitions: BpmnDefinitions;
31
+ /** How many operations took effect. */
32
+ applied: number;
33
+ /** Operations that did not, each with a reason. */
34
+ problems: OperationProblem[];
35
+ }
36
+ export interface ApplyBpmnOperationsOptions {
37
+ /**
38
+ * Throw if any operation fails, leaving the model untouched. Default `true`
39
+ * — silence is what made the previous implementation dangerous.
40
+ */
41
+ strict?: boolean;
42
+ }
43
+ /** Thrown by {@link applyBpmnOperations} in strict mode. */
44
+ export declare class OperationError extends Error {
45
+ readonly problems: OperationProblem[];
46
+ constructor(problems: OperationProblem[]);
47
+ }
48
+ /**
49
+ * Applies operations to a copy of the model.
50
+ *
51
+ * @param definitions - The model to edit. Never mutated.
52
+ * @param operations - Operations to apply, in order.
53
+ * @param options - `strict` (default `true`) throws instead of returning problems.
54
+ * @returns The edited model, how many operations landed, and what did not.
55
+ * @throws {OperationError} In strict mode, when any operation fails. Nothing is
56
+ * applied — the caller's model and the returned one are both untouched.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * import { applyBpmnOperations } from "@bpmnkit/core"
61
+ *
62
+ * const { definitions } = applyBpmnOperations(parsed, [
63
+ * { op: "rename", id: "Task_1", name: "Approve invoice" },
64
+ * ])
65
+ * ```
66
+ */
67
+ export declare function applyBpmnOperations(definitions: BpmnDefinitions, operations: readonly BpmnOperation[], options?: ApplyBpmnOperationsOptions): ApplyBpmnOperationsResult;
68
+ /**
69
+ * Applies a {@link CompactDiagram} onto an existing model as a set of changes,
70
+ * rather than expanding it into a replacement.
71
+ *
72
+ * `expand(compact)` builds a whole new model from about fifteen properties per
73
+ * element, so using it to apply an edit destroys everything the compact form
74
+ * does not carry. This walks the same input as a description of the intended
75
+ * topology and turns it into {@link BpmnOperation}s: elements that already exist
76
+ * are patched in place and keep their extensions, new ones are inserted, and
77
+ * ones the input no longer mentions are removed.
78
+ *
79
+ * Processes are only ever added, never removed — a caller sending one process of
80
+ * a multi-process document means "this is how that process should look", not
81
+ * "delete the others".
82
+ *
83
+ * @param definitions - The model to update. Never mutated.
84
+ * @param compact - The intended topology.
85
+ * @param options - `strict` (default `true`) throws instead of returning problems.
86
+ * @returns The updated model, plus what applied and what did not.
87
+ */
88
+ export declare function reconcileCompact(definitions: BpmnDefinitions, compact: CompactDiagram, options?: ApplyBpmnOperationsOptions): ApplyBpmnOperationsResult;
89
+ //# sourceMappingURL=full-operations.d.ts.map