@bpmnkit/core 0.1.1 → 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.
- package/README.md +32 -1
- package/dist/bpmn/agentic.d.ts +121 -0
- package/dist/bpmn/agentic.js +97 -0
- package/dist/bpmn/auto-layout.d.ts +5 -5
- package/dist/bpmn/auto-layout.js +592 -36
- package/dist/bpmn/bpmn-builder.d.ts +265 -3
- package/dist/bpmn/bpmn-builder.js +603 -197
- package/dist/bpmn/bpmn-model.d.ts +114 -0
- package/dist/bpmn/bpmn-parser.js +1414 -521
- package/dist/bpmn/bpmn-serializer.js +107 -19
- package/dist/bpmn/compact.d.ts +17 -2
- package/dist/bpmn/compact.js +3 -3
- package/dist/bpmn/full-operations.d.ts +89 -0
- package/dist/bpmn/full-operations.js +478 -0
- package/dist/bpmn/index.d.ts +19 -0
- package/dist/bpmn/index.js +21 -0
- package/dist/bpmn/optimize/agentic.d.ts +10 -0
- package/dist/bpmn/optimize/agentic.js +88 -0
- package/dist/bpmn/optimize/deploy.d.ts +16 -0
- package/dist/bpmn/optimize/deploy.js +143 -0
- package/dist/bpmn/optimize/feel-syntax.d.ts +12 -0
- package/dist/bpmn/optimize/feel-syntax.js +87 -0
- package/dist/bpmn/optimize/feel.js +7 -4
- package/dist/bpmn/optimize/flow.js +22 -2
- package/dist/bpmn/optimize/index.js +20 -9
- package/dist/bpmn/optimize/patterns.js +23 -16
- package/dist/bpmn/optimize/tasks.js +30 -7
- package/dist/bpmn/optimize/types.d.ts +10 -1
- package/dist/bpmn/optimize/utils.js +2 -4
- package/dist/bpmn/optimize/variable-flow.js +58 -67
- package/dist/bpmn/semantic-hash.d.ts +93 -0
- package/dist/bpmn/semantic-hash.js +155 -0
- package/dist/bpmn/sha256.d.ts +17 -0
- package/dist/bpmn/sha256.js +95 -0
- package/dist/bpmn/zeebe-extensions.d.ts +83 -0
- package/dist/bpmn/zeebe-extensions.js +117 -0
- package/dist/bpmn/zeebe-placement.d.ts +12 -0
- package/dist/bpmn/zeebe-placement.js +140 -0
- package/dist/errors.d.ts +40 -1
- package/dist/errors.js +41 -0
- package/dist/index.d.ts +16 -5
- package/dist/index.js +9 -3
- package/dist/layout/annotations.js +36 -1
- package/dist/layout/collaboration/alignment.d.ts +26 -0
- package/dist/layout/collaboration/alignment.js +66 -0
- package/dist/layout/collaboration/ordering.d.ts +21 -0
- package/dist/layout/collaboration/ordering.js +102 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/layout-engine.d.ts +13 -3
- package/dist/layout/layout-engine.js +9 -4
- package/dist/layout/semantic/bands.d.ts +19 -0
- package/dist/layout/semantic/bands.js +324 -0
- package/dist/layout/semantic/graph.d.ts +37 -0
- package/dist/layout/semantic/graph.js +242 -0
- package/dist/layout/semantic/index.d.ts +13 -0
- package/dist/layout/semantic/index.js +181 -0
- package/dist/layout/semantic/place.d.ts +40 -0
- package/dist/layout/semantic/place.js +271 -0
- package/dist/layout/semantic/route.d.ts +14 -0
- package/dist/layout/semantic/route.js +514 -0
- package/dist/layout/types.d.ts +17 -0
- package/dist/node/index.d.ts +10 -0
- package/dist/node/index.js +9 -0
- package/dist/node/write.d.ts +81 -0
- package/dist/node/write.js +167 -0
- package/dist/plan/compile.d.ts +39 -0
- package/dist/plan/compile.js +380 -0
- package/dist/plan/extract.d.ts +31 -0
- package/dist/plan/extract.js +248 -0
- package/dist/plan/index.d.ts +6 -0
- package/dist/plan/index.js +5 -0
- package/dist/plan/merge.d.ts +13 -0
- package/dist/plan/merge.js +80 -0
- package/dist/plan/slug.d.ts +5 -0
- package/dist/plan/slug.js +22 -0
- package/dist/plan/types.d.ts +225 -0
- package/dist/plan/types.js +13 -0
- package/dist/types/id-generator.js +11 -3
- package/dist/xml/index.d.ts +3 -1
- package/dist/xml/index.js +2 -1
- package/dist/xml/xml-parser.d.ts +32 -0
- package/dist/xml/xml-parser.js +394 -143
- package/package.json +9 -2
|
@@ -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
|
-
|
|
381
|
-
const reverseAdj = new Map();
|
|
432
|
+
const scopeAt = reachingProduces(p.sequenceFlows, elementProduces);
|
|
382
433
|
for (const flow of p.sequenceFlows) {
|
|
383
|
-
const
|
|
384
|
-
|
|
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
|
|
491
|
-
|
|
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
|
|
@@ -53,6 +53,25 @@ export interface ZeebeCalledDecision {
|
|
|
53
53
|
/** Process variable that receives the decision result. */
|
|
54
54
|
resultVariable: string;
|
|
55
55
|
}
|
|
56
|
+
/** Zeebe user task assignment (assignee / candidate groups / candidate users). */
|
|
57
|
+
export interface ZeebeAssignmentDefinition {
|
|
58
|
+
assignee?: string;
|
|
59
|
+
candidateGroups?: string;
|
|
60
|
+
candidateUsers?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Zeebe user task scheduling (due date / follow-up date). */
|
|
63
|
+
export interface ZeebeTaskSchedule {
|
|
64
|
+
dueDate?: string;
|
|
65
|
+
followUpDate?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Zeebe user task priority (0-100, default 50). */
|
|
68
|
+
export interface ZeebePriorityDefinition {
|
|
69
|
+
priority: string;
|
|
70
|
+
}
|
|
71
|
+
/** Zeebe message correlation key for a message catch/boundary/receive element. */
|
|
72
|
+
export interface ZeebeSubscription {
|
|
73
|
+
correlationKey: string;
|
|
74
|
+
}
|
|
56
75
|
/** Collected Zeebe extensions on a service task. */
|
|
57
76
|
export interface ZeebeExtensions {
|
|
58
77
|
taskDefinition?: ZeebeTaskDefinition;
|
|
@@ -66,9 +85,73 @@ export interface ZeebeExtensions {
|
|
|
66
85
|
calledDecision?: ZeebeCalledDecision;
|
|
67
86
|
/** Marks this as a Camunda 8 native user task (zeebe:userTask). */
|
|
68
87
|
userTask?: true;
|
|
88
|
+
/** User task assignee/candidates (zeebe:assignmentDefinition). */
|
|
89
|
+
assignmentDefinition?: ZeebeAssignmentDefinition;
|
|
90
|
+
/** User task due/follow-up dates (zeebe:taskSchedule). */
|
|
91
|
+
taskSchedule?: ZeebeTaskSchedule;
|
|
92
|
+
/** User task priority (zeebe:priorityDefinition). */
|
|
93
|
+
priorityDefinition?: ZeebePriorityDefinition;
|
|
94
|
+
/** Message correlation key for a message catch/boundary/receive element (zeebe:subscription). */
|
|
95
|
+
subscription?: ZeebeSubscription;
|
|
69
96
|
/** Unrecognized extension elements preserved for roundtrip. */
|
|
70
97
|
unknownElements?: XmlElement[];
|
|
71
98
|
}
|
|
72
99
|
/** Convert Zeebe extensions to XmlElement array for the BPMN model. */
|
|
73
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;
|
|
74
157
|
//# sourceMappingURL=zeebe-extensions.d.ts.map
|