@bpmnkit/core 0.0.8
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 +185 -0
- package/dist/bpmn/auto-layout.d.ts +10 -0
- package/dist/bpmn/auto-layout.js +203 -0
- package/dist/bpmn/bpmn-builder.d.ts +381 -0
- package/dist/bpmn/bpmn-builder.js +1223 -0
- package/dist/bpmn/bpmn-model.d.ts +403 -0
- package/dist/bpmn/bpmn-model.js +2 -0
- package/dist/bpmn/bpmn-parser.d.ts +4 -0
- package/dist/bpmn/bpmn-parser.js +642 -0
- package/dist/bpmn/bpmn-serializer.d.ts +4 -0
- package/dist/bpmn/bpmn-serializer.js +515 -0
- package/dist/bpmn/compact.d.ts +91 -0
- package/dist/bpmn/compact.js +349 -0
- package/dist/bpmn/di-color.d.ts +9 -0
- package/dist/bpmn/di-color.js +32 -0
- package/dist/bpmn/index.d.ts +105 -0
- package/dist/bpmn/index.js +176 -0
- package/dist/bpmn/optimize/feel.d.ts +4 -0
- package/dist/bpmn/optimize/feel.js +186 -0
- package/dist/bpmn/optimize/flow.d.ts +4 -0
- package/dist/bpmn/optimize/flow.js +323 -0
- package/dist/bpmn/optimize/index.d.ts +5 -0
- package/dist/bpmn/optimize/index.js +51 -0
- package/dist/bpmn/optimize/naming.d.ts +8 -0
- package/dist/bpmn/optimize/naming.js +145 -0
- package/dist/bpmn/optimize/tasks.d.ts +4 -0
- package/dist/bpmn/optimize/tasks.js +273 -0
- package/dist/bpmn/optimize/types.d.ts +44 -0
- package/dist/bpmn/optimize/types.js +2 -0
- package/dist/bpmn/optimize/utils.d.ts +23 -0
- package/dist/bpmn/optimize/utils.js +129 -0
- package/dist/bpmn/rest-connector.d.ts +48 -0
- package/dist/bpmn/rest-connector.js +69 -0
- package/dist/bpmn/svg.d.ts +35 -0
- package/dist/bpmn/svg.js +557 -0
- package/dist/bpmn/type-guards.d.ts +136 -0
- package/dist/bpmn/type-guards.js +211 -0
- package/dist/bpmn/utils.d.ts +88 -0
- package/dist/bpmn/utils.js +207 -0
- package/dist/bpmn/zeebe-extensions.d.ts +72 -0
- package/dist/bpmn/zeebe-extensions.js +96 -0
- package/dist/dmn/compact.d.ts +44 -0
- package/dist/dmn/compact.js +125 -0
- package/dist/dmn/dmn-builder.d.ts +59 -0
- package/dist/dmn/dmn-builder.js +144 -0
- package/dist/dmn/dmn-layout.d.ts +57 -0
- package/dist/dmn/dmn-layout.js +256 -0
- package/dist/dmn/dmn-model.d.ts +158 -0
- package/dist/dmn/dmn-model.js +2 -0
- package/dist/dmn/dmn-parser.d.ts +4 -0
- package/dist/dmn/dmn-parser.js +275 -0
- package/dist/dmn/dmn-serializer.d.ts +4 -0
- package/dist/dmn/dmn-serializer.js +296 -0
- package/dist/dmn/index.d.ts +28 -0
- package/dist/dmn/index.js +51 -0
- package/dist/errors.d.ts +77 -0
- package/dist/errors.js +66 -0
- package/dist/form/compact.d.ts +20 -0
- package/dist/form/compact.js +146 -0
- package/dist/form/form-builder.d.ts +111 -0
- package/dist/form/form-builder.js +347 -0
- package/dist/form/form-model.d.ts +221 -0
- package/dist/form/form-model.js +2 -0
- package/dist/form/form-parser.d.ts +4 -0
- package/dist/form/form-parser.js +514 -0
- package/dist/form/form-serializer.d.ts +4 -0
- package/dist/form/form-serializer.js +240 -0
- package/dist/form/index.d.ts +27 -0
- package/dist/form/index.js +43 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +19 -0
- package/dist/layout/bench.d.ts +86 -0
- package/dist/layout/bench.js +225 -0
- package/dist/layout/coordinates.d.ts +62 -0
- package/dist/layout/coordinates.js +868 -0
- package/dist/layout/crossing.d.ts +8 -0
- package/dist/layout/crossing.js +60 -0
- package/dist/layout/graph.d.ts +28 -0
- package/dist/layout/graph.js +126 -0
- package/dist/layout/index.d.ts +7 -0
- package/dist/layout/index.js +5 -0
- package/dist/layout/layers.d.ts +13 -0
- package/dist/layout/layers.js +49 -0
- package/dist/layout/layout-engine.d.ts +21 -0
- package/dist/layout/layout-engine.js +191 -0
- package/dist/layout/overlap.d.ts +10 -0
- package/dist/layout/overlap.js +70 -0
- package/dist/layout/routing.d.ts +26 -0
- package/dist/layout/routing.js +439 -0
- package/dist/layout/subprocess.d.ts +14 -0
- package/dist/layout/subprocess.js +77 -0
- package/dist/layout/types.d.ts +72 -0
- package/dist/layout/types.js +37 -0
- package/dist/types/id-generator.d.ts +8 -0
- package/dist/types/id-generator.js +31 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +2 -0
- package/dist/types/xml-element.d.ts +12 -0
- package/dist/types/xml-element.js +2 -0
- package/dist/xml/index.d.ts +2 -0
- package/dist/xml/index.js +2 -0
- package/dist/xml/xml-parser.d.ts +13 -0
- package/dist/xml/xml-parser.js +271 -0
- package/package.json +38 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { parseDmn } from "./dmn-parser.js";
|
|
2
|
+
// ── Element sizes (matches Camunda Modeler defaults) ──────────────────────────
|
|
3
|
+
const SIZES = {
|
|
4
|
+
decision: { width: 180, height: 80 },
|
|
5
|
+
inputData: { width: 125, height: 45 },
|
|
6
|
+
knowledgeSource: { width: 100, height: 63 },
|
|
7
|
+
businessKnowledgeModel: { width: 160, height: 80 },
|
|
8
|
+
};
|
|
9
|
+
const H_GAP = 60; // horizontal gap between layers
|
|
10
|
+
const V_GAP = 40; // vertical gap within a layer
|
|
11
|
+
const MARGIN_X = 160;
|
|
12
|
+
const MARGIN_Y = 100;
|
|
13
|
+
// ── Graph collection ──────────────────────────────────────────────────────────
|
|
14
|
+
function collectNodes(defs) {
|
|
15
|
+
const nodes = [];
|
|
16
|
+
for (const d of defs.decisions)
|
|
17
|
+
nodes.push({ id: d.id, kind: "decision", name: d.name });
|
|
18
|
+
for (const d of defs.inputData)
|
|
19
|
+
nodes.push({ id: d.id, kind: "inputData", name: d.name });
|
|
20
|
+
for (const ks of defs.knowledgeSources)
|
|
21
|
+
nodes.push({ id: ks.id, kind: "knowledgeSource", name: ks.name });
|
|
22
|
+
for (const bkm of defs.businessKnowledgeModels)
|
|
23
|
+
nodes.push({ id: bkm.id, kind: "businessKnowledgeModel", name: bkm.name });
|
|
24
|
+
return nodes;
|
|
25
|
+
}
|
|
26
|
+
function collectEdges(defs) {
|
|
27
|
+
const edges = [];
|
|
28
|
+
for (const d of defs.decisions) {
|
|
29
|
+
for (const req of d.informationRequirements) {
|
|
30
|
+
const src = req.requiredDecision ?? req.requiredInput ?? "";
|
|
31
|
+
if (src)
|
|
32
|
+
edges.push({ sourceId: src, targetId: d.id, reqId: req.id });
|
|
33
|
+
}
|
|
34
|
+
for (const req of d.knowledgeRequirements) {
|
|
35
|
+
edges.push({ sourceId: req.requiredKnowledge, targetId: d.id, reqId: req.id });
|
|
36
|
+
}
|
|
37
|
+
for (const req of d.authorityRequirements) {
|
|
38
|
+
const src = req.requiredAuthority ?? req.requiredDecision ?? req.requiredInput ?? "";
|
|
39
|
+
if (src)
|
|
40
|
+
edges.push({ sourceId: src, targetId: d.id, reqId: req.id });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const bkm of defs.businessKnowledgeModels) {
|
|
44
|
+
for (const req of bkm.knowledgeRequirements) {
|
|
45
|
+
edges.push({ sourceId: req.requiredKnowledge, targetId: bkm.id, reqId: req.id });
|
|
46
|
+
}
|
|
47
|
+
for (const req of bkm.authorityRequirements) {
|
|
48
|
+
const src = req.requiredAuthority ?? req.requiredDecision ?? req.requiredInput ?? "";
|
|
49
|
+
if (src)
|
|
50
|
+
edges.push({ sourceId: src, targetId: bkm.id, reqId: req.id });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const ks of defs.knowledgeSources) {
|
|
54
|
+
for (const req of ks.authorityRequirements) {
|
|
55
|
+
const src = req.requiredAuthority ?? req.requiredDecision ?? req.requiredInput ?? "";
|
|
56
|
+
if (src)
|
|
57
|
+
edges.push({ sourceId: src, targetId: ks.id, reqId: req.id });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return edges;
|
|
61
|
+
}
|
|
62
|
+
// ── Layer assignment (longest-path / BFS) ────────────────────────────────────
|
|
63
|
+
function assignLayers(nodes, edges) {
|
|
64
|
+
const outgoing = new Map();
|
|
65
|
+
const inDegree = new Map();
|
|
66
|
+
for (const n of nodes) {
|
|
67
|
+
outgoing.set(n.id, []);
|
|
68
|
+
inDegree.set(n.id, 0);
|
|
69
|
+
}
|
|
70
|
+
for (const e of edges) {
|
|
71
|
+
outgoing.get(e.sourceId)?.push(e.targetId);
|
|
72
|
+
inDegree.set(e.targetId, (inDegree.get(e.targetId) ?? 0) + 1);
|
|
73
|
+
}
|
|
74
|
+
const layers = new Map();
|
|
75
|
+
const queue = [];
|
|
76
|
+
for (const n of nodes) {
|
|
77
|
+
if ((inDegree.get(n.id) ?? 0) === 0) {
|
|
78
|
+
layers.set(n.id, 0);
|
|
79
|
+
queue.push(n.id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let i = 0;
|
|
83
|
+
while (i < queue.length) {
|
|
84
|
+
const id = queue[i++];
|
|
85
|
+
if (id === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
const layer = layers.get(id) ?? 0;
|
|
88
|
+
for (const succ of outgoing.get(id) ?? []) {
|
|
89
|
+
const newLayer = Math.max(layers.get(succ) ?? 0, layer + 1);
|
|
90
|
+
layers.set(succ, newLayer);
|
|
91
|
+
const deg = (inDegree.get(succ) ?? 1) - 1;
|
|
92
|
+
inDegree.set(succ, deg);
|
|
93
|
+
if (deg === 0)
|
|
94
|
+
queue.push(succ);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Disconnected nodes default to layer 0
|
|
98
|
+
for (const n of nodes) {
|
|
99
|
+
if (!layers.has(n.id))
|
|
100
|
+
layers.set(n.id, 0);
|
|
101
|
+
}
|
|
102
|
+
return layers;
|
|
103
|
+
}
|
|
104
|
+
// ── Layout ────────────────────────────────────────────────────────────────────
|
|
105
|
+
/**
|
|
106
|
+
* Auto-layout a DMN definitions object.
|
|
107
|
+
* Assigns DMNDI diagram positions to all DRG elements using a left-to-right
|
|
108
|
+
* layered layout based on the requirement DAG.
|
|
109
|
+
*/
|
|
110
|
+
export function layoutDmn(defs) {
|
|
111
|
+
const nodes = collectNodes(defs);
|
|
112
|
+
if (nodes.length === 0)
|
|
113
|
+
return defs;
|
|
114
|
+
const edges = collectEdges(defs);
|
|
115
|
+
const layerMap = assignLayers(nodes, edges);
|
|
116
|
+
// Group nodes by layer
|
|
117
|
+
const byLayer = new Map();
|
|
118
|
+
for (const n of nodes) {
|
|
119
|
+
const layer = layerMap.get(n.id) ?? 0;
|
|
120
|
+
const arr = byLayer.get(layer);
|
|
121
|
+
if (arr)
|
|
122
|
+
arr.push(n);
|
|
123
|
+
else
|
|
124
|
+
byLayer.set(layer, [n]);
|
|
125
|
+
}
|
|
126
|
+
const sortedLayers = [...byLayer.keys()].sort((a, b) => a - b);
|
|
127
|
+
// Compute x per layer (left = upstream/leaf, right = final decision)
|
|
128
|
+
let x = MARGIN_X;
|
|
129
|
+
const xByLayer = new Map();
|
|
130
|
+
const widthByLayer = new Map();
|
|
131
|
+
for (const layerIdx of sortedLayers) {
|
|
132
|
+
const layerNodes = byLayer.get(layerIdx) ?? [];
|
|
133
|
+
const maxWidth = Math.max(...layerNodes.map((n) => SIZES[n.kind].width));
|
|
134
|
+
xByLayer.set(layerIdx, x);
|
|
135
|
+
widthByLayer.set(layerIdx, maxWidth);
|
|
136
|
+
x += maxWidth + H_GAP;
|
|
137
|
+
}
|
|
138
|
+
// Compute total height per layer (for vertical centering)
|
|
139
|
+
const heightByLayer = new Map();
|
|
140
|
+
for (const layerIdx of sortedLayers) {
|
|
141
|
+
const layerNodes = byLayer.get(layerIdx) ?? [];
|
|
142
|
+
let h = 0;
|
|
143
|
+
for (const n of layerNodes) {
|
|
144
|
+
h += SIZES[n.kind].height;
|
|
145
|
+
}
|
|
146
|
+
h += Math.max(0, layerNodes.length - 1) * V_GAP;
|
|
147
|
+
heightByLayer.set(layerIdx, h);
|
|
148
|
+
}
|
|
149
|
+
const maxHeight = Math.max(...[...heightByLayer.values()], 0);
|
|
150
|
+
// Position nodes
|
|
151
|
+
const positioned = new Map();
|
|
152
|
+
for (const layerIdx of sortedLayers) {
|
|
153
|
+
const layerNodes = byLayer.get(layerIdx) ?? [];
|
|
154
|
+
const layerX = xByLayer.get(layerIdx) ?? MARGIN_X;
|
|
155
|
+
const layerHeight = heightByLayer.get(layerIdx) ?? 0;
|
|
156
|
+
const startY = MARGIN_Y + (maxHeight - layerHeight) / 2;
|
|
157
|
+
let y = startY;
|
|
158
|
+
for (const n of layerNodes) {
|
|
159
|
+
const size = SIZES[n.kind];
|
|
160
|
+
positioned.set(n.id, {
|
|
161
|
+
...n,
|
|
162
|
+
layer: layerIdx,
|
|
163
|
+
x: layerX,
|
|
164
|
+
y,
|
|
165
|
+
width: size.width,
|
|
166
|
+
height: size.height,
|
|
167
|
+
});
|
|
168
|
+
y += size.height + V_GAP;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Build shapes
|
|
172
|
+
const shapes = [];
|
|
173
|
+
for (const [, node] of positioned) {
|
|
174
|
+
shapes.push({
|
|
175
|
+
dmnElementRef: node.id,
|
|
176
|
+
bounds: { x: node.x, y: node.y, width: node.width, height: node.height },
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// Build edges — connect right edge of source to left edge of target
|
|
180
|
+
const diagramEdges = [];
|
|
181
|
+
for (const edge of edges) {
|
|
182
|
+
const src = positioned.get(edge.sourceId);
|
|
183
|
+
const tgt = positioned.get(edge.targetId);
|
|
184
|
+
if (!src || !tgt)
|
|
185
|
+
continue;
|
|
186
|
+
const srcX = src.x + src.width;
|
|
187
|
+
const srcY = src.y + src.height / 2;
|
|
188
|
+
const tgtX = tgt.x;
|
|
189
|
+
const tgtY = tgt.y + tgt.height / 2;
|
|
190
|
+
diagramEdges.push({
|
|
191
|
+
dmnElementRef: edge.reqId,
|
|
192
|
+
waypoints: [
|
|
193
|
+
{ x: srcX, y: srcY },
|
|
194
|
+
{ x: tgtX, y: tgtY },
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const diagram = { shapes, edges: diagramEdges };
|
|
199
|
+
return { ...defs, diagram };
|
|
200
|
+
}
|
|
201
|
+
/** Parse the reference layout, strip DMNDI, run auto-layout, and compare. */
|
|
202
|
+
export function benchmarkDmnLayout(xml, fileName) {
|
|
203
|
+
const ref = parseDmn(xml);
|
|
204
|
+
const refShapes = new Map();
|
|
205
|
+
for (const shape of ref.diagram?.shapes ?? []) {
|
|
206
|
+
refShapes.set(shape.dmnElementRef, shape);
|
|
207
|
+
}
|
|
208
|
+
const stripped = { ...ref, diagram: undefined };
|
|
209
|
+
const layouted = layoutDmn(stripped);
|
|
210
|
+
const autoShapes = new Map();
|
|
211
|
+
for (const shape of layouted.diagram?.shapes ?? []) {
|
|
212
|
+
autoShapes.set(shape.dmnElementRef, shape);
|
|
213
|
+
}
|
|
214
|
+
const allNodes = collectNodes(ref);
|
|
215
|
+
const comparisons = [];
|
|
216
|
+
let matchedCount = 0;
|
|
217
|
+
for (const n of allNodes) {
|
|
218
|
+
const refShape = refShapes.get(n.id);
|
|
219
|
+
const autoShape = autoShapes.get(n.id);
|
|
220
|
+
if (!refShape || !autoShape)
|
|
221
|
+
continue;
|
|
222
|
+
matchedCount++;
|
|
223
|
+
const refCx = refShape.bounds.x + refShape.bounds.width / 2;
|
|
224
|
+
const refCy = refShape.bounds.y + refShape.bounds.height / 2;
|
|
225
|
+
const autoCx = autoShape.bounds.x + autoShape.bounds.width / 2;
|
|
226
|
+
const autoCy = autoShape.bounds.y + autoShape.bounds.height / 2;
|
|
227
|
+
const dx = autoCx - refCx;
|
|
228
|
+
const dy = autoCy - refCy;
|
|
229
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
230
|
+
comparisons.push({
|
|
231
|
+
id: n.id,
|
|
232
|
+
name: n.name,
|
|
233
|
+
kind: n.kind,
|
|
234
|
+
ref: { cx: refCx, cy: refCy },
|
|
235
|
+
auto: { cx: autoCx, cy: autoCy },
|
|
236
|
+
delta: { dx, dy },
|
|
237
|
+
distance,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
comparisons.sort((a, b) => b.distance - a.distance);
|
|
241
|
+
const avgDistance = comparisons.length > 0
|
|
242
|
+
? comparisons.reduce((s, c) => s + c.distance, 0) / comparisons.length
|
|
243
|
+
: 0;
|
|
244
|
+
const p90Distance = comparisons.length > 0 ? (comparisons[Math.floor(comparisons.length * 0.9)]?.distance ?? 0) : 0;
|
|
245
|
+
const maxDistance = comparisons[0]?.distance ?? 0;
|
|
246
|
+
return {
|
|
247
|
+
fileName,
|
|
248
|
+
elementCount: allNodes.length,
|
|
249
|
+
matchedCount,
|
|
250
|
+
avgDistance,
|
|
251
|
+
p90Distance,
|
|
252
|
+
maxDistance,
|
|
253
|
+
elements: comparisons,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=dmn-layout.js.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { XmlElement } from "../types/xml-element.js";
|
|
2
|
+
/** DMN hit policy for decision tables. */
|
|
3
|
+
export type HitPolicy = "UNIQUE" | "FIRST" | "ANY" | "COLLECT" | "RULE ORDER" | "OUTPUT ORDER" | "PRIORITY";
|
|
4
|
+
/** DMN collect aggregation for COLLECT hit policy. */
|
|
5
|
+
export type DmnAggregation = "SUM" | "MIN" | "MAX" | "COUNT";
|
|
6
|
+
/** DMN type reference for inputs/outputs. */
|
|
7
|
+
export type DmnTypeRef = "string" | "boolean" | "number" | "date";
|
|
8
|
+
/** A single input column in a decision table. */
|
|
9
|
+
export interface DmnInput {
|
|
10
|
+
id: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
inputExpression: {
|
|
13
|
+
id: string;
|
|
14
|
+
typeRef?: DmnTypeRef;
|
|
15
|
+
text?: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** A single output column in a decision table. */
|
|
19
|
+
export interface DmnOutput {
|
|
20
|
+
id: string;
|
|
21
|
+
label?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
typeRef?: DmnTypeRef;
|
|
24
|
+
}
|
|
25
|
+
/** An input entry (unary test) within a rule. */
|
|
26
|
+
export interface DmnInputEntry {
|
|
27
|
+
id: string;
|
|
28
|
+
text: string;
|
|
29
|
+
}
|
|
30
|
+
/** An output entry (literal expression) within a rule. */
|
|
31
|
+
export interface DmnOutputEntry {
|
|
32
|
+
id: string;
|
|
33
|
+
text: string;
|
|
34
|
+
}
|
|
35
|
+
/** A single rule (row) in a decision table. */
|
|
36
|
+
export interface DmnRule {
|
|
37
|
+
id: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
inputEntries: DmnInputEntry[];
|
|
40
|
+
outputEntries: DmnOutputEntry[];
|
|
41
|
+
}
|
|
42
|
+
/** A decision table within a decision element. */
|
|
43
|
+
export interface DmnDecisionTable {
|
|
44
|
+
id: string;
|
|
45
|
+
hitPolicy?: HitPolicy;
|
|
46
|
+
aggregation?: DmnAggregation;
|
|
47
|
+
inputs: DmnInput[];
|
|
48
|
+
outputs: DmnOutput[];
|
|
49
|
+
rules: DmnRule[];
|
|
50
|
+
}
|
|
51
|
+
/** Information requirement — connects a decision or input data to a decision. */
|
|
52
|
+
export interface DmnInformationRequirement {
|
|
53
|
+
id: string;
|
|
54
|
+
/** Ref to required decision id (mutually exclusive with requiredInput). */
|
|
55
|
+
requiredDecision?: string;
|
|
56
|
+
/** Ref to required input data id (mutually exclusive with requiredDecision). */
|
|
57
|
+
requiredInput?: string;
|
|
58
|
+
}
|
|
59
|
+
/** Knowledge requirement — connects a BKM to a decision or BKM. */
|
|
60
|
+
export interface DmnKnowledgeRequirement {
|
|
61
|
+
id: string;
|
|
62
|
+
/** Ref to the required BKM id. */
|
|
63
|
+
requiredKnowledge: string;
|
|
64
|
+
}
|
|
65
|
+
/** Authority requirement — connects a knowledge source or decision to a DRG element. */
|
|
66
|
+
export interface DmnAuthorityRequirement {
|
|
67
|
+
id: string;
|
|
68
|
+
requiredDecision?: string;
|
|
69
|
+
requiredInput?: string;
|
|
70
|
+
/** Ref to knowledge source id. */
|
|
71
|
+
requiredAuthority?: string;
|
|
72
|
+
}
|
|
73
|
+
/** A top-level decision element. */
|
|
74
|
+
export interface DmnDecision {
|
|
75
|
+
id: string;
|
|
76
|
+
name?: string;
|
|
77
|
+
/** Decision table (optional — a decision may contain a literal expression instead). */
|
|
78
|
+
decisionTable?: DmnDecisionTable;
|
|
79
|
+
informationRequirements: DmnInformationRequirement[];
|
|
80
|
+
knowledgeRequirements: DmnKnowledgeRequirement[];
|
|
81
|
+
authorityRequirements: DmnAuthorityRequirement[];
|
|
82
|
+
extensionElements?: XmlElement[];
|
|
83
|
+
}
|
|
84
|
+
/** Input data element — an external data source feeding into decisions. */
|
|
85
|
+
export interface DmnInputData {
|
|
86
|
+
id: string;
|
|
87
|
+
name?: string;
|
|
88
|
+
}
|
|
89
|
+
/** Knowledge source element — an authority for decisions or BKMs. */
|
|
90
|
+
export interface DmnKnowledgeSource {
|
|
91
|
+
id: string;
|
|
92
|
+
name?: string;
|
|
93
|
+
authorityRequirements: DmnAuthorityRequirement[];
|
|
94
|
+
}
|
|
95
|
+
/** Business knowledge model element — a reusable function or decision table. */
|
|
96
|
+
export interface DmnBusinessKnowledgeModel {
|
|
97
|
+
id: string;
|
|
98
|
+
name?: string;
|
|
99
|
+
knowledgeRequirements: DmnKnowledgeRequirement[];
|
|
100
|
+
authorityRequirements: DmnAuthorityRequirement[];
|
|
101
|
+
}
|
|
102
|
+
/** Text annotation element — a free-form textual comment. */
|
|
103
|
+
export interface DmnTextAnnotation {
|
|
104
|
+
id: string;
|
|
105
|
+
text?: string;
|
|
106
|
+
}
|
|
107
|
+
/** Association between any two DRG elements (often with text annotations). */
|
|
108
|
+
export interface DmnAssociation {
|
|
109
|
+
id: string;
|
|
110
|
+
sourceRef: string;
|
|
111
|
+
targetRef: string;
|
|
112
|
+
}
|
|
113
|
+
/** A waypoint in a diagram edge path. */
|
|
114
|
+
export interface DmnWaypoint {
|
|
115
|
+
x: number;
|
|
116
|
+
y: number;
|
|
117
|
+
}
|
|
118
|
+
/** DMN diagram shape for visual representation. */
|
|
119
|
+
export interface DmnDiagramShape {
|
|
120
|
+
dmnElementRef: string;
|
|
121
|
+
bounds: {
|
|
122
|
+
x: number;
|
|
123
|
+
y: number;
|
|
124
|
+
width: number;
|
|
125
|
+
height: number;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** DMN diagram edge for visual representation. */
|
|
129
|
+
export interface DmnDiagramEdge {
|
|
130
|
+
dmnElementRef: string;
|
|
131
|
+
waypoints: DmnWaypoint[];
|
|
132
|
+
}
|
|
133
|
+
/** DMN diagram information. */
|
|
134
|
+
export interface DmnDiagram {
|
|
135
|
+
shapes: DmnDiagramShape[];
|
|
136
|
+
edges: DmnDiagramEdge[];
|
|
137
|
+
}
|
|
138
|
+
/** Root DMN definitions element. */
|
|
139
|
+
export interface DmnDefinitions {
|
|
140
|
+
id: string;
|
|
141
|
+
name: string;
|
|
142
|
+
namespace: string;
|
|
143
|
+
exporter?: string;
|
|
144
|
+
exporterVersion?: string;
|
|
145
|
+
/** Namespace declarations from the XML document (prefix → URI). */
|
|
146
|
+
namespaces: Record<string, string>;
|
|
147
|
+
/** Modeler extension attributes (e.g. executionPlatform). */
|
|
148
|
+
modelerAttributes: Record<string, string>;
|
|
149
|
+
decisions: DmnDecision[];
|
|
150
|
+
inputData: DmnInputData[];
|
|
151
|
+
knowledgeSources: DmnKnowledgeSource[];
|
|
152
|
+
businessKnowledgeModels: DmnBusinessKnowledgeModel[];
|
|
153
|
+
textAnnotations: DmnTextAnnotation[];
|
|
154
|
+
associations: DmnAssociation[];
|
|
155
|
+
diagram?: DmnDiagram;
|
|
156
|
+
extensionElements?: XmlElement[];
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=dmn-model.d.ts.map
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { parseXml } from "../xml/xml-parser.js";
|
|
2
|
+
const DMN_NS = "https://www.omg.org/spec/DMN/20191111/MODEL/";
|
|
3
|
+
/** Strip namespace prefix from a tag name. */
|
|
4
|
+
function localName(name) {
|
|
5
|
+
const idx = name.indexOf(":");
|
|
6
|
+
return idx >= 0 ? name.slice(idx + 1) : name;
|
|
7
|
+
}
|
|
8
|
+
/** Find child elements by local name. */
|
|
9
|
+
function findChildren(element, tagLocalName) {
|
|
10
|
+
return element.children.filter((c) => localName(c.name) === tagLocalName);
|
|
11
|
+
}
|
|
12
|
+
/** Find first child element by local name. */
|
|
13
|
+
function findChild(element, tagLocalName) {
|
|
14
|
+
return element.children.find((c) => localName(c.name) === tagLocalName);
|
|
15
|
+
}
|
|
16
|
+
/** Get attribute value, trying with and without namespace prefixes. */
|
|
17
|
+
function attr(element, name) {
|
|
18
|
+
// Try direct name first
|
|
19
|
+
if (element.attributes[name] !== undefined)
|
|
20
|
+
return element.attributes[name];
|
|
21
|
+
// Try all prefixed variants
|
|
22
|
+
for (const [key, value] of Object.entries(element.attributes)) {
|
|
23
|
+
if (localName(key) === name)
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
function requiredAttr(element, name) {
|
|
29
|
+
const value = attr(element, name);
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
throw new Error(`Missing required attribute "${name}" on element <${element.name}>`);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
/** Strip the leading '#' from a DMN href reference. */
|
|
36
|
+
function deref(href) {
|
|
37
|
+
if (!href)
|
|
38
|
+
return undefined;
|
|
39
|
+
return href.startsWith("#") ? href.slice(1) : href;
|
|
40
|
+
}
|
|
41
|
+
function parseTypeRef(value) {
|
|
42
|
+
if (!value)
|
|
43
|
+
return undefined;
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function parseHitPolicy(value) {
|
|
47
|
+
if (!value)
|
|
48
|
+
return undefined;
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
function parseInputExpression(el) {
|
|
52
|
+
const textEl = findChild(el, "text");
|
|
53
|
+
return {
|
|
54
|
+
id: requiredAttr(el, "id"),
|
|
55
|
+
typeRef: parseTypeRef(attr(el, "typeRef")),
|
|
56
|
+
text: textEl?.text,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function parseInput(el) {
|
|
60
|
+
const exprEl = findChild(el, "inputExpression");
|
|
61
|
+
if (!exprEl) {
|
|
62
|
+
throw new Error(`Missing <inputExpression> in input "${attr(el, "id")}"`);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
id: requiredAttr(el, "id"),
|
|
66
|
+
label: attr(el, "label"),
|
|
67
|
+
inputExpression: parseInputExpression(exprEl),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function parseOutput(el) {
|
|
71
|
+
return {
|
|
72
|
+
id: requiredAttr(el, "id"),
|
|
73
|
+
label: attr(el, "label"),
|
|
74
|
+
name: attr(el, "name"),
|
|
75
|
+
typeRef: parseTypeRef(attr(el, "typeRef")),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function parseInputEntry(el) {
|
|
79
|
+
const textEl = findChild(el, "text");
|
|
80
|
+
return {
|
|
81
|
+
id: requiredAttr(el, "id"),
|
|
82
|
+
text: textEl?.text ?? "",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function parseOutputEntry(el) {
|
|
86
|
+
const textEl = findChild(el, "text");
|
|
87
|
+
return {
|
|
88
|
+
id: requiredAttr(el, "id"),
|
|
89
|
+
text: textEl?.text ?? "",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function parseRule(el) {
|
|
93
|
+
const descEl = findChild(el, "description");
|
|
94
|
+
return {
|
|
95
|
+
id: requiredAttr(el, "id"),
|
|
96
|
+
description: descEl?.text,
|
|
97
|
+
inputEntries: findChildren(el, "inputEntry").map(parseInputEntry),
|
|
98
|
+
outputEntries: findChildren(el, "outputEntry").map(parseOutputEntry),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function parseDecisionTable(el) {
|
|
102
|
+
return {
|
|
103
|
+
id: requiredAttr(el, "id"),
|
|
104
|
+
hitPolicy: parseHitPolicy(attr(el, "hitPolicy")),
|
|
105
|
+
aggregation: attr(el, "aggregation"),
|
|
106
|
+
inputs: findChildren(el, "input").map(parseInput),
|
|
107
|
+
outputs: findChildren(el, "output").map(parseOutput),
|
|
108
|
+
rules: findChildren(el, "rule").map(parseRule),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function parseInformationRequirement(el) {
|
|
112
|
+
const reqDecEl = findChild(el, "requiredDecision");
|
|
113
|
+
const reqInEl = findChild(el, "requiredInput");
|
|
114
|
+
return {
|
|
115
|
+
id: requiredAttr(el, "id"),
|
|
116
|
+
requiredDecision: deref(attr(reqDecEl ?? { name: "", attributes: {}, children: [] }, "href")),
|
|
117
|
+
requiredInput: deref(attr(reqInEl ?? { name: "", attributes: {}, children: [] }, "href")),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function parseKnowledgeRequirement(el) {
|
|
121
|
+
const reqEl = findChild(el, "requiredKnowledge");
|
|
122
|
+
return {
|
|
123
|
+
id: requiredAttr(el, "id"),
|
|
124
|
+
requiredKnowledge: deref(attr(reqEl ?? { name: "", attributes: {}, children: [] }, "href")) ?? "",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function parseAuthorityRequirement(el) {
|
|
128
|
+
const reqDecEl = findChild(el, "requiredDecision");
|
|
129
|
+
const reqInEl = findChild(el, "requiredInput");
|
|
130
|
+
const reqAuthEl = findChild(el, "requiredAuthority");
|
|
131
|
+
return {
|
|
132
|
+
id: requiredAttr(el, "id"),
|
|
133
|
+
requiredDecision: deref(attr(reqDecEl ?? { name: "", attributes: {}, children: [] }, "href")),
|
|
134
|
+
requiredInput: deref(attr(reqInEl ?? { name: "", attributes: {}, children: [] }, "href")),
|
|
135
|
+
requiredAuthority: deref(attr(reqAuthEl ?? { name: "", attributes: {}, children: [] }, "href")),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function parseDecision(el) {
|
|
139
|
+
const tableEl = findChild(el, "decisionTable");
|
|
140
|
+
const extensionEls = findChild(el, "extensionElements");
|
|
141
|
+
const knownLocalNames = new Set([
|
|
142
|
+
"decisionTable",
|
|
143
|
+
"extensionElements",
|
|
144
|
+
"informationRequirement",
|
|
145
|
+
"knowledgeRequirement",
|
|
146
|
+
"authorityRequirement",
|
|
147
|
+
]);
|
|
148
|
+
const unknownChildren = el.children.filter((c) => !knownLocalNames.has(localName(c.name)));
|
|
149
|
+
return {
|
|
150
|
+
id: requiredAttr(el, "id"),
|
|
151
|
+
name: attr(el, "name"),
|
|
152
|
+
decisionTable: tableEl ? parseDecisionTable(tableEl) : undefined,
|
|
153
|
+
informationRequirements: findChildren(el, "informationRequirement").map(parseInformationRequirement),
|
|
154
|
+
knowledgeRequirements: findChildren(el, "knowledgeRequirement").map(parseKnowledgeRequirement),
|
|
155
|
+
authorityRequirements: findChildren(el, "authorityRequirement").map(parseAuthorityRequirement),
|
|
156
|
+
extensionElements: extensionEls
|
|
157
|
+
? extensionEls.children
|
|
158
|
+
: unknownChildren.length > 0
|
|
159
|
+
? unknownChildren
|
|
160
|
+
: undefined,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function parseInputData(el) {
|
|
164
|
+
return {
|
|
165
|
+
id: requiredAttr(el, "id"),
|
|
166
|
+
name: attr(el, "name"),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function parseKnowledgeSource(el) {
|
|
170
|
+
return {
|
|
171
|
+
id: requiredAttr(el, "id"),
|
|
172
|
+
name: attr(el, "name"),
|
|
173
|
+
authorityRequirements: findChildren(el, "authorityRequirement").map(parseAuthorityRequirement),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function parseBusinessKnowledgeModel(el) {
|
|
177
|
+
return {
|
|
178
|
+
id: requiredAttr(el, "id"),
|
|
179
|
+
name: attr(el, "name"),
|
|
180
|
+
knowledgeRequirements: findChildren(el, "knowledgeRequirement").map(parseKnowledgeRequirement),
|
|
181
|
+
authorityRequirements: findChildren(el, "authorityRequirement").map(parseAuthorityRequirement),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function parseTextAnnotation(el) {
|
|
185
|
+
const textEl = findChild(el, "text");
|
|
186
|
+
return {
|
|
187
|
+
id: requiredAttr(el, "id"),
|
|
188
|
+
text: textEl?.text,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function parseAssociation(el) {
|
|
192
|
+
return {
|
|
193
|
+
id: requiredAttr(el, "id"),
|
|
194
|
+
sourceRef: deref(attr(el, "sourceRef")) ?? "",
|
|
195
|
+
targetRef: deref(attr(el, "targetRef")) ?? "",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function parseDiagram(dmndiEl) {
|
|
199
|
+
const diagramEl = findChild(dmndiEl, "DMNDiagram");
|
|
200
|
+
const shapes = [];
|
|
201
|
+
const edges = [];
|
|
202
|
+
if (diagramEl) {
|
|
203
|
+
for (const shapeEl of findChildren(diagramEl, "DMNShape")) {
|
|
204
|
+
const boundsEl = findChild(shapeEl, "Bounds");
|
|
205
|
+
if (boundsEl) {
|
|
206
|
+
shapes.push({
|
|
207
|
+
dmnElementRef: requiredAttr(shapeEl, "dmnElementRef"),
|
|
208
|
+
bounds: {
|
|
209
|
+
x: Number(attr(boundsEl, "x") ?? "0"),
|
|
210
|
+
y: Number(attr(boundsEl, "y") ?? "0"),
|
|
211
|
+
width: Number(attr(boundsEl, "width") ?? "0"),
|
|
212
|
+
height: Number(attr(boundsEl, "height") ?? "0"),
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
for (const edgeEl of findChildren(diagramEl, "DMNEdge")) {
|
|
218
|
+
const waypoints = findChildren(edgeEl, "waypoint").map((wp) => ({
|
|
219
|
+
x: Number(attr(wp, "x") ?? "0"),
|
|
220
|
+
y: Number(attr(wp, "y") ?? "0"),
|
|
221
|
+
}));
|
|
222
|
+
edges.push({
|
|
223
|
+
dmnElementRef: requiredAttr(edgeEl, "dmnElementRef"),
|
|
224
|
+
waypoints,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return { shapes, edges };
|
|
229
|
+
}
|
|
230
|
+
/** Parse a DMN XML string into a typed DmnDefinitions model. */
|
|
231
|
+
export function parseDmn(xml) {
|
|
232
|
+
const root = parseXml(xml);
|
|
233
|
+
if (localName(root.name) !== "definitions") {
|
|
234
|
+
throw new Error(`Expected <definitions> root element, got <${root.name}>`);
|
|
235
|
+
}
|
|
236
|
+
// Extract namespace declarations
|
|
237
|
+
const namespaces = {};
|
|
238
|
+
const modelerAttributes = {};
|
|
239
|
+
for (const [key, value] of Object.entries(root.attributes)) {
|
|
240
|
+
if (key.startsWith("xmlns:")) {
|
|
241
|
+
namespaces[key.slice(6)] = value;
|
|
242
|
+
}
|
|
243
|
+
else if (key === "xmlns") {
|
|
244
|
+
namespaces[""] = value;
|
|
245
|
+
}
|
|
246
|
+
else if (key.startsWith("modeler:")) {
|
|
247
|
+
modelerAttributes[key.slice(8)] = value;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const decisions = findChildren(root, "decision").map(parseDecision);
|
|
251
|
+
const inputData = findChildren(root, "inputData").map(parseInputData);
|
|
252
|
+
const knowledgeSources = findChildren(root, "knowledgeSource").map(parseKnowledgeSource);
|
|
253
|
+
const businessKnowledgeModels = findChildren(root, "businessKnowledgeModel").map(parseBusinessKnowledgeModel);
|
|
254
|
+
const textAnnotations = findChildren(root, "textAnnotation").map(parseTextAnnotation);
|
|
255
|
+
const associations = findChildren(root, "association").map(parseAssociation);
|
|
256
|
+
const dmndiEl = findChild(root, "DMNDI");
|
|
257
|
+
const diagram = dmndiEl ? parseDiagram(dmndiEl) : undefined;
|
|
258
|
+
return {
|
|
259
|
+
id: requiredAttr(root, "id"),
|
|
260
|
+
name: requiredAttr(root, "name"),
|
|
261
|
+
namespace: attr(root, "namespace") ?? DMN_NS,
|
|
262
|
+
exporter: attr(root, "exporter"),
|
|
263
|
+
exporterVersion: attr(root, "exporterVersion"),
|
|
264
|
+
namespaces,
|
|
265
|
+
modelerAttributes,
|
|
266
|
+
decisions,
|
|
267
|
+
inputData,
|
|
268
|
+
knowledgeSources,
|
|
269
|
+
businessKnowledgeModels,
|
|
270
|
+
textAnnotations,
|
|
271
|
+
associations,
|
|
272
|
+
diagram,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=dmn-parser.js.map
|