@bpmnkit/core 0.0.14 → 0.0.16
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 +2 -0
- package/dist/bpmn/compact.d.ts +5 -0
- package/dist/bpmn/compact.js +45 -6
- package/dist/bpmn/operations.d.ts +82 -0
- package/dist/bpmn/operations.js +152 -0
- package/dist/bpmn/optimize/index.js +26 -5
- package/dist/bpmn/optimize/patterns.d.ts +4 -0
- package/dist/bpmn/optimize/patterns.js +365 -0
- package/dist/bpmn/optimize/types.d.ts +5 -1
- package/dist/bpmn/optimize/variable-flow.d.ts +6 -0
- package/dist/bpmn/optimize/variable-flow.js +431 -0
- package/dist/bpmn/story.d.ts +10 -0
- package/dist/bpmn/story.js +336 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +3 -0
- package/package.json +4 -2
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
2
|
+
function escapeHtml(s) {
|
|
3
|
+
return s
|
|
4
|
+
.replace(/&/g, "&")
|
|
5
|
+
.replace(/</g, "<")
|
|
6
|
+
.replace(/>/g, ">")
|
|
7
|
+
.replace(/"/g, """)
|
|
8
|
+
.replace(/'/g, "'");
|
|
9
|
+
}
|
|
10
|
+
function getCardInfo(el, laneName) {
|
|
11
|
+
switch (el.type) {
|
|
12
|
+
case "startEvent":
|
|
13
|
+
return { role: "start", header: "Process starts" };
|
|
14
|
+
case "endEvent":
|
|
15
|
+
return { role: "end", header: "Process ends" };
|
|
16
|
+
case "serviceTask":
|
|
17
|
+
return { role: "service", header: "System" };
|
|
18
|
+
case "userTask":
|
|
19
|
+
return { role: "user", header: laneName ?? "User" };
|
|
20
|
+
case "businessRuleTask":
|
|
21
|
+
return { role: "service", header: "Decision table" };
|
|
22
|
+
case "scriptTask":
|
|
23
|
+
return { role: "service", header: "Script" };
|
|
24
|
+
case "exclusiveGateway":
|
|
25
|
+
case "inclusiveGateway":
|
|
26
|
+
return { role: "gateway", header: "Decision" };
|
|
27
|
+
case "parallelGateway":
|
|
28
|
+
return { role: "parallel", header: "Parallel" };
|
|
29
|
+
case "callActivity":
|
|
30
|
+
return { role: "subprocess", header: "Sub-process" };
|
|
31
|
+
case "subProcess":
|
|
32
|
+
case "eventSubProcess":
|
|
33
|
+
case "transaction":
|
|
34
|
+
return { role: "subprocess", header: "Sub-process" };
|
|
35
|
+
case "intermediateCatchEvent":
|
|
36
|
+
case "intermediateThrowEvent":
|
|
37
|
+
case "boundaryEvent":
|
|
38
|
+
return { role: "event", header: "Event" };
|
|
39
|
+
default: {
|
|
40
|
+
const t = el.type;
|
|
41
|
+
const header = t.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase());
|
|
42
|
+
return { role: "task", header };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ── Topological sort (Kahn's algorithm) ─────────────────────────────────────
|
|
47
|
+
function topoSort(elements, flows) {
|
|
48
|
+
const idToEl = new Map();
|
|
49
|
+
for (const el of elements)
|
|
50
|
+
idToEl.set(el.id, el);
|
|
51
|
+
// in-degree and adjacency
|
|
52
|
+
const inDegree = new Map();
|
|
53
|
+
const successors = new Map();
|
|
54
|
+
for (const el of elements) {
|
|
55
|
+
inDegree.set(el.id, 0);
|
|
56
|
+
successors.set(el.id, []);
|
|
57
|
+
}
|
|
58
|
+
for (const flow of flows) {
|
|
59
|
+
if (!idToEl.has(flow.sourceRef) || !idToEl.has(flow.targetRef))
|
|
60
|
+
continue;
|
|
61
|
+
successors.get(flow.sourceRef)?.push(flow.targetRef);
|
|
62
|
+
inDegree.set(flow.targetRef, (inDegree.get(flow.targetRef) ?? 0) + 1);
|
|
63
|
+
}
|
|
64
|
+
const queue = [];
|
|
65
|
+
for (const [id, deg] of inDegree) {
|
|
66
|
+
if (deg === 0)
|
|
67
|
+
queue.push(id);
|
|
68
|
+
}
|
|
69
|
+
const result = [];
|
|
70
|
+
const visited = new Set();
|
|
71
|
+
while (queue.length > 0) {
|
|
72
|
+
const id = queue.shift();
|
|
73
|
+
if (id === undefined)
|
|
74
|
+
break;
|
|
75
|
+
if (visited.has(id))
|
|
76
|
+
continue;
|
|
77
|
+
visited.add(id);
|
|
78
|
+
const el = idToEl.get(id);
|
|
79
|
+
if (el)
|
|
80
|
+
result.push(el);
|
|
81
|
+
for (const next of successors.get(id) ?? []) {
|
|
82
|
+
const deg = (inDegree.get(next) ?? 1) - 1;
|
|
83
|
+
inDegree.set(next, deg);
|
|
84
|
+
if (deg === 0)
|
|
85
|
+
queue.push(next);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Handle cycles: append any unvisited elements in original order
|
|
89
|
+
for (const el of elements) {
|
|
90
|
+
if (!visited.has(el.id))
|
|
91
|
+
result.push(el);
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
// ── Lane mapping ─────────────────────────────────────────────────────────────
|
|
96
|
+
function buildLaneMap(process) {
|
|
97
|
+
const map = new Map();
|
|
98
|
+
if (!process.laneSet)
|
|
99
|
+
return map;
|
|
100
|
+
for (const lane of process.laneSet.lanes) {
|
|
101
|
+
const name = lane.name ?? lane.id;
|
|
102
|
+
for (const ref of lane.flowNodeRefs) {
|
|
103
|
+
map.set(ref, name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return map;
|
|
107
|
+
}
|
|
108
|
+
// ── Outgoing conditions ───────────────────────────────────────────────────────
|
|
109
|
+
function getOutgoingConditions(el, flows) {
|
|
110
|
+
const outgoing = new Set(el.outgoing);
|
|
111
|
+
const result = [];
|
|
112
|
+
for (const flow of flows) {
|
|
113
|
+
if (!outgoing.has(flow.id))
|
|
114
|
+
continue;
|
|
115
|
+
if (flow.conditionExpression) {
|
|
116
|
+
result.push({
|
|
117
|
+
label: flow.name ?? flow.targetRef,
|
|
118
|
+
condition: flow.conditionExpression.text,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
// ── Card HTML ─────────────────────────────────────────────────────────────────
|
|
125
|
+
function renderCard(el, flows, laneName) {
|
|
126
|
+
const { role, header } = getCardInfo(el, laneName);
|
|
127
|
+
const name = "name" in el ? (el.name ?? "") : "";
|
|
128
|
+
const conditions = role === "gateway" ? getOutgoingConditions(el, flows) : [];
|
|
129
|
+
let conditionsHtml = "";
|
|
130
|
+
if (conditions.length > 0) {
|
|
131
|
+
const items = conditions
|
|
132
|
+
.map((c) => `<div class="bks-condition"><span class="bks-condition-label">${escapeHtml(c.label)}</span><span class="bks-condition-expr">${escapeHtml(c.condition)}</span></div>`)
|
|
133
|
+
.join("");
|
|
134
|
+
conditionsHtml = `<div class="bks-conditions">${items}</div>`;
|
|
135
|
+
}
|
|
136
|
+
return `<div class="bks-card bks-card--${role}" data-bpmnkit-id="${escapeHtml(el.id)}"><div class="bks-card-header">${escapeHtml(header)}</div><div class="bks-card-body">${escapeHtml(name)}</div>${conditionsHtml}</div>`;
|
|
137
|
+
}
|
|
138
|
+
// ── Lane HTML ─────────────────────────────────────────────────────────────────
|
|
139
|
+
function renderLane(laneName, elements, flows, laneMap) {
|
|
140
|
+
if (elements.length === 0)
|
|
141
|
+
return "";
|
|
142
|
+
const cards = elements
|
|
143
|
+
.map((el, i) => {
|
|
144
|
+
const card = renderCard(el, flows, laneName === "_default" ? undefined : laneName);
|
|
145
|
+
const arrow = i < elements.length - 1 ? '<div class="bks-arrow">→</div>' : "";
|
|
146
|
+
return card + arrow;
|
|
147
|
+
})
|
|
148
|
+
.join("");
|
|
149
|
+
const laneHeader = laneName !== "_default" ? `<div class="bks-lane-header">${escapeHtml(laneName)}</div>` : "";
|
|
150
|
+
return `<div class="bks-lane">${laneHeader}<div class="bks-lane-cards">${cards}</div></div>`;
|
|
151
|
+
}
|
|
152
|
+
// ── Standalone CSS ────────────────────────────────────────────────────────────
|
|
153
|
+
function buildStandaloneCss(theme) {
|
|
154
|
+
const isDark = theme === "dark";
|
|
155
|
+
const vars = isDark
|
|
156
|
+
? `
|
|
157
|
+
--bks-bg: #0d0d16;
|
|
158
|
+
--bks-surface: #161626;
|
|
159
|
+
--bks-border: #2a2a42;
|
|
160
|
+
--bks-fg: #cdd6f4;
|
|
161
|
+
--bks-fg-muted: #8888a8;
|
|
162
|
+
--bks-accent: #6b9df7;
|
|
163
|
+
--bks-success: #22c55e;
|
|
164
|
+
--bks-danger: #f87171;
|
|
165
|
+
--bks-warn: #f59e0b;
|
|
166
|
+
--bks-teal: #2dd4bf;
|
|
167
|
+
--bks-purple: #a78bfa;`
|
|
168
|
+
: `
|
|
169
|
+
--bks-bg: #f4f4f8;
|
|
170
|
+
--bks-surface: #ffffff;
|
|
171
|
+
--bks-border: #d0d0e8;
|
|
172
|
+
--bks-fg: #1a1a2e;
|
|
173
|
+
--bks-fg-muted: #6666a0;
|
|
174
|
+
--bks-accent: #1a56db;
|
|
175
|
+
--bks-success: #16a34a;
|
|
176
|
+
--bks-danger: #dc2626;
|
|
177
|
+
--bks-warn: #d97706;
|
|
178
|
+
--bks-teal: #0d9488;
|
|
179
|
+
--bks-purple: #7c3aed;`;
|
|
180
|
+
return `
|
|
181
|
+
:root {${vars}
|
|
182
|
+
}
|
|
183
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
184
|
+
body {
|
|
185
|
+
background: var(--bks-bg);
|
|
186
|
+
color: var(--bks-fg);
|
|
187
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
188
|
+
font-size: 14px;
|
|
189
|
+
line-height: 1.5;
|
|
190
|
+
padding: 24px;
|
|
191
|
+
}
|
|
192
|
+
.bks-process-title {
|
|
193
|
+
font-size: 20px;
|
|
194
|
+
font-weight: 700;
|
|
195
|
+
margin-bottom: 20px;
|
|
196
|
+
color: var(--bks-fg);
|
|
197
|
+
}
|
|
198
|
+
.bks-lane {
|
|
199
|
+
margin-bottom: 16px;
|
|
200
|
+
}
|
|
201
|
+
.bks-lane-header {
|
|
202
|
+
font-size: 11px;
|
|
203
|
+
font-weight: 700;
|
|
204
|
+
text-transform: uppercase;
|
|
205
|
+
letter-spacing: 0.06em;
|
|
206
|
+
color: var(--bks-fg-muted);
|
|
207
|
+
padding: 4px 0 8px;
|
|
208
|
+
border-bottom: 1px solid var(--bks-border);
|
|
209
|
+
margin-bottom: 10px;
|
|
210
|
+
}
|
|
211
|
+
.bks-lane-cards {
|
|
212
|
+
display: flex;
|
|
213
|
+
flex-wrap: wrap;
|
|
214
|
+
align-items: center;
|
|
215
|
+
gap: 4px;
|
|
216
|
+
}
|
|
217
|
+
.bks-card {
|
|
218
|
+
background: var(--bks-surface);
|
|
219
|
+
border: 1px solid var(--bks-border);
|
|
220
|
+
border-radius: 8px;
|
|
221
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
222
|
+
padding: 10px 14px;
|
|
223
|
+
min-width: 120px;
|
|
224
|
+
max-width: 200px;
|
|
225
|
+
}
|
|
226
|
+
.bks-card-header {
|
|
227
|
+
font-size: 10px;
|
|
228
|
+
font-weight: 700;
|
|
229
|
+
text-transform: uppercase;
|
|
230
|
+
letter-spacing: 0.05em;
|
|
231
|
+
color: var(--bks-fg-muted);
|
|
232
|
+
margin-bottom: 4px;
|
|
233
|
+
}
|
|
234
|
+
.bks-card-body {
|
|
235
|
+
font-size: 13px;
|
|
236
|
+
font-weight: 500;
|
|
237
|
+
color: var(--bks-fg);
|
|
238
|
+
word-break: break-word;
|
|
239
|
+
}
|
|
240
|
+
.bks-card--start { border-left: 3px solid var(--bks-success); }
|
|
241
|
+
.bks-card--end { border-left: 3px solid var(--bks-fg-muted); }
|
|
242
|
+
.bks-card--service { border-left: 3px solid var(--bks-accent); }
|
|
243
|
+
.bks-card--user { border-left: 3px solid var(--bks-teal); }
|
|
244
|
+
.bks-card--gateway { border-left: 3px solid var(--bks-warn); }
|
|
245
|
+
.bks-card--parallel { border-left: 3px solid var(--bks-fg-muted); }
|
|
246
|
+
.bks-card--subprocess { border-left: 3px solid var(--bks-purple); }
|
|
247
|
+
.bks-card--event { border-left: 3px solid var(--bks-accent); }
|
|
248
|
+
.bks-card--task { border-left: 3px solid var(--bks-border); }
|
|
249
|
+
.bks-conditions {
|
|
250
|
+
margin-top: 6px;
|
|
251
|
+
display: flex;
|
|
252
|
+
flex-direction: column;
|
|
253
|
+
gap: 3px;
|
|
254
|
+
}
|
|
255
|
+
.bks-condition {
|
|
256
|
+
font-size: 11px;
|
|
257
|
+
display: flex;
|
|
258
|
+
gap: 4px;
|
|
259
|
+
flex-wrap: wrap;
|
|
260
|
+
}
|
|
261
|
+
.bks-condition-label {
|
|
262
|
+
font-weight: 600;
|
|
263
|
+
color: var(--bks-fg);
|
|
264
|
+
}
|
|
265
|
+
.bks-condition-expr {
|
|
266
|
+
color: var(--bks-fg-muted);
|
|
267
|
+
font-family: ui-monospace, monospace;
|
|
268
|
+
}
|
|
269
|
+
.bks-arrow {
|
|
270
|
+
color: var(--bks-fg-muted);
|
|
271
|
+
font-size: 18px;
|
|
272
|
+
padding: 0 2px;
|
|
273
|
+
flex-shrink: 0;
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
}
|
|
277
|
+
// ── Main renderer ─────────────────────────────────────────────────────────────
|
|
278
|
+
/** Render a BPMN process as a story-mode HTML string (no DOM required). */
|
|
279
|
+
export function renderStoryHtml(defs, options) {
|
|
280
|
+
const standalone = options?.standalone ?? false;
|
|
281
|
+
const theme = options?.theme ?? "light";
|
|
282
|
+
const process = defs.processes[0];
|
|
283
|
+
if (!process)
|
|
284
|
+
return standalone ? wrapDocument("", "", theme) : "";
|
|
285
|
+
const laneMap = buildLaneMap(process);
|
|
286
|
+
const sorted = topoSort(process.flowElements, process.sequenceFlows);
|
|
287
|
+
// Group by lane
|
|
288
|
+
const laneNames = new Set();
|
|
289
|
+
const laneElements = new Map();
|
|
290
|
+
if (process.laneSet && process.laneSet.lanes.length > 0) {
|
|
291
|
+
// Collect unique lane names in alpha order
|
|
292
|
+
const sortedLaneNames = process.laneSet.lanes
|
|
293
|
+
.map((l) => l.name ?? l.id)
|
|
294
|
+
.sort((a, b) => a.localeCompare(b));
|
|
295
|
+
for (const n of sortedLaneNames) {
|
|
296
|
+
laneNames.add(n);
|
|
297
|
+
laneElements.set(n, []);
|
|
298
|
+
}
|
|
299
|
+
// Place each element in its lane
|
|
300
|
+
for (const el of sorted) {
|
|
301
|
+
const lane = laneMap.get(el.id) ?? sortedLaneNames[0];
|
|
302
|
+
if (lane !== undefined) {
|
|
303
|
+
laneElements.get(lane)?.push(el);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
// No lane set — single default lane
|
|
309
|
+
laneNames.add("_default");
|
|
310
|
+
laneElements.set("_default", sorted);
|
|
311
|
+
}
|
|
312
|
+
let body = "";
|
|
313
|
+
for (const laneName of laneNames) {
|
|
314
|
+
const els = laneElements.get(laneName) ?? [];
|
|
315
|
+
body += renderLane(laneName, els, process.sequenceFlows, laneMap);
|
|
316
|
+
}
|
|
317
|
+
const processTitle = process.name ?? process.id;
|
|
318
|
+
const fragment = `<div class="bks-process-title">${escapeHtml(processTitle)}</div>${body}`;
|
|
319
|
+
if (standalone) {
|
|
320
|
+
return wrapDocument(fragment, buildStandaloneCss(theme), theme);
|
|
321
|
+
}
|
|
322
|
+
return fragment;
|
|
323
|
+
}
|
|
324
|
+
function wrapDocument(body, css, theme) {
|
|
325
|
+
return `<!DOCTYPE html>
|
|
326
|
+
<html lang="en" data-theme="${theme}">
|
|
327
|
+
<head>
|
|
328
|
+
<meta charset="UTF-8">
|
|
329
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
330
|
+
<title>BPMN Story View</title>
|
|
331
|
+
<style>${css}</style>
|
|
332
|
+
</head>
|
|
333
|
+
<body>${body}</body>
|
|
334
|
+
</html>`;
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=story.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,9 @@ export { parseXml, serializeXml } from "./xml/index.js";
|
|
|
24
24
|
export { readDiColor, writeDiColor, BIOC_NS, COLOR_NS } from "./bpmn/di-color.js";
|
|
25
25
|
export type { DiColor } from "./bpmn/di-color.js";
|
|
26
26
|
export { optimize } from "./bpmn/optimize/index.js";
|
|
27
|
+
export { renderStoryHtml } from "./bpmn/story.js";
|
|
28
|
+
export type { StoryRenderOptions } from "./bpmn/story.js";
|
|
29
|
+
export { analyzeVariableFlow, extractFeelIdentifiers } from "./bpmn/optimize/variable-flow.js";
|
|
27
30
|
export type { OptimizationReport, OptimizationFinding, OptimizationSeverity, OptimizationCategory, ApplyFixResult, OptimizeOptions, } from "./bpmn/optimize/types.js";
|
|
28
31
|
export { layoutProcess, layoutFlowNodes } from "./layout/index.js";
|
|
29
32
|
export { benchmarkLayout, compareLayouts, formatBenchmarkResult, generateAutoLayout, parseReferenceLayout, } from "./layout/index.js";
|
|
@@ -31,6 +34,8 @@ export type { BenchmarkResult, BoundingBox, ElementComparison, ElementPosition,
|
|
|
31
34
|
export type { Bounds, LayoutEdge, LayoutNode, LayoutResult, Waypoint } from "./layout/index.js";
|
|
32
35
|
export { ELEMENT_SIZES, GRID_CELL_HEIGHT } from "./layout/index.js";
|
|
33
36
|
export { compactify, expand } from "./bpmn/compact.js";
|
|
37
|
+
export { applyOperations } from "./bpmn/operations.js";
|
|
38
|
+
export type { BpmnOperation } from "./bpmn/operations.js";
|
|
34
39
|
export { exportSvg } from "./bpmn/svg.js";
|
|
35
40
|
export type { SvgExportOptions } from "./bpmn/svg.js";
|
|
36
41
|
export type { CompactDiagram, CompactElement, CompactFlow, CompactProcess, } from "./bpmn/compact.js";
|
package/dist/index.js
CHANGED
|
@@ -11,9 +11,12 @@ export { generateId, resetIdCounter } from "./types/id-generator.js";
|
|
|
11
11
|
export { parseXml, serializeXml } from "./xml/index.js";
|
|
12
12
|
export { readDiColor, writeDiColor, BIOC_NS, COLOR_NS } from "./bpmn/di-color.js";
|
|
13
13
|
export { optimize } from "./bpmn/optimize/index.js";
|
|
14
|
+
export { renderStoryHtml } from "./bpmn/story.js";
|
|
15
|
+
export { analyzeVariableFlow, extractFeelIdentifiers } from "./bpmn/optimize/variable-flow.js";
|
|
14
16
|
export { layoutProcess, layoutFlowNodes } from "./layout/index.js";
|
|
15
17
|
export { benchmarkLayout, compareLayouts, formatBenchmarkResult, generateAutoLayout, parseReferenceLayout, } from "./layout/index.js";
|
|
16
18
|
export { ELEMENT_SIZES, GRID_CELL_HEIGHT } from "./layout/index.js";
|
|
17
19
|
export { compactify, expand } from "./bpmn/compact.js";
|
|
20
|
+
export { applyOperations } from "./bpmn/operations.js";
|
|
18
21
|
export { exportSvg } from "./bpmn/svg.js";
|
|
19
22
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bpmnkit/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"dist/**/*.js",
|
|
17
17
|
"dist/**/*.d.ts"
|
|
18
18
|
],
|
|
19
|
-
"dependencies": {
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@bpmnkit/feel": "0.0.13"
|
|
21
|
+
},
|
|
20
22
|
"description": "TypeScript-first BPMN 2.0 SDK — parse, build, layout, and optimize diagrams",
|
|
21
23
|
"keywords": [
|
|
22
24
|
"bpmn",
|