@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 CHANGED
@@ -186,6 +186,8 @@ const outXml = Bpmn.export(restored)
186
186
  | [`@bpmnkit/cli-sdk`](https://www.npmjs.com/package/@bpmnkit/cli-sdk) | Plugin authoring SDK for the casen CLI |
187
187
  | [`@bpmnkit/create-casen-plugin`](https://www.npmjs.com/package/@bpmnkit/create-casen-plugin) | Scaffold a new casen CLI plugin in seconds |
188
188
  | [`@bpmnkit/casen-report`](https://www.npmjs.com/package/@bpmnkit/casen-report) | HTML reports from Camunda 8 incident and SLA data |
189
+ | [`@bpmnkit/casen-worker-http`](https://www.npmjs.com/package/@bpmnkit/casen-worker-http) | Example HTTP worker plugin — completes jobs with live JSONPlaceholder API data |
190
+ | [`@bpmnkit/casen-worker-ai`](https://www.npmjs.com/package/@bpmnkit/casen-worker-ai) | AI task worker — classify, summarize, extract, and decide using Claude |
189
191
 
190
192
  ## License
191
193
 
@@ -30,6 +30,11 @@ export interface CompactElement {
30
30
  attachedTo?: string;
31
31
  /** Boundary event: false = non-interrupting */
32
32
  interrupting?: boolean;
33
+ /** Nested content for sub-process container types (subProcess, eventSubProcess, etc.) */
34
+ children?: {
35
+ elements: CompactElement[];
36
+ flows: CompactFlow[];
37
+ };
33
38
  }
34
39
  /** A sequence flow in compact form. */
35
40
  export interface CompactFlow {
@@ -57,6 +57,21 @@ function compactifyElement(el) {
57
57
  if (el.cancelActivity === false)
58
58
  result.interrupting = false;
59
59
  }
60
+ // Recurse into sub-process container children
61
+ if ("flowElements" in el && Array.isArray(el.flowElements) && el.flowElements.length > 0) {
62
+ const seqFlows = "sequenceFlows" in el && Array.isArray(el.sequenceFlows) ? el.sequenceFlows : [];
63
+ result.children = {
64
+ elements: el.flowElements.map(compactifyElement),
65
+ flows: seqFlows.map((sf) => {
66
+ const f = { id: sf.id, from: sf.sourceRef, to: sf.targetRef };
67
+ if (sf.name)
68
+ f.name = sf.name;
69
+ if (sf.conditionExpression)
70
+ f.condition = sf.conditionExpression.text;
71
+ return f;
72
+ }),
73
+ };
74
+ }
60
75
  return result;
61
76
  }
62
77
  /**
@@ -175,6 +190,35 @@ function makeExtensions(el) {
175
190
  }
176
191
  return ext;
177
192
  }
193
+ function buildSubContent(children) {
194
+ if (!children || children.elements.length === 0) {
195
+ return { flowElements: [], sequenceFlows: [], textAnnotations: [], associations: [] };
196
+ }
197
+ const inc = new Map();
198
+ const out = new Map();
199
+ for (const f of children.flows) {
200
+ const o = out.get(f.from) ?? [];
201
+ o.push(f.id);
202
+ out.set(f.from, o);
203
+ const i = inc.get(f.to) ?? [];
204
+ i.push(f.id);
205
+ inc.set(f.to, i);
206
+ }
207
+ return {
208
+ flowElements: children.elements.map((child) => buildFlowElement(child, inc.get(child.id) ?? [], out.get(child.id) ?? [])),
209
+ sequenceFlows: children.flows.map((f) => ({
210
+ id: f.id,
211
+ name: f.name,
212
+ sourceRef: f.from,
213
+ targetRef: f.to,
214
+ conditionExpression: f.condition ? { text: f.condition, attributes: {} } : undefined,
215
+ extensionElements: [],
216
+ unknownAttributes: {},
217
+ })),
218
+ textAnnotations: [],
219
+ associations: [],
220
+ };
221
+ }
178
222
  function buildFlowElement(el, incoming, outgoing) {
179
223
  const base = {
180
224
  id: el.id,
@@ -186,12 +230,7 @@ function buildFlowElement(el, incoming, outgoing) {
186
230
  };
187
231
  const eventDef = el.eventType ? makeEventDef(el.eventType) : undefined;
188
232
  const eventDefs = eventDef ? [eventDef] : [];
189
- const subContent = {
190
- flowElements: [],
191
- sequenceFlows: [],
192
- textAnnotations: [],
193
- associations: [],
194
- };
233
+ const subContent = buildSubContent(el.children);
195
234
  switch (el.type) {
196
235
  case "startEvent":
197
236
  return { ...base, type: "startEvent", eventDefinitions: eventDefs };
@@ -0,0 +1,82 @@
1
+ import type { CompactDiagram, CompactElement } from "./compact.js";
2
+ /**
3
+ * An atomic edit operation on a {@link CompactDiagram}.
4
+ * All element/flow references use stable string IDs, not array positions.
5
+ *
6
+ * Used by {@link applyOperations} to apply AI-suggested improvements without
7
+ * regenerating the full BPMN XML.
8
+ */
9
+ export type BpmnOperation =
10
+ /** Rename an element. */
11
+ {
12
+ op: "rename";
13
+ id: string;
14
+ name: string;
15
+ }
16
+ /** Patch arbitrary fields of an element (type-safe subset of CompactElement). */
17
+ | {
18
+ op: "update";
19
+ id: string;
20
+ patch: Partial<CompactElement>;
21
+ }
22
+ /** Remove an element by ID. */
23
+ | {
24
+ op: "delete";
25
+ id: string;
26
+ }
27
+ /**
28
+ * Insert a new element.
29
+ * Optionally place it after or before an existing element ID.
30
+ * Use `parent` to insert inside a sub-process container.
31
+ */
32
+ | {
33
+ op: "insert";
34
+ element: CompactElement;
35
+ after?: string;
36
+ before?: string;
37
+ parent?: string;
38
+ }
39
+ /**
40
+ * Add a sequence flow between two existing elements.
41
+ * Use `parent` when both elements are inside a sub-process.
42
+ */
43
+ | {
44
+ op: "add_flow";
45
+ id?: string;
46
+ from: string;
47
+ to: string;
48
+ condition?: string;
49
+ name?: string;
50
+ parent?: string;
51
+ }
52
+ /** Remove a sequence flow by ID. */
53
+ | {
54
+ op: "delete_flow";
55
+ id: string;
56
+ }
57
+ /** Redirect a sequence flow to a different source or target. */
58
+ | {
59
+ op: "redirect_flow";
60
+ id: string;
61
+ from?: string;
62
+ to?: string;
63
+ };
64
+ /**
65
+ * Apply a list of {@link BpmnOperation}s to a {@link CompactDiagram}, returning
66
+ * a new diagram (the original is not mutated).
67
+ *
68
+ * Operations are applied in order. Element and flow references use stable IDs
69
+ * so operations survive concurrent unrelated insertions.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const updated = applyOperations(compact, [
74
+ * { op: "rename", id: "task_1", name: "Approve Invoice" },
75
+ * { op: "insert", element: { id: "t_notify", type: "userTask", name: "Notify Finance" }, after: "task_1" },
76
+ * { op: "add_flow", from: "t_notify", to: "end_1" },
77
+ * ])
78
+ * const xml = Bpmn.export(expand(updated))
79
+ * ```
80
+ */
81
+ export declare function applyOperations(diagram: CompactDiagram, ops: BpmnOperation[]): CompactDiagram;
82
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1,152 @@
1
+ function findElementIn(container, id) {
2
+ for (let i = 0; i < container.elements.length; i++) {
3
+ const el = container.elements[i];
4
+ if (!el)
5
+ continue;
6
+ if (el.id === id)
7
+ return { container, element: el, index: i };
8
+ if (el.children) {
9
+ const found = findElementIn(el.children, id);
10
+ if (found)
11
+ return found;
12
+ }
13
+ }
14
+ return null;
15
+ }
16
+ function findFlowIn(container, id) {
17
+ for (let i = 0; i < container.flows.length; i++) {
18
+ const f = container.flows[i];
19
+ if (!f)
20
+ continue;
21
+ if (f.id === id)
22
+ return { container, flow: f, index: i };
23
+ }
24
+ for (const el of container.elements) {
25
+ if (el.children) {
26
+ const found = findFlowIn(el.children, id);
27
+ if (found)
28
+ return found;
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ function resolveContainer(process, parentId) {
34
+ if (!parentId)
35
+ return process;
36
+ const found = findElementIn(process, parentId);
37
+ if (!found)
38
+ return process;
39
+ if (!found.element.children)
40
+ found.element.children = { elements: [], flows: [] };
41
+ return found.element.children;
42
+ }
43
+ function nextFlowId(process) {
44
+ const ids = new Set();
45
+ const collect = (c) => {
46
+ for (const f of c.flows)
47
+ ids.add(f.id);
48
+ for (const e of c.elements)
49
+ if (e.children)
50
+ collect(e.children);
51
+ };
52
+ collect(process);
53
+ let n = ids.size + 1;
54
+ while (ids.has(`flow_${n}`))
55
+ n++;
56
+ return `flow_${n}`;
57
+ }
58
+ function applyOne(diagram, op) {
59
+ for (const process of diagram.processes) {
60
+ switch (op.op) {
61
+ case "rename": {
62
+ const found = findElementIn(process, op.id);
63
+ if (found)
64
+ found.element.name = op.name;
65
+ break;
66
+ }
67
+ case "update": {
68
+ const found = findElementIn(process, op.id);
69
+ if (found)
70
+ Object.assign(found.element, op.patch);
71
+ break;
72
+ }
73
+ case "delete": {
74
+ const found = findElementIn(process, op.id);
75
+ if (found)
76
+ found.container.elements.splice(found.index, 1);
77
+ break;
78
+ }
79
+ case "insert": {
80
+ const container = resolveContainer(process, op.parent);
81
+ if (op.after !== undefined) {
82
+ const idx = container.elements.findIndex((e) => e.id === op.after);
83
+ container.elements.splice(idx >= 0 ? idx + 1 : container.elements.length, 0, op.element);
84
+ }
85
+ else if (op.before !== undefined) {
86
+ const idx = container.elements.findIndex((e) => e.id === op.before);
87
+ container.elements.splice(idx >= 0 ? idx : 0, 0, op.element);
88
+ }
89
+ else {
90
+ container.elements.push(op.element);
91
+ }
92
+ break;
93
+ }
94
+ case "add_flow": {
95
+ const container = resolveContainer(process, op.parent);
96
+ const flow = {
97
+ id: op.id ?? nextFlowId(process),
98
+ from: op.from,
99
+ to: op.to,
100
+ };
101
+ if (op.name)
102
+ flow.name = op.name;
103
+ if (op.condition)
104
+ flow.condition = op.condition;
105
+ container.flows.push(flow);
106
+ break;
107
+ }
108
+ case "delete_flow": {
109
+ const found = findFlowIn(process, op.id);
110
+ if (found)
111
+ found.container.flows.splice(found.index, 1);
112
+ break;
113
+ }
114
+ case "redirect_flow": {
115
+ const found = findFlowIn(process, op.id);
116
+ if (found) {
117
+ if (op.from !== undefined)
118
+ found.flow.from = op.from;
119
+ if (op.to !== undefined)
120
+ found.flow.to = op.to;
121
+ }
122
+ break;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ // ── Public API ────────────────────────────────────────────────────────────────
128
+ /**
129
+ * Apply a list of {@link BpmnOperation}s to a {@link CompactDiagram}, returning
130
+ * a new diagram (the original is not mutated).
131
+ *
132
+ * Operations are applied in order. Element and flow references use stable IDs
133
+ * so operations survive concurrent unrelated insertions.
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const updated = applyOperations(compact, [
138
+ * { op: "rename", id: "task_1", name: "Approve Invoice" },
139
+ * { op: "insert", element: { id: "t_notify", type: "userTask", name: "Notify Finance" }, after: "task_1" },
140
+ * { op: "add_flow", from: "t_notify", to: "end_1" },
141
+ * ])
142
+ * const xml = Bpmn.export(expand(updated))
143
+ * ```
144
+ */
145
+ export function applyOperations(diagram, ops) {
146
+ const result = structuredClone(diagram);
147
+ for (const op of ops) {
148
+ applyOne(result, op);
149
+ }
150
+ return result;
151
+ }
152
+ //# sourceMappingURL=operations.js.map
@@ -1,8 +1,18 @@
1
1
  import { analyzeFeel } from "./feel.js";
2
2
  import { analyzeFlow } from "./flow.js";
3
3
  import { analyzeNaming } from "./naming.js";
4
+ import { analyzePatterns } from "./patterns.js";
4
5
  import { analyzeTasks } from "./tasks.js";
5
- const ALL_CATEGORIES = ["feel", "flow", "naming", "task-reuse", "extract"];
6
+ import { analyzeVariableFlow } from "./variable-flow.js";
7
+ const ALL_CATEGORIES = [
8
+ "feel",
9
+ "flow",
10
+ "naming",
11
+ "task-reuse",
12
+ "extract",
13
+ "pattern",
14
+ "data-flow",
15
+ ];
6
16
  function resolveOptions(opts) {
7
17
  return {
8
18
  feelLengthThreshold: opts?.feelLengthThreshold ?? 80,
@@ -30,11 +40,22 @@ export function optimize(defs, options) {
30
40
  if (resolved.categories.includes("task-reuse")) {
31
41
  findings.push(...analyzeTasks(process, resolved));
32
42
  }
43
+ if (resolved.categories.includes("pattern")) {
44
+ findings.push(...analyzePatterns(process));
45
+ }
46
+ if (resolved.categories.includes("data-flow")) {
47
+ findings.push(...analyzeVariableFlow(process));
48
+ }
33
49
  }
34
- const byCategory = Object.fromEntries(["feel", "flow", "naming", "task-reuse", "extract"].map((c) => [
35
- c,
36
- findings.filter((f) => f.category === c).length,
37
- ]));
50
+ const byCategory = Object.fromEntries([
51
+ "feel",
52
+ "flow",
53
+ "naming",
54
+ "task-reuse",
55
+ "extract",
56
+ "pattern",
57
+ "data-flow",
58
+ ].map((c) => [c, findings.filter((f) => f.category === c).length]));
38
59
  const bySeverity = Object.fromEntries(["info", "warning", "error"].map((s) => [
39
60
  s,
40
61
  findings.filter((f) => f.severity === s).length,
@@ -0,0 +1,4 @@
1
+ import type { BpmnProcess } from "../bpmn-model.js";
2
+ import type { OptimizationFinding } from "./types.js";
3
+ export declare function analyzePatterns(p: BpmnProcess): OptimizationFinding[];
4
+ //# sourceMappingURL=patterns.d.ts.map