@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
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![ai-assisted](https://img.shields.io/badge/AI--assisted-claude-8b5cf6?style=flat-square)](https://github.com/bpmnkit/monorepo)
10
10
  [![experimental](https://img.shields.io/badge/status-experimental-f59e0b?style=flat-square)](https://github.com/bpmnkit/monorepo)
11
11
 
12
- [Website](https://bpmnkit.com) · [Documentation](https://docs.bpmnkit.com) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/packages/core/CHANGELOG.md)
12
+ [Website](https://bpmnkit.com) · [Documentation](https://bpmnkit.com/docs) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/packages/core/CHANGELOG.md)
13
13
  </div>
14
14
 
15
15
  ---
@@ -139,6 +139,35 @@ const outXml = Bpmn.export(restored)
139
139
  | `Bpmn.makeEmpty(processId?, name?)` | Minimal BPMN XML with one start event |
140
140
  | `Bpmn.SAMPLE_XML` | 3-node sample diagram string |
141
141
 
142
+ ### Semantics
143
+
144
+ | Export | Description |
145
+ |--------|-------------|
146
+ | `semanticHash(defs)` | SHA-256 of the model, excluding the diagram. Unchanged by layout |
147
+ | `projectSemantics(defs)` | The canonical, presentation-free projection the hash covers |
148
+ | `diffSemantics(a, b)` | What changed between two models, keyed by element id |
149
+
150
+ ### Editing
151
+
152
+ | Export | Description |
153
+ |--------|-------------|
154
+ | `applyBpmnOperations(defs, ops)` | Apply edit operations to the full model. Strict: unresolved ids throw |
155
+ | `reconcileCompact(defs, compact)` | Apply a compact diagram as changes, keeping what compact cannot carry |
156
+ | `compactify(defs)` | Read-only token-efficient view for LLM prompts. Lossy — not an edit path |
157
+
158
+ ### Writing files (`@bpmnkit/core/node`)
159
+
160
+ | Export | Description |
161
+ |--------|-------------|
162
+ | `writeBpmn(defs, opts)` | Serialize, read back, verify the model survived, then write atomically |
163
+
164
+ ```typescript
165
+ import { writeBpmn } from "@bpmnkit/core/node"
166
+
167
+ // Refuses rather than overwrite; pass force: true to replace.
168
+ const { semanticHash, changes } = await writeBpmn(defs, { output: "flow.bpmn" })
169
+ ```
170
+
142
171
  ### DMN
143
172
 
144
173
  | Export | Description |
@@ -149,6 +149,52 @@ export interface GatewayOptions extends ElementOptions {
149
149
  /** ID of the default sequence flow (set manually; prefer branch().defaultFlow()). */
150
150
  defaultFlow?: string;
151
151
  }
152
+ /** Options for {@link ProcessBuilder.build}. */
153
+ export interface BuildOptions {
154
+ /**
155
+ * Refuse to infer join gateways, so converging branches must be declared.
156
+ *
157
+ * Generated code should set this: the builder's inference is a help to someone
158
+ * reading the chain they just wrote, and a silent topology change to a model
159
+ * that cannot see what it did not emit.
160
+ */
161
+ explicitJoins?: boolean;
162
+ /**
163
+ * The former name for {@link BuildOptions.explicitJoins}, still honoured.
164
+ *
165
+ * Renamed because "strict" says nothing about what it is strict *about*, and
166
+ * because `applyBpmnOperations` takes a `strict` that means something else
167
+ * entirely — whether to throw or report problems.
168
+ *
169
+ * @deprecated Use `explicitJoins`.
170
+ */
171
+ strict?: boolean;
172
+ }
173
+ /** Options for a collaboration participant (a pool). */
174
+ export interface ParticipantOptions {
175
+ /** Pool label. */
176
+ name?: string;
177
+ /** Id of the process this pool executes. Omit for a black box. */
178
+ processId?: string;
179
+ }
180
+ /** Options for a root-level message declared on a diagram. */
181
+ export interface DiagramMessageOptions {
182
+ /** Message name, which is what Camunda 8 publishes against. */
183
+ name?: string;
184
+ /** FEEL expression Camunda 8 correlates published messages on (`zeebe:subscription`). */
185
+ correlationKey?: string;
186
+ }
187
+ /** Options for a message flow between two pools. */
188
+ export interface MessageFlowOptions {
189
+ /** Id of the participant, or of a flow node inside one, the message leaves. */
190
+ source: string;
191
+ /** Id of the participant, or of a flow node inside one, the message reaches. */
192
+ target: string;
193
+ /** Flow label. */
194
+ name?: string;
195
+ /** Id of a message declared with `.message()`. */
196
+ messageRef?: string;
197
+ }
152
198
  /** Options for an intermediate catch event. */
153
199
  export interface IntermediateCatchEventOptions extends ElementOptions {
154
200
  /** Timer duration (ISO 8601) — creates a timer catch event. */
@@ -288,6 +334,8 @@ export interface AdHocSubProcessOptions extends ElementOptions {
288
334
  export declare class BranchBuilder {
289
335
  /** @internal */
290
336
  readonly _elements: BpmnFlowElement[];
337
+ /** Ids in `_elements`, for O(1) duplicate checks. */
338
+ private readonly _ids;
291
339
  /** @internal */
292
340
  readonly _flows: BpmnSequenceFlow[];
293
341
  /** @internal */
@@ -387,6 +435,8 @@ export declare class BranchBuilder {
387
435
  export declare class SubProcessContentBuilder {
388
436
  /** @internal */
389
437
  readonly _elements: BpmnFlowElement[];
438
+ /** Ids in `_elements`, for O(1) duplicate checks. */
439
+ private readonly _ids;
390
440
  /** @internal */
391
441
  readonly _flows: BpmnSequenceFlow[];
392
442
  /** @internal */
@@ -464,8 +514,12 @@ export declare class ProcessBuilder {
464
514
  private readonly processId;
465
515
  private processName?;
466
516
  private _isExecutable;
517
+ /** Whether `executable()` was called, so continue-mode leaves it alone if not. */
518
+ private _executableSet;
467
519
  private _versionTag?;
468
520
  private readonly flowElements;
521
+ /** Ids in `flowElements`, for O(1) duplicate and existence checks. */
522
+ private readonly elementIds;
469
523
  private readonly sequenceFlows;
470
524
  private readonly rootErrors;
471
525
  private readonly rootMessages;
@@ -481,7 +535,69 @@ export declare class ProcessBuilder {
481
535
  private _executionPlatformVersion;
482
536
  private _serviceTaskDefaults;
483
537
  private _savedMainFlowId;
538
+ /** Set by {@link ProcessBuilder.from}; makes `build()` update rather than generate. */
539
+ private _source?;
540
+ /** Pre-existing flow endpoints, so `build()` can prove it did not rewire them. */
541
+ private _sourceFlowTargets;
542
+ /** An `insertAfter` flow waiting to be reattached to what gets built next. */
543
+ private _pendingSpliceFlowId;
544
+ /** Flows the caller deliberately spliced, exempt from the rewiring guard. */
545
+ private readonly _splicedFlowIds;
484
546
  constructor(processId: string);
547
+ /**
548
+ * Continues an existing model rather than generating a new one.
549
+ *
550
+ * `build()` then returns *that document* with this process's contents
551
+ * replaced, so everything the builder has no opinion about — other processes,
552
+ * the collaboration, lanes, diagram interchange, root elements, imports,
553
+ * unmodelled content — is still there afterwards. Generating a replacement
554
+ * from scratch is what loses those.
555
+ *
556
+ * The input is not mutated; the builder works on a copy.
557
+ *
558
+ * @param definitions - The parsed model to continue.
559
+ * @param processId - Which process to continue. Named explicitly, because
560
+ * "the first process" is a guess that goes wrong on a collaboration.
561
+ * @example
562
+ * ```typescript
563
+ * const updated = ProcessBuilder.from(Bpmn.parse(xml), "order")
564
+ * .at("validate")
565
+ * .serviceTask("notify", { name: "Notify", taskType: "notify" })
566
+ * .build()
567
+ * ```
568
+ */
569
+ static from(definitions: BpmnDefinitions, processId: string): ProcessBuilder;
570
+ /**
571
+ * Moves the cursor to an existing flow node, so the next call chains from it.
572
+ *
573
+ * @param nodeId - A flow node directly contained by this process. Nodes inside
574
+ * a sub-process are not reachable: continuing into one means building that
575
+ * sub-process, not this one.
576
+ */
577
+ at(nodeId: string): this;
578
+ /**
579
+ * Splices what you build next into the path leaving an existing node.
580
+ *
581
+ * `insertAfter("validate")` followed by `.serviceTask("notify", …)` turns
582
+ * `validate → end` into `validate → notify → end`. The existing flow keeps its
583
+ * id and its target and only changes where it starts, so an edge nobody asked
584
+ * to move keeps its identity in the diagram and in a diff.
585
+ *
586
+ * This is the counterpart to {@link at}, which continues from a node whose
587
+ * path is open. Which one you mean is not guessable, so it is not guessed.
588
+ *
589
+ * @param nodeId - A flow node with exactly one outgoing sequence flow.
590
+ */
591
+ insertAfter(nodeId: string): this;
592
+ /** Points the cursor at an existing node and forgets any branch state. */
593
+ private moveCursor;
594
+ /**
595
+ * Reattaches the flow a pending `insertAfter` detached, to whatever the cursor
596
+ * has reached. With nothing built in between the cursor has not moved and this
597
+ * is a no-op, which is the right answer for `insertAfter(x)` followed by
598
+ * nothing.
599
+ */
600
+ private resolvePendingSplice;
485
601
  /** Enable auto-layout: `build()` will run the layout engine and populate diagram interchange data. */
486
602
  withAutoLayout(): this;
487
603
  /** Set the Camunda execution platform version stamped into the BPMN definitions. Defaults to `"8.9.0"`. */
@@ -604,10 +720,50 @@ export declare class ProcessBuilder {
604
720
  *
605
721
  * Resolves all forward-referenced `incoming` / `outgoing` arrays and wraps
606
722
  * the process in a {@link BpmnDefinitions} ready for XML serialization.
723
+ *
724
+ * **The join contract.** Branches built with `.branch()` converge implicitly:
725
+ * where several paths from one gateway reach the same element, a matching join
726
+ * gateway is inserted for you. That is convenient by hand and a trap for
727
+ * generated code, which cannot see the element it did not write. Pass
728
+ * `{ explicitJoins: true }` to be told instead of helped — the build throws,
729
+ * naming the gateways it would have inserted, and you declare them yourself
730
+ * with `.connectTo(joinId)`.
731
+ *
732
+ * A join you declare only counts if it *matches the split*: an exclusive split
733
+ * converging on a parallel gateway is not the gateway inference would have
734
+ * added, so it is still inferred — and with `explicitJoins` that refusal is the
735
+ * only thing that tells you.
736
+ *
737
+ * {@link ProcessBuilder.from} never infers joins at all, whatever this option
738
+ * says: inference reads the whole topology, and on a document you were handed
739
+ * that means rewriting edges you never touched.
740
+ *
741
+ * @param options - `explicitJoins` refuses inferred join gateways. `strict` is
742
+ * the former name for it and still works.
607
743
  */
608
- build(options?: {
609
- strict?: boolean;
610
- }): BpmnDefinitions;
744
+ build(options?: BuildOptions): BpmnDefinitions;
745
+ /**
746
+ * Writes this process's contents back into the document it came from.
747
+ *
748
+ * Everything not listed here is kept by identity — other processes, the
749
+ * collaboration, diagram interchange, root elements, the process's own lanes,
750
+ * documentation, extensions and unmodelled content. That is the whole point of
751
+ * continuing rather than regenerating.
752
+ *
753
+ * Diagram interchange is *not* regenerated: existing shapes keep their
754
+ * positions, and elements added here have none until `withAutoLayout()` or a
755
+ * later `applyAutoLayout()` gives them one.
756
+ */
757
+ private buildOntoSource;
758
+ /**
759
+ * Refuses to rewire a flow that was already in the document.
760
+ *
761
+ * `insertJoinGateways` retargets converging flows, which is right for a
762
+ * topology this builder just created and wrong for one it was handed: a
763
+ * document would come back with edges the caller never touched pointing
764
+ * somewhere else. Continuing a model has to leave the model alone.
765
+ */
766
+ private assertSourceTopologyIntact;
611
767
  private validate;
612
768
  private addFlowElement;
613
769
  }
@@ -620,11 +776,61 @@ export declare class DiagramBuilder {
620
776
  private readonly _processes;
621
777
  private readonly _errors;
622
778
  private readonly _messages;
779
+ private readonly _participants;
780
+ private readonly _messageFlows;
781
+ private _collaborationId;
623
782
  private _executionPlatformVersion;
624
783
  constructor(id: string);
625
784
  /** Set the Camunda execution platform version stamped into the BPMN definitions. Defaults to `"8.9.0"`. */
626
785
  executionPlatformVersion(version: string): this;
627
786
  process(id: string, callback: (b: ProcessBuilder) => void): this;
787
+ /** Renames the collaboration element. Defaults to `"Collaboration_1"`. */
788
+ collaborationId(id: string): this;
789
+ /**
790
+ * Adds a pool.
791
+ *
792
+ * Omit `processId` for a black box — a participant whose internals are not
793
+ * modelled. That is a real BPMN construct, not an incomplete one: it is how
794
+ * you draw the counterparty you exchange messages with but do not execute.
795
+ *
796
+ * @param id - The participant's element id, used verbatim.
797
+ */
798
+ participant(id: string, options?: ParticipantOptions): this;
799
+ /**
800
+ * Declares a root-level message, which a message flow may name and a Camunda 8
801
+ * message subscription correlates on.
802
+ *
803
+ * `ProcessBuilder` already creates messages by name for message events, so
804
+ * only call this for a message no event declared — typically one carried by a
805
+ * message flow between pools.
806
+ *
807
+ * @param id - The message's element id, used verbatim.
808
+ */
809
+ message(id: string, options?: DiagramMessageOptions): this;
810
+ /**
811
+ * Connects two pools.
812
+ *
813
+ * `source` and `target` name either participants or flow nodes inside them.
814
+ * Both forms are valid BPMN and the layout engine reads either, but they must
815
+ * be in *different* pools — a message flow is what crosses a pool boundary,
816
+ * and one that does not is the error this catches.
817
+ *
818
+ * @param id - The message flow's element id, used verbatim.
819
+ */
820
+ messageFlow(id: string, options: MessageFlowOptions): this;
821
+ /**
822
+ * Reports every way the declared collaboration would not survive contact with
823
+ * a modeler, so `build()` can refuse rather than emit a file that opens broken.
824
+ */
825
+ private collaborationProblems;
826
+ /** Maps every participant id and every flow node id to its owning participant. */
827
+ private participantIndex;
628
828
  build(): BpmnDefinitions;
829
+ /**
830
+ * A document with no participants has no collaboration — an empty
831
+ * `<bpmn:collaboration/>` is not a neutral addition, it makes every process a
832
+ * pool-less participant in a modeler.
833
+ */
834
+ private buildCollaborations;
629
835
  }
630
836
  //# sourceMappingURL=bpmn-builder.d.ts.map