@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
@@ -53,21 +53,45 @@ function computeSimilarity(a, b) {
53
53
  score += 0.15;
54
54
  return score;
55
55
  }
56
- /** Cluster service tasks into connected components above the similarity threshold. */
56
+ /**
57
+ * Cluster service tasks into connected components above the similarity
58
+ * threshold. Similarity requires an identical job type, so tasks are bucketed
59
+ * by type first and the pairwise comparison only runs within a bucket.
60
+ */
57
61
  function cluster(tasks, threshold) {
62
+ const byType = new Map();
63
+ const clusters = [];
64
+ for (const task of tasks) {
65
+ const type = readZeebeTaskType(task.extensionElements);
66
+ // Without a job type nothing is similar to this task; it stands alone.
67
+ if (type === null) {
68
+ clusters.push([task]);
69
+ continue;
70
+ }
71
+ const bucket = byType.get(type);
72
+ if (bucket)
73
+ bucket.push(task);
74
+ else
75
+ byType.set(type, [task]);
76
+ }
77
+ for (const bucket of byType.values())
78
+ clusterBucket(bucket, threshold, clusters);
79
+ // Report clusters in declaration order of their first member.
80
+ const order = new Map(tasks.map((task, index) => [task, index]));
81
+ clusters.sort((a, b) => (order.get(a[0]) ?? 0) - (order.get(b[0]) ?? 0));
82
+ return clusters;
83
+ }
84
+ function clusterBucket(tasks, threshold, clusters) {
58
85
  const n = tasks.length;
59
86
  const visited = new Array(n).fill(false);
60
- const clusters = [];
61
87
  for (let i = 0; i < n; i++) {
62
88
  if (visited[i])
63
89
  continue;
64
90
  const group = [];
65
91
  const queue = [i];
66
92
  visited[i] = true;
67
- while (queue.length > 0) {
68
- const curr = queue.shift();
69
- if (curr === undefined)
70
- break;
93
+ for (let head = 0; head < queue.length; head++) {
94
+ const curr = queue[head];
71
95
  const task = tasks[curr];
72
96
  if (task === undefined)
73
97
  break;
@@ -87,7 +111,6 @@ function cluster(tasks, threshold) {
87
111
  }
88
112
  clusters.push(group);
89
113
  }
90
- return clusters;
91
114
  }
92
115
  // ---------------------------------------------------------------------------
93
116
  // Build the extracted BpmnDefinitions
@@ -70,10 +70,8 @@ export function buildFlowIndex(p) {
70
70
  export function reachableFrom(startIds, bySource) {
71
71
  const visited = new Set(startIds);
72
72
  const queue = [...startIds];
73
- while (queue.length > 0) {
74
- const current = queue.shift();
75
- if (current === undefined)
76
- break;
73
+ for (let head = 0; head < queue.length; head++) {
74
+ const current = queue[head];
77
75
  const outflows = bySource.get(current) ?? [];
78
76
  for (const flow of outflows) {
79
77
  if (!visited.has(flow.targetRef)) {
@@ -238,6 +238,58 @@ function readResultVariable(ext) {
238
238
  return null;
239
239
  }
240
240
  // ---------------------------------------------------------------------------
241
+ // Scope propagation
242
+ // ---------------------------------------------------------------------------
243
+ /**
244
+ * For every node in the flow graph, the variables produced by that node or by
245
+ * any node with a path to it. A worklist fixed point over the graph handles
246
+ * cycles and costs O((V + E) · vars), instead of one full graph walk per flow.
247
+ */
248
+ function reachingProduces(flows, produces) {
249
+ const successors = new Map();
250
+ const scope = new Map();
251
+ for (const flow of flows) {
252
+ const out = successors.get(flow.sourceRef);
253
+ if (out)
254
+ out.push(flow.targetRef);
255
+ else
256
+ successors.set(flow.sourceRef, [flow.targetRef]);
257
+ if (!scope.has(flow.sourceRef)) {
258
+ scope.set(flow.sourceRef, new Set(produces.get(flow.sourceRef) ?? []));
259
+ }
260
+ if (!scope.has(flow.targetRef)) {
261
+ scope.set(flow.targetRef, new Set(produces.get(flow.targetRef) ?? []));
262
+ }
263
+ }
264
+ const queue = [...scope.keys()];
265
+ const queued = new Set(queue);
266
+ for (let head = 0; head < queue.length; head++) {
267
+ const id = queue[head];
268
+ queued.delete(id);
269
+ const own = scope.get(id);
270
+ const next = successors.get(id);
271
+ if (!own || own.size === 0 || !next)
272
+ continue;
273
+ for (const target of next) {
274
+ const targetScope = scope.get(target);
275
+ if (!targetScope)
276
+ continue;
277
+ let grew = false;
278
+ for (const v of own) {
279
+ if (!targetScope.has(v)) {
280
+ targetScope.add(v);
281
+ grew = true;
282
+ }
283
+ }
284
+ if (grew && !queued.has(target)) {
285
+ queued.add(target);
286
+ queue.push(target);
287
+ }
288
+ }
289
+ }
290
+ return scope;
291
+ }
292
+ // ---------------------------------------------------------------------------
241
293
  // Main analysis
242
294
  // ---------------------------------------------------------------------------
243
295
  export function analyzeVariableFlow(p) {
@@ -377,42 +429,10 @@ export function analyzeVariableFlow(p) {
377
429
  });
378
430
  }
379
431
  // ── Per-edge scope findings (variables available at each sequence flow) ──
380
- // Build reverse adjacency: targetId → set of source IDs
381
- const reverseAdj = new Map();
432
+ const scopeAt = reachingProduces(p.sequenceFlows, elementProduces);
382
433
  for (const flow of p.sequenceFlows) {
383
- const set = reverseAdj.get(flow.targetRef) ?? new Set();
384
- set.add(flow.sourceRef);
385
- reverseAdj.set(flow.targetRef, set);
386
- }
387
- // Collect all transitive predecessors of an element (inclusive of start)
388
- function allPredecessors(elementId) {
389
- const visited = new Set();
390
- const queue = [elementId];
391
- while (queue.length > 0) {
392
- const current = queue.shift();
393
- if (current === undefined)
394
- break;
395
- const preds = reverseAdj.get(current);
396
- if (preds === undefined)
397
- continue;
398
- for (const pred of preds) {
399
- if (!visited.has(pred)) {
400
- visited.add(pred);
401
- queue.push(pred);
402
- }
403
- }
404
- }
405
- return visited;
406
- }
407
- for (const flow of p.sequenceFlows) {
408
- const predIds = allPredecessors(flow.sourceRef);
409
- predIds.add(flow.sourceRef);
410
- const inScope = new Set();
411
- for (const predId of predIds) {
412
- for (const v of elementProduces.get(predId) ?? [])
413
- inScope.add(v);
414
- }
415
- if (inScope.size === 0)
434
+ const inScope = scopeAt.get(flow.sourceRef);
435
+ if (inScope === undefined || inScope.size === 0)
416
436
  continue;
417
437
  const scopeVars = [...inScope].sort();
418
438
  findings.push({
@@ -455,42 +475,13 @@ export function analyzeVariableFlow(p) {
455
475
  }
456
476
  }
457
477
  }
458
- // Build inner reverse adjacency for BFS
459
- const innerReverseAdj = new Map();
460
- for (const flow of sp.sequenceFlows) {
461
- const set = innerReverseAdj.get(flow.targetRef) ?? new Set();
462
- set.add(flow.sourceRef);
463
- innerReverseAdj.set(flow.targetRef, set);
464
- }
465
- function innerAllPredecessors(elementId) {
466
- const visited = new Set();
467
- const queue = [elementId];
468
- while (queue.length > 0) {
469
- const current = queue.shift();
470
- if (current === undefined)
471
- break;
472
- const preds = innerReverseAdj.get(current);
473
- if (preds === undefined)
474
- continue;
475
- for (const pred of preds) {
476
- if (!visited.has(pred)) {
477
- visited.add(pred);
478
- queue.push(pred);
479
- }
480
- }
481
- }
482
- return visited;
483
- }
484
478
  // Emit edge-scope findings for inner sequence flows
479
+ const innerScopeAt = reachingProduces(sp.sequenceFlows, innerProduces);
485
480
  for (const flow of sp.sequenceFlows) {
486
- const predIds = innerAllPredecessors(flow.sourceRef);
487
- predIds.add(flow.sourceRef);
488
481
  // Scope always includes the iteration variable, plus anything inner elements produce
489
482
  const inScope = new Set([inputElement]);
490
- for (const predId of predIds) {
491
- for (const v of innerProduces.get(predId) ?? [])
492
- inScope.add(v);
493
- }
483
+ for (const v of innerScopeAt.get(flow.sourceRef) ?? [])
484
+ inScope.add(v);
494
485
  const scopeVars = [...inScope].sort();
495
486
  findings.push({
496
487
  id: `data-flow/edge-scope:${flow.id}`,
@@ -0,0 +1,93 @@
1
+ import type { BpmnDefinitions } from "./bpmn-model.js";
2
+ /**
3
+ * A presentation-free, canonical view of a BPMN model, and a hash over it.
4
+ *
5
+ * Two documents that mean the same thing hash the same, however they are laid
6
+ * out, ordered or formatted. That turns a claim into an assertion: re-running
7
+ * auto-layout, or re-serialising a file, cannot change the hash — if it does,
8
+ * something touched the model, not the diagram.
9
+ *
10
+ * What is excluded, and why:
11
+ *
12
+ * - **Diagram interchange** (`diagrams`) entirely — shapes, edges, waypoints and
13
+ * their `bioc`/`color` extensions are where a layout lives.
14
+ * - **`zeebe:modelerTemplateIcon`** — a base64 icon that would otherwise
15
+ * dominate every diff it appears in.
16
+ * - **`exporter` / `exporterVersion`** — which tool wrote the file, not what the
17
+ * file says. The same model exported by two tools hashes the same.
18
+ *
19
+ * `modeler:executionPlatform` and its version are deliberately **kept**: they
20
+ * name the engine the model targets, so changing them is a real change. This
21
+ * differs from some other implementations; flip it here if that is not wanted.
22
+ *
23
+ * Ordering carries no meaning in BPMN — `flowElements` may appear in any order —
24
+ * so collections are sorted canonically and object keys are sorted by name.
25
+ */
26
+ export type JsonPrimitive = string | number | boolean | null;
27
+ export type JsonValue = JsonPrimitive | JsonValue[] | {
28
+ [key: string]: JsonValue;
29
+ };
30
+ /**
31
+ * Namespace prefixes that carry presentation rather than meaning.
32
+ *
33
+ * Named once so the two things that need the notion cannot disagree about what
34
+ * counts as presentation: this module, and the descriptor coverage check, which
35
+ * treats these packages as handled structurally by the diagram model rather than
36
+ * as gaps.
37
+ *
38
+ * They are excluded here in two different ways, which is why the list is not
39
+ * itself the attribute filter. `bpmndi` / `dc` / `di` describe the diagram, and
40
+ * the diagram is dropped wholesale via the `diagrams` key. `bioc` and `color`
41
+ * are extension *attributes* that ride on diagram elements, so they are dropped
42
+ * by name wherever they appear.
43
+ */
44
+ export declare const PRESENTATION_PREFIXES: ReadonlySet<string>;
45
+ /** A canonical view of a model, plus a per-element index for diffing. */
46
+ export interface SemanticProjection {
47
+ /** Canonical JSON of the whole model, presentation excluded. */
48
+ readonly value: JsonValue;
49
+ /**
50
+ * One entry per element that carries an `id`, projected shallowly —
51
+ * descendants with their own id appear as that id, so a change is attributed
52
+ * to the element that actually changed rather than to all its ancestors.
53
+ */
54
+ readonly elements: ReadonlyMap<string, JsonValue>;
55
+ }
56
+ /**
57
+ * Projects a model onto its canonical, presentation-free form.
58
+ *
59
+ * @param definitions - The model to project.
60
+ */
61
+ export declare function projectSemantics(definitions: BpmnDefinitions): SemanticProjection;
62
+ /**
63
+ * Returns the SHA-256 of a model's canonical projection.
64
+ *
65
+ * Stable across formatting, element order, attribute order and any change to
66
+ * the diagram. Use it to tell "the model changed" from "the picture moved".
67
+ *
68
+ * @param definitions - The model to hash.
69
+ */
70
+ export declare function semanticHash(definitions: BpmnDefinitions): string;
71
+ /** What changed between two models, keyed by element id. */
72
+ export interface SemanticDiff {
73
+ /** Ids present only in the later model. */
74
+ added: string[];
75
+ /** Ids present only in the earlier model. */
76
+ removed: string[];
77
+ /** Ids whose own projection differs, with both sides. */
78
+ changed: Array<{
79
+ id: string;
80
+ before: JsonValue;
81
+ after: JsonValue;
82
+ }>;
83
+ }
84
+ /**
85
+ * Compares two models element by element.
86
+ *
87
+ * @param before - The earlier model.
88
+ * @param after - The later model.
89
+ * @returns Ids added, removed, and changed — the report a write boundary or a
90
+ * review loop shows a user before touching anything.
91
+ */
92
+ export declare function diffSemantics(before: BpmnDefinitions, after: BpmnDefinitions): SemanticDiff;
93
+ //# sourceMappingURL=semantic-hash.d.ts.map
@@ -0,0 +1,155 @@
1
+ import { sha256Hex } from "./sha256.js";
2
+ /** Attribute names dropped wherever they appear. */
3
+ const EXCLUDED_ATTRIBUTES = new Set(["zeebe:modelerTemplateIcon"]);
4
+ /**
5
+ * Namespace prefixes that carry presentation rather than meaning.
6
+ *
7
+ * Named once so the two things that need the notion cannot disagree about what
8
+ * counts as presentation: this module, and the descriptor coverage check, which
9
+ * treats these packages as handled structurally by the diagram model rather than
10
+ * as gaps.
11
+ *
12
+ * They are excluded here in two different ways, which is why the list is not
13
+ * itself the attribute filter. `bpmndi` / `dc` / `di` describe the diagram, and
14
+ * the diagram is dropped wholesale via the `diagrams` key. `bioc` and `color`
15
+ * are extension *attributes* that ride on diagram elements, so they are dropped
16
+ * by name wherever they appear.
17
+ */
18
+ export const PRESENTATION_PREFIXES = new Set([
19
+ "bpmndi",
20
+ "dc",
21
+ "di",
22
+ "bioc",
23
+ "color",
24
+ ]);
25
+ /** The subset of the above excluded per-attribute rather than per-subtree. */
26
+ const EXCLUDED_ATTRIBUTE_PREFIXES = ["bioc:", "color:"];
27
+ /** Top-level `BpmnDefinitions` keys that describe the exporter or the diagram. */
28
+ const EXCLUDED_DEFINITIONS_KEYS = new Set(["diagrams", "exporter", "exporterVersion"]);
29
+ function isExcludedAttribute(name) {
30
+ return (EXCLUDED_ATTRIBUTES.has(name) ||
31
+ EXCLUDED_ATTRIBUTE_PREFIXES.some((prefix) => name.startsWith(prefix)));
32
+ }
33
+ function isRecord(value) {
34
+ return typeof value === "object" && value !== null && !Array.isArray(value);
35
+ }
36
+ /**
37
+ * Canonicalises a value: drops empties, sorts object keys, and sorts array
38
+ * entries by their own canonical form so ordering cannot affect the result.
39
+ */
40
+ function canonicalise(value, excludedKeys) {
41
+ if (value === undefined || value === null)
42
+ return undefined;
43
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
44
+ return value;
45
+ }
46
+ if (Array.isArray(value)) {
47
+ const entries = value
48
+ .map((entry) => canonicalise(entry))
49
+ .filter((entry) => entry !== undefined)
50
+ .map((entry) => [JSON.stringify(entry), entry])
51
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
52
+ .map(([, entry]) => entry);
53
+ return entries.length > 0 ? entries : undefined;
54
+ }
55
+ if (!isRecord(value))
56
+ return undefined;
57
+ const result = {};
58
+ for (const key of Object.keys(value).sort()) {
59
+ if (excludedKeys?.has(key))
60
+ continue;
61
+ if (isExcludedAttribute(key))
62
+ continue;
63
+ const projected = canonicalise(value[key]);
64
+ if (projected !== undefined)
65
+ result[key] = projected;
66
+ }
67
+ return Object.keys(result).length > 0 ? result : undefined;
68
+ }
69
+ /**
70
+ * Replaces every descendant that carries its own `id` with that id, so an
71
+ * element's projection describes the element and not its whole subtree.
72
+ */
73
+ function shallow(value) {
74
+ if (Array.isArray(value))
75
+ return value.map(shallow);
76
+ if (typeof value !== "object" || value === null)
77
+ return value;
78
+ const result = {};
79
+ for (const [key, entry] of Object.entries(value)) {
80
+ if (key === "id") {
81
+ result[key] = entry;
82
+ continue;
83
+ }
84
+ if (Array.isArray(entry)) {
85
+ result[key] = entry.map((item) => isRecord(item) && typeof item.id === "string" ? item.id : shallow(item));
86
+ continue;
87
+ }
88
+ result[key] =
89
+ isRecord(entry) && typeof entry.id === "string" ? entry.id : shallow(entry);
90
+ }
91
+ return result;
92
+ }
93
+ function collectElements(value, into) {
94
+ if (Array.isArray(value)) {
95
+ for (const entry of value)
96
+ collectElements(entry, into);
97
+ return;
98
+ }
99
+ if (!isRecord(value))
100
+ return;
101
+ if (typeof value.id === "string") {
102
+ into.set(value.id, shallow(value));
103
+ }
104
+ for (const entry of Object.values(value)) {
105
+ collectElements(entry, into);
106
+ }
107
+ }
108
+ /**
109
+ * Projects a model onto its canonical, presentation-free form.
110
+ *
111
+ * @param definitions - The model to project.
112
+ */
113
+ export function projectSemantics(definitions) {
114
+ const value = canonicalise(definitions, EXCLUDED_DEFINITIONS_KEYS) ?? {};
115
+ const elements = new Map();
116
+ collectElements(value, elements);
117
+ return { value, elements };
118
+ }
119
+ /**
120
+ * Returns the SHA-256 of a model's canonical projection.
121
+ *
122
+ * Stable across formatting, element order, attribute order and any change to
123
+ * the diagram. Use it to tell "the model changed" from "the picture moved".
124
+ *
125
+ * @param definitions - The model to hash.
126
+ */
127
+ export function semanticHash(definitions) {
128
+ return sha256Hex(JSON.stringify(projectSemantics(definitions).value));
129
+ }
130
+ /**
131
+ * Compares two models element by element.
132
+ *
133
+ * @param before - The earlier model.
134
+ * @param after - The later model.
135
+ * @returns Ids added, removed, and changed — the report a write boundary or a
136
+ * review loop shows a user before touching anything.
137
+ */
138
+ export function diffSemantics(before, after) {
139
+ const left = projectSemantics(before).elements;
140
+ const right = projectSemantics(after).elements;
141
+ const added = [...right.keys()].filter((id) => !left.has(id)).sort();
142
+ const removed = [...left.keys()].filter((id) => !right.has(id)).sort();
143
+ const changed = [...right.keys()]
144
+ .filter((id) => left.has(id))
145
+ .sort()
146
+ .flatMap((id) => {
147
+ const earlier = left.get(id);
148
+ const later = right.get(id);
149
+ return JSON.stringify(earlier) === JSON.stringify(later)
150
+ ? []
151
+ : [{ id, before: earlier, after: later }];
152
+ });
153
+ return { added, removed, changed };
154
+ }
155
+ //# sourceMappingURL=semantic-hash.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SHA-256, synchronous and dependency-free.
3
+ *
4
+ * `node:crypto` would break browser builds and `crypto.subtle` is async and
5
+ * unavailable outside secure contexts, either of which would force every caller
6
+ * of {@link semanticHash} — and the write boundary above it — to become async
7
+ * for no benefit. The algorithm is small enough to carry.
8
+ *
9
+ * @see https://csrc.nist.gov/publications/detail/fips/180/4/final
10
+ */
11
+ /**
12
+ * Returns the lowercase hex SHA-256 digest of a string, encoded as UTF-8.
13
+ *
14
+ * @param input - Text to digest.
15
+ */
16
+ export declare function sha256Hex(input: string): string;
17
+ //# sourceMappingURL=sha256.d.ts.map
@@ -0,0 +1,95 @@
1
+ /**
2
+ * SHA-256, synchronous and dependency-free.
3
+ *
4
+ * `node:crypto` would break browser builds and `crypto.subtle` is async and
5
+ * unavailable outside secure contexts, either of which would force every caller
6
+ * of {@link semanticHash} — and the write boundary above it — to become async
7
+ * for no benefit. The algorithm is small enough to carry.
8
+ *
9
+ * @see https://csrc.nist.gov/publications/detail/fips/180/4/final
10
+ */
11
+ /** First 32 bits of the fractional parts of the cube roots of the first 64 primes. */
12
+ // biome-ignore format: the round constants read better as a block
13
+ const K = new Uint32Array([
14
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
15
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
16
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
17
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
18
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
19
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
20
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
21
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
22
+ ]);
23
+ function rotr(value, bits) {
24
+ return (value >>> bits) | (value << (32 - bits));
25
+ }
26
+ /**
27
+ * Returns the lowercase hex SHA-256 digest of a string, encoded as UTF-8.
28
+ *
29
+ * @param input - Text to digest.
30
+ */
31
+ export function sha256Hex(input) {
32
+ const bytes = new TextEncoder().encode(input);
33
+ const bitLength = bytes.length * 8;
34
+ // Pad to a multiple of 64 bytes: 0x80, zeroes, then the length as 64 bits.
35
+ const padded = new Uint8Array((((bytes.length + 9 + 63) / 64) | 0) * 64);
36
+ padded.set(bytes);
37
+ padded[bytes.length] = 0x80;
38
+ const view = new DataView(padded.buffer);
39
+ view.setUint32(padded.length - 8, Math.floor(bitLength / 0x1_0000_0000));
40
+ view.setUint32(padded.length - 4, bitLength >>> 0);
41
+ const h = new Uint32Array([
42
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
43
+ ]);
44
+ const w = new Uint32Array(64);
45
+ for (let offset = 0; offset < padded.length; offset += 64) {
46
+ for (let i = 0; i < 16; i++) {
47
+ w[i] = view.getUint32(offset + i * 4);
48
+ }
49
+ for (let i = 16; i < 64; i++) {
50
+ const a = w[i - 15];
51
+ const b = w[i - 2];
52
+ const s0 = rotr(a, 7) ^ rotr(a, 18) ^ (a >>> 3);
53
+ const s1 = rotr(b, 17) ^ rotr(b, 19) ^ (b >>> 10);
54
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
55
+ }
56
+ let a = h[0];
57
+ let b = h[1];
58
+ let c = h[2];
59
+ let d = h[3];
60
+ let e = h[4];
61
+ let f = h[5];
62
+ let g = h[6];
63
+ let hh = h[7];
64
+ for (let i = 0; i < 64; i++) {
65
+ const s1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
66
+ const ch = (e & f) ^ (~e & g);
67
+ const temp1 = (hh + s1 + ch + K[i] + w[i]) >>> 0;
68
+ const s0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
69
+ const maj = (a & b) ^ (a & c) ^ (b & c);
70
+ const temp2 = (s0 + maj) >>> 0;
71
+ hh = g;
72
+ g = f;
73
+ f = e;
74
+ e = (d + temp1) >>> 0;
75
+ d = c;
76
+ c = b;
77
+ b = a;
78
+ a = (temp1 + temp2) >>> 0;
79
+ }
80
+ h[0] = (h[0] + a) >>> 0;
81
+ h[1] = (h[1] + b) >>> 0;
82
+ h[2] = (h[2] + c) >>> 0;
83
+ h[3] = (h[3] + d) >>> 0;
84
+ h[4] = (h[4] + e) >>> 0;
85
+ h[5] = (h[5] + f) >>> 0;
86
+ h[6] = (h[6] + g) >>> 0;
87
+ h[7] = (h[7] + hh) >>> 0;
88
+ }
89
+ let digest = "";
90
+ for (const word of h) {
91
+ digest += word.toString(16).padStart(8, "0");
92
+ }
93
+ return digest;
94
+ }
95
+ //# sourceMappingURL=sha256.js.map
@@ -98,4 +98,60 @@ export interface ZeebeExtensions {
98
98
  }
99
99
  /** Convert Zeebe extensions to XmlElement array for the BPMN model. */
100
100
  export declare function zeebeExtensionsToXmlElements(extensions: ZeebeExtensions): XmlElement[];
101
+ /**
102
+ * Reports where a Zeebe extension may go, from the descriptor rather than from
103
+ * our own idea of the rules.
104
+ *
105
+ * `zeebe.json` records a `meta.allowedIn` list per extension type;
106
+ * `scripts/generate-zeebe-placement.ts` resolves those entries — many of which
107
+ * name abstract BPMN types or Zeebe aliases — into the concrete element names in
108
+ * `ZEEBE_PLACEMENT`. Writing `zeebe:calledDecision` onto a service task produces
109
+ * a file Camunda rejects at deploy time; catching it at the write is the point.
110
+ *
111
+ * **An extension the table does not mention is allowed.** The descriptor
112
+ * declares no owner for `zeebe:subscription` or `zeebe:properties`, so we do not
113
+ * know where they may go and must not invent a rule — this check rejects only
114
+ * what the descriptor positively forbids. Vendor extensions outside the `zeebe:`
115
+ * namespace are not this function's business and are likewise allowed.
116
+ *
117
+ * @param ownerElement - The owner's BPMN element name, e.g. `bpmn:serviceTask`.
118
+ * @param extension - The extension element name, e.g. `zeebe:taskDefinition`.
119
+ */
120
+ export declare function isZeebePlacementAllowed(ownerElement: string, extension: string): boolean;
121
+ /** Thrown when a Zeebe extension is written somewhere the descriptor forbids. */
122
+ export declare class ZeebePlacementError extends Error {
123
+ readonly ownerElement: string;
124
+ readonly extension: string;
125
+ readonly allowedOn: readonly string[];
126
+ constructor(ownerElement: string, extension: string, allowedOn: readonly string[]);
127
+ }
128
+ /**
129
+ * Throws {@link ZeebePlacementError} if the placement is one the descriptor
130
+ * forbids. See {@link isZeebePlacementAllowed} for what "forbids" covers.
131
+ */
132
+ export declare function assertZeebePlacement(ownerElement: string, extension: string): void;
133
+ /**
134
+ * The BPMN element name a flow element is written as.
135
+ *
136
+ * The model's `type` is the element's local name in every case but one:
137
+ * `eventSubProcess` is our name for a `bpmn:subProcess` carrying
138
+ * `triggeredByEvent`, and BPMN has no element of that name.
139
+ */
140
+ export declare function bpmnElementName(flowElement: {
141
+ type: string;
142
+ }): string;
143
+ /**
144
+ * Finds a Zeebe extension element on a flow element, creating it if absent, and
145
+ * refuses a placement the descriptor forbids.
146
+ *
147
+ * Use this rather than pushing onto `extensionElements` directly: the push
148
+ * cannot fail, so an extension on the wrong element becomes a deploy-time error
149
+ * in someone else's terminal instead of a throw here.
150
+ *
151
+ * @throws ZeebePlacementError
152
+ */
153
+ export declare function ensureZeebeExtension(owner: {
154
+ type: string;
155
+ extensionElements: XmlElement[];
156
+ }, extension: string): XmlElement;
101
157
  //# sourceMappingURL=zeebe-extensions.d.ts.map