@bpmnkit/core 0.1.1 → 0.1.2
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/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 +56 -0
- package/dist/bpmn/bpmn-builder.js +148 -182
- package/dist/bpmn/bpmn-model.d.ts +4 -0
- package/dist/bpmn/bpmn-parser.js +9 -1
- package/dist/bpmn/bpmn-serializer.js +6 -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 +5 -2
- package/dist/bpmn/optimize/flow.js +22 -2
- package/dist/bpmn/optimize/index.js +20 -9
- package/dist/bpmn/optimize/types.d.ts +10 -1
- package/dist/bpmn/zeebe-extensions.d.ts +27 -0
- package/dist/bpmn/zeebe-extensions.js +38 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +2 -0
- 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 +29 -0
- package/dist/layout/semantic/graph.js +217 -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 +454 -0
- package/dist/layout/types.d.ts +17 -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/package.json +2 -2
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { parseExpression } from "@bpmnkit/feel";
|
|
2
|
+
import { buildAiAgentSubProcess } from "../bpmn/agentic.js";
|
|
3
|
+
import { applyAutoLayout } from "../bpmn/auto-layout.js";
|
|
4
|
+
import { Bpmn } from "../bpmn/index.js";
|
|
5
|
+
import { optimize } from "../bpmn/optimize/index.js";
|
|
6
|
+
import { slugify, uniqueId } from "./slug.js";
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// ID assignment — every step gets a stable id before any builder call is made,
|
|
9
|
+
// so gateway branches can `connectTo()` the right target up front.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
function assignIds(steps, taken, idOf) {
|
|
12
|
+
for (const step of steps) {
|
|
13
|
+
const base = step.id ?? slugify(step.name ?? step.kind);
|
|
14
|
+
idOf.set(step, uniqueId(base, taken));
|
|
15
|
+
if (step.errorBoundary)
|
|
16
|
+
assignIds(step.errorBoundary.steps, taken, idOf);
|
|
17
|
+
if (step.timerBoundary)
|
|
18
|
+
assignIds(step.timerBoundary.steps, taken, idOf);
|
|
19
|
+
if (step.kind === "gateway") {
|
|
20
|
+
for (const branch of step.branches)
|
|
21
|
+
assignIds(branch.steps, taken, idOf);
|
|
22
|
+
}
|
|
23
|
+
if (step.kind === "subProcess")
|
|
24
|
+
assignIds(step.steps, taken, idOf);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// FEEL validation
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
function checkFeel(path, value, problems) {
|
|
31
|
+
if (!value || !value.startsWith("="))
|
|
32
|
+
return;
|
|
33
|
+
const { errors } = parseExpression(value.slice(1));
|
|
34
|
+
for (const err of errors) {
|
|
35
|
+
problems.push({ path, message: `Invalid FEEL expression: ${err.message}` });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Connector / agent tool resolution
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
function resolveConnectorOrProblem(ref, path, resolve, problems) {
|
|
42
|
+
if (!resolve) {
|
|
43
|
+
problems.push({
|
|
44
|
+
path,
|
|
45
|
+
message: `Step references connector template "${ref.template}" but no connector resolver was provided to compilePlan()`,
|
|
46
|
+
});
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const result = resolve(ref.template, ref.values ?? {});
|
|
50
|
+
for (const p of result.problems) {
|
|
51
|
+
problems.push({ path: p.key ? `${path}.values.${p.key}` : path, message: p.message });
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
function toolServiceTaskOptions(tool, path, resolve, problems) {
|
|
56
|
+
if (tool.connector) {
|
|
57
|
+
const result = resolveConnectorOrProblem(tool.connector, path, resolve, problems);
|
|
58
|
+
if (result?.serviceTask)
|
|
59
|
+
return result.serviceTask;
|
|
60
|
+
return { name: tool.id, taskType: "" };
|
|
61
|
+
}
|
|
62
|
+
return { name: tool.id, taskType: tool.jobType ?? "" };
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Step emission
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
function emitStep(b, step, id, path, nextId, idOf, resolve, problems) {
|
|
68
|
+
switch (step.kind) {
|
|
69
|
+
case "start":
|
|
70
|
+
// Handled by the caller — start steps are only valid as plan.steps[0].
|
|
71
|
+
return;
|
|
72
|
+
case "connector": {
|
|
73
|
+
const result = resolveConnectorOrProblem(step.connector, `${path}.connector`, resolve, problems);
|
|
74
|
+
b.serviceTask(id, {
|
|
75
|
+
...(result?.serviceTask ?? { name: step.name ?? id, taskType: "" }),
|
|
76
|
+
documentation: step.documentation,
|
|
77
|
+
...(step.retries ? { retries: step.retries } : {}),
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
case "serviceTask": {
|
|
82
|
+
const inputs = Object.entries(step.inputs ?? {}).map(([target, source]) => ({
|
|
83
|
+
source,
|
|
84
|
+
target,
|
|
85
|
+
}));
|
|
86
|
+
const outputs = Object.entries(step.outputs ?? {}).map(([target, source]) => ({
|
|
87
|
+
source,
|
|
88
|
+
target,
|
|
89
|
+
}));
|
|
90
|
+
for (const [k, v] of Object.entries(step.inputs ?? {}))
|
|
91
|
+
checkFeel(`${path}.inputs.${k}`, v, problems);
|
|
92
|
+
for (const [k, v] of Object.entries(step.outputs ?? {}))
|
|
93
|
+
checkFeel(`${path}.outputs.${k}`, v, problems);
|
|
94
|
+
b.serviceTask(id, {
|
|
95
|
+
name: step.name ?? id,
|
|
96
|
+
documentation: step.documentation,
|
|
97
|
+
taskType: step.jobType,
|
|
98
|
+
retries: step.retries,
|
|
99
|
+
taskHeaders: step.taskHeaders,
|
|
100
|
+
ioMapping: inputs.length || outputs.length ? { inputs, outputs } : undefined,
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
case "userTask":
|
|
105
|
+
b.userTask(id, {
|
|
106
|
+
name: step.name ?? id,
|
|
107
|
+
documentation: step.documentation,
|
|
108
|
+
zeebeUserTask: true,
|
|
109
|
+
formId: step.formId,
|
|
110
|
+
assignee: step.assignee,
|
|
111
|
+
candidateGroups: step.candidateGroups,
|
|
112
|
+
candidateUsers: step.candidateUsers,
|
|
113
|
+
dueDate: step.dueDate,
|
|
114
|
+
followUpDate: step.followUpDate,
|
|
115
|
+
priority: step.priority,
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
case "businessRuleTask":
|
|
119
|
+
b.businessRuleTask(id, {
|
|
120
|
+
name: step.name ?? id,
|
|
121
|
+
documentation: step.documentation,
|
|
122
|
+
decisionId: step.decisionId,
|
|
123
|
+
resultVariable: step.resultVariable,
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
case "scriptTask":
|
|
127
|
+
checkFeel(`${path}.expression`, step.expression, problems);
|
|
128
|
+
b.scriptTask(id, {
|
|
129
|
+
name: step.name ?? id,
|
|
130
|
+
documentation: step.documentation,
|
|
131
|
+
expression: step.expression,
|
|
132
|
+
resultVariable: step.resultVariable,
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
case "sendTask":
|
|
136
|
+
b.sendTask(id, {
|
|
137
|
+
name: step.name ?? id,
|
|
138
|
+
documentation: step.documentation,
|
|
139
|
+
messageName: step.messageName,
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
case "receiveTask":
|
|
143
|
+
b.receiveTask(id, {
|
|
144
|
+
name: step.name ?? id,
|
|
145
|
+
documentation: step.documentation,
|
|
146
|
+
messageName: step.messageName,
|
|
147
|
+
correlationKey: step.correlationKey,
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
case "callActivity":
|
|
151
|
+
b.callActivity(id, {
|
|
152
|
+
name: step.name ?? id,
|
|
153
|
+
documentation: step.documentation,
|
|
154
|
+
processId: step.processId,
|
|
155
|
+
propagateAllChildVariables: step.propagateAllChildVariables,
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
case "aiAgent": {
|
|
159
|
+
checkFeel(`${path}.systemPrompt`, step.systemPrompt, problems);
|
|
160
|
+
checkFeel(`${path}.userPrompt`, step.userPrompt, problems);
|
|
161
|
+
checkFeel(`${path}.completionCondition`, step.completionCondition, problems);
|
|
162
|
+
if (step.tools.length === 0) {
|
|
163
|
+
problems.push({ path: `${path}.tools`, message: "aiAgent step has no tools" });
|
|
164
|
+
}
|
|
165
|
+
const agent = buildAiAgentSubProcess({
|
|
166
|
+
id,
|
|
167
|
+
name: step.name,
|
|
168
|
+
model: {
|
|
169
|
+
provider: step.provider,
|
|
170
|
+
inputs: { [`provider.${step.provider}.model.model`]: step.model, ...step.providerInputs },
|
|
171
|
+
},
|
|
172
|
+
systemPrompt: step.systemPrompt,
|
|
173
|
+
userPrompt: step.userPrompt,
|
|
174
|
+
memoryStorageType: step.memoryStorageType,
|
|
175
|
+
maxModelCalls: step.maxModelCalls,
|
|
176
|
+
outputVariable: step.outputVariable,
|
|
177
|
+
retries: step.retries,
|
|
178
|
+
completionCondition: step.completionCondition,
|
|
179
|
+
cancelRemainingInstances: step.cancelRemainingInstances,
|
|
180
|
+
tools: step.tools.map((tool) => ({
|
|
181
|
+
id: tool.id,
|
|
182
|
+
description: tool.description,
|
|
183
|
+
serviceTask: toolServiceTaskOptions(tool, `${path}.tools[${tool.id}]`, resolve, problems),
|
|
184
|
+
params: (tool.params ?? []).map((p) => ({
|
|
185
|
+
name: p.name,
|
|
186
|
+
description: p.description,
|
|
187
|
+
type: p.type,
|
|
188
|
+
required: p.required,
|
|
189
|
+
schema: p.schema,
|
|
190
|
+
target: p.target ?? p.name,
|
|
191
|
+
})),
|
|
192
|
+
resultSource: tool.resultExpression,
|
|
193
|
+
})),
|
|
194
|
+
});
|
|
195
|
+
b.adHocSubProcess(id, agent.content, agent.options);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
case "gateway": {
|
|
199
|
+
const gwOptions = { name: step.name, documentation: step.documentation };
|
|
200
|
+
switch (step.gatewayType) {
|
|
201
|
+
case "exclusive":
|
|
202
|
+
b.exclusiveGateway(id, gwOptions);
|
|
203
|
+
break;
|
|
204
|
+
case "parallel":
|
|
205
|
+
b.parallelGateway(id, gwOptions);
|
|
206
|
+
break;
|
|
207
|
+
case "inclusive":
|
|
208
|
+
b.inclusiveGateway(id, gwOptions);
|
|
209
|
+
break;
|
|
210
|
+
case "eventBased":
|
|
211
|
+
b.eventBasedGateway(id, gwOptions);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
for (let bi = 0; bi < step.branches.length; bi++) {
|
|
215
|
+
const branch = step.branches[bi];
|
|
216
|
+
checkFeel(`${path}.branches[${bi}].condition`, branch.condition, problems);
|
|
217
|
+
b.branch(branch.name ?? `branch_${bi + 1}`, (bb) => {
|
|
218
|
+
if (branch.default)
|
|
219
|
+
bb.defaultFlow();
|
|
220
|
+
else if (branch.condition)
|
|
221
|
+
bb.condition(branch.condition);
|
|
222
|
+
emitSteps(bb, branch.steps, idOf, resolve, problems, `${path}.branches[${bi}].steps`);
|
|
223
|
+
if (nextId)
|
|
224
|
+
bb.connectTo(nextId);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
case "subProcess":
|
|
230
|
+
b.subProcess(id, (sb) => emitSteps(sb, step.steps, idOf, resolve, problems, `${path}.steps`), {
|
|
231
|
+
name: step.name,
|
|
232
|
+
documentation: step.documentation,
|
|
233
|
+
multiInstance: step.multiInstance
|
|
234
|
+
? {
|
|
235
|
+
isSequential: step.multiInstance.isSequential,
|
|
236
|
+
collection: step.multiInstance.collection,
|
|
237
|
+
elementVariable: step.multiInstance.elementVariable,
|
|
238
|
+
completionCondition: step.multiInstance.completionCondition,
|
|
239
|
+
}
|
|
240
|
+
: undefined,
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
case "wait":
|
|
244
|
+
checkFeel(`${path}.message.correlationKey`, step.message?.correlationKey, problems);
|
|
245
|
+
b.intermediateCatchEvent(id, {
|
|
246
|
+
name: step.name ?? id,
|
|
247
|
+
documentation: step.documentation,
|
|
248
|
+
timerDuration: step.timer?.duration,
|
|
249
|
+
timerDate: step.timer?.date,
|
|
250
|
+
timerCycle: step.timer?.cycle,
|
|
251
|
+
messageName: step.message?.name,
|
|
252
|
+
correlationKey: step.message?.correlationKey,
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
case "end":
|
|
256
|
+
b.endEvent(id, {
|
|
257
|
+
name: step.name ?? id,
|
|
258
|
+
documentation: step.documentation,
|
|
259
|
+
errorCode: step.errorCode,
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
case "raw":
|
|
263
|
+
problems.push({
|
|
264
|
+
path,
|
|
265
|
+
message: `"raw" steps are not yet compiled — element type "${step.elementType}" was skipped`,
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function emitBoundaries(b, step, id, path, idOf, resolve, problems) {
|
|
271
|
+
if (step.errorBoundary) {
|
|
272
|
+
b.withBoundary(`${id}_error`, {
|
|
273
|
+
errorCode: step.errorBoundary.errorCode,
|
|
274
|
+
cancelActivity: step.errorBoundary.interrupting ?? true,
|
|
275
|
+
}, (hb) => emitSteps(hb, step.errorBoundary.steps, idOf, resolve, problems, `${path}.errorBoundary.steps`));
|
|
276
|
+
}
|
|
277
|
+
if (step.timerBoundary) {
|
|
278
|
+
b.withBoundary(`${id}_timer`, {
|
|
279
|
+
timerDuration: step.timerBoundary.duration,
|
|
280
|
+
timerDate: step.timerBoundary.date,
|
|
281
|
+
timerCycle: step.timerBoundary.cycle,
|
|
282
|
+
cancelActivity: step.timerBoundary.interrupting ?? true,
|
|
283
|
+
}, (hb) => emitSteps(hb, step.timerBoundary.steps, idOf, resolve, problems, `${path}.timerBoundary.steps`));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function emitSteps(b, steps, idOf, resolve, problems, path, indexOffset = 0) {
|
|
287
|
+
for (let i = 0; i < steps.length; i++) {
|
|
288
|
+
const step = steps[i];
|
|
289
|
+
const id = idOf.get(step);
|
|
290
|
+
if (!id)
|
|
291
|
+
continue;
|
|
292
|
+
const stepPath = `${path}[${i + indexOffset}]`;
|
|
293
|
+
const nextStep = steps[i + 1];
|
|
294
|
+
const nextId = nextStep ? idOf.get(nextStep) : undefined;
|
|
295
|
+
emitStep(b, step, id, stepPath, nextId, idOf, resolve, problems);
|
|
296
|
+
emitBoundaries(b, step, id, stepPath, idOf, resolve, problems);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// Public API
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
/**
|
|
303
|
+
* Compiles a `ProcessPlan` into laid-out, validated BPMN XML. Deterministic:
|
|
304
|
+
* the same plan always produces the same XML. Problems are collected, not
|
|
305
|
+
* thrown — check `result.problems` before using `result.xml`.
|
|
306
|
+
*/
|
|
307
|
+
export function compilePlan(plan, opts = {}) {
|
|
308
|
+
const problems = [];
|
|
309
|
+
if (plan.version !== 1) {
|
|
310
|
+
problems.push({ path: "version", message: `Unsupported plan version ${String(plan.version)}` });
|
|
311
|
+
return { problems };
|
|
312
|
+
}
|
|
313
|
+
if (!plan.process?.id) {
|
|
314
|
+
problems.push({ path: "process.id", message: "process.id is required" });
|
|
315
|
+
return { problems };
|
|
316
|
+
}
|
|
317
|
+
if (!plan.steps || plan.steps.length === 0) {
|
|
318
|
+
problems.push({ path: "steps", message: "Plan has no steps" });
|
|
319
|
+
return { problems };
|
|
320
|
+
}
|
|
321
|
+
const [firstStep, ...restSteps] = plan.steps;
|
|
322
|
+
if (!firstStep || firstStep.kind !== "start") {
|
|
323
|
+
problems.push({ path: "steps[0]", message: 'The first step must have kind "start"' });
|
|
324
|
+
return { problems };
|
|
325
|
+
}
|
|
326
|
+
const taken = new Set();
|
|
327
|
+
const idOf = new Map();
|
|
328
|
+
assignIds(plan.steps, taken, idOf);
|
|
329
|
+
const builder = Bpmn.createProcess(plan.process.id);
|
|
330
|
+
if (plan.process.name)
|
|
331
|
+
builder.name(plan.process.name);
|
|
332
|
+
if (plan.process.versionTag)
|
|
333
|
+
builder.versionTag(plan.process.versionTag);
|
|
334
|
+
let startResult;
|
|
335
|
+
if (firstStep.connector) {
|
|
336
|
+
startResult = resolveConnectorOrProblem(firstStep.connector, "steps[0].connector", opts.resolveConnector, problems);
|
|
337
|
+
}
|
|
338
|
+
const startId = idOf.get(firstStep);
|
|
339
|
+
builder.startEvent(startId, {
|
|
340
|
+
name: firstStep.name ?? "Start",
|
|
341
|
+
documentation: firstStep.documentation,
|
|
342
|
+
timerDuration: firstStep.timer?.duration,
|
|
343
|
+
timerDate: firstStep.timer?.date,
|
|
344
|
+
timerCycle: firstStep.timer?.cycle,
|
|
345
|
+
messageName: firstStep.message?.name,
|
|
346
|
+
zeebeProperties: startResult?.startEvent?.zeebeProperties,
|
|
347
|
+
});
|
|
348
|
+
emitSteps(builder, restSteps, idOf, opts.resolveConnector, problems, "steps", 1);
|
|
349
|
+
let defs;
|
|
350
|
+
try {
|
|
351
|
+
defs = builder.build();
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
problems.push({
|
|
355
|
+
path: "steps",
|
|
356
|
+
message: `Build failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
357
|
+
});
|
|
358
|
+
return { problems };
|
|
359
|
+
}
|
|
360
|
+
let laidOut = applyAutoLayout(defs);
|
|
361
|
+
if (!opts.skipAutoFix) {
|
|
362
|
+
const report = optimize(laidOut);
|
|
363
|
+
for (const finding of report.findings) {
|
|
364
|
+
finding.applyFix?.(laidOut);
|
|
365
|
+
}
|
|
366
|
+
laidOut = applyAutoLayout(laidOut);
|
|
367
|
+
}
|
|
368
|
+
const finalReport = optimize(laidOut);
|
|
369
|
+
for (const finding of finalReport.findings) {
|
|
370
|
+
if (finding.severity === "error") {
|
|
371
|
+
problems.push({
|
|
372
|
+
path: finding.elementIds.length > 0 ? `element:${finding.elementIds.join(",")}` : "process",
|
|
373
|
+
message: finding.message,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const xml = Bpmn.export(laidOut);
|
|
378
|
+
return { defs: laidOut, xml, problems };
|
|
379
|
+
}
|
|
380
|
+
//# sourceMappingURL=compile.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifts an existing `BpmnDefinitions` process back into `ProcessPlan` form,
|
|
3
|
+
* so `/bpmnkit:extend`-style skills can express a delta instead of
|
|
4
|
+
* regenerating a whole process.
|
|
5
|
+
*
|
|
6
|
+
* Scope (documented, not silently exceeded): linear chains of tasks/events,
|
|
7
|
+
* plus a single level of exclusive/parallel/inclusive gateway branches that
|
|
8
|
+
* reconverge to a common next element. Sub-processes, ad-hoc sub-processes
|
|
9
|
+
* (including AI Agent sub-processes), nested gateways, pools/lanes, and data
|
|
10
|
+
* objects are not lifted — they are reported in `unsupported` rather than
|
|
11
|
+
* silently dropped or guessed at. Use `mergePlan()` to add new steps to a
|
|
12
|
+
* process without needing to fully extract it first.
|
|
13
|
+
*/
|
|
14
|
+
import type { BpmnDefinitions } from "../bpmn/bpmn-model.js";
|
|
15
|
+
import type { ProcessPlan } from "./types.js";
|
|
16
|
+
export interface UnsupportedElement {
|
|
17
|
+
id: string;
|
|
18
|
+
type: string;
|
|
19
|
+
reason: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ExtractResult {
|
|
22
|
+
plan: ProcessPlan;
|
|
23
|
+
unsupported: UnsupportedElement[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extracts a single BPMN process into `ProcessPlan` form. Handles linear
|
|
27
|
+
* chains and a single level of gateway branching that reconverges; anything
|
|
28
|
+
* else is listed in `unsupported`, not fabricated.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractPlan(defs: BpmnDefinitions, processId?: string): ExtractResult;
|
|
31
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { readZeebeIoMapping, readZeebeTaskHeaders, readZeebeTaskType, } from "../bpmn/optimize/utils.js";
|
|
2
|
+
function ioMappingToRecords(ext) {
|
|
3
|
+
const io = readZeebeIoMapping(ext);
|
|
4
|
+
if (!io)
|
|
5
|
+
return {};
|
|
6
|
+
const inputs = {};
|
|
7
|
+
for (const i of io.inputs)
|
|
8
|
+
inputs[i.target] = i.source;
|
|
9
|
+
const outputs = {};
|
|
10
|
+
for (const o of io.outputs)
|
|
11
|
+
outputs[o.target] = o.source;
|
|
12
|
+
return {
|
|
13
|
+
inputs: Object.keys(inputs).length > 0 ? inputs : undefined,
|
|
14
|
+
outputs: Object.keys(outputs).length > 0 ? outputs : undefined,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function taskHeadersOf(ext) {
|
|
18
|
+
const h = readZeebeTaskHeaders(ext);
|
|
19
|
+
if (!h || h.headers.length === 0)
|
|
20
|
+
return undefined;
|
|
21
|
+
const out = {};
|
|
22
|
+
for (const entry of h.headers)
|
|
23
|
+
out[entry.key] = entry.value;
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function findExt(el, name) {
|
|
27
|
+
return el.extensionElements.find((e) => e.name === name);
|
|
28
|
+
}
|
|
29
|
+
function extractStep(el, unsupported, rootErrors) {
|
|
30
|
+
const base = { id: el.id, name: el.name, documentation: el.documentation };
|
|
31
|
+
switch (el.type) {
|
|
32
|
+
case "startEvent":
|
|
33
|
+
return { ...base, kind: "start" };
|
|
34
|
+
case "endEvent": {
|
|
35
|
+
const errorDef = el.eventDefinitions.find((d) => d.type === "error");
|
|
36
|
+
const errorCode = errorDef
|
|
37
|
+
? rootErrors.find((e) => e.id === errorDef.errorRef)?.errorCode
|
|
38
|
+
: undefined;
|
|
39
|
+
const terminate = el.eventDefinitions.some((d) => d.type === "terminate");
|
|
40
|
+
return { ...base, kind: "end", errorCode, terminate: terminate || undefined };
|
|
41
|
+
}
|
|
42
|
+
case "serviceTask": {
|
|
43
|
+
const jobType = readZeebeTaskType(el.extensionElements);
|
|
44
|
+
if (!jobType) {
|
|
45
|
+
unsupported.push({
|
|
46
|
+
id: el.id,
|
|
47
|
+
type: el.type,
|
|
48
|
+
reason: "service task has no zeebe:taskDefinition type",
|
|
49
|
+
});
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
...base,
|
|
54
|
+
kind: "serviceTask",
|
|
55
|
+
jobType,
|
|
56
|
+
taskHeaders: taskHeadersOf(el.extensionElements),
|
|
57
|
+
...ioMappingToRecords(el.extensionElements),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
case "userTask": {
|
|
61
|
+
const assignment = findExt(el, "zeebe:assignmentDefinition");
|
|
62
|
+
const schedule = findExt(el, "zeebe:taskSchedule");
|
|
63
|
+
const priority = findExt(el, "zeebe:priorityDefinition");
|
|
64
|
+
const form = findExt(el, "zeebe:formDefinition");
|
|
65
|
+
return {
|
|
66
|
+
...base,
|
|
67
|
+
kind: "userTask",
|
|
68
|
+
formId: form?.attributes.formId,
|
|
69
|
+
assignee: assignment?.attributes.assignee,
|
|
70
|
+
candidateGroups: assignment?.attributes.candidateGroups,
|
|
71
|
+
candidateUsers: assignment?.attributes.candidateUsers,
|
|
72
|
+
dueDate: schedule?.attributes.dueDate,
|
|
73
|
+
followUpDate: schedule?.attributes.followUpDate,
|
|
74
|
+
priority: priority?.attributes.priority ? Number(priority.attributes.priority) : undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
case "businessRuleTask": {
|
|
78
|
+
const decision = findExt(el, "zeebe:calledDecision");
|
|
79
|
+
if (!decision) {
|
|
80
|
+
unsupported.push({
|
|
81
|
+
id: el.id,
|
|
82
|
+
type: el.type,
|
|
83
|
+
reason: "business rule task has no zeebe:calledDecision",
|
|
84
|
+
});
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
...base,
|
|
89
|
+
kind: "businessRuleTask",
|
|
90
|
+
decisionId: decision.attributes.decisionId ?? "",
|
|
91
|
+
resultVariable: decision.attributes.resultVariable,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
case "callActivity": {
|
|
95
|
+
const called = findExt(el, "zeebe:calledElement");
|
|
96
|
+
return {
|
|
97
|
+
...base,
|
|
98
|
+
kind: "callActivity",
|
|
99
|
+
processId: called?.attributes.processId ?? "",
|
|
100
|
+
propagateAllChildVariables: called?.attributes.propagateAllChildVariables === "true",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
default:
|
|
104
|
+
unsupported.push({
|
|
105
|
+
id: el.id,
|
|
106
|
+
type: el.type,
|
|
107
|
+
reason: `element type "${el.type}" is not liftable yet`,
|
|
108
|
+
});
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Extracts a single BPMN process into `ProcessPlan` form. Handles linear
|
|
114
|
+
* chains and a single level of gateway branching that reconverges; anything
|
|
115
|
+
* else is listed in `unsupported`, not fabricated.
|
|
116
|
+
*/
|
|
117
|
+
export function extractPlan(defs, processId) {
|
|
118
|
+
const process = processId
|
|
119
|
+
? defs.processes.find((p) => p.id === processId)
|
|
120
|
+
: defs.processes[0];
|
|
121
|
+
if (!process) {
|
|
122
|
+
return {
|
|
123
|
+
plan: { version: 1, process: { id: processId ?? "unknown" }, steps: [] },
|
|
124
|
+
unsupported: [{ id: processId ?? "unknown", type: "process", reason: "process not found" }],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const unsupported = [];
|
|
128
|
+
const bySource = new Map();
|
|
129
|
+
for (const flow of process.sequenceFlows) {
|
|
130
|
+
const list = bySource.get(flow.sourceRef) ?? [];
|
|
131
|
+
list.push(flow);
|
|
132
|
+
bySource.set(flow.sourceRef, list);
|
|
133
|
+
}
|
|
134
|
+
const byId = new Map(process.flowElements.map((e) => [e.id, e]));
|
|
135
|
+
const start = process.flowElements.find((e) => e.type === "startEvent");
|
|
136
|
+
if (!start) {
|
|
137
|
+
return {
|
|
138
|
+
plan: { version: 1, process: { id: process.id, name: process.name }, steps: [] },
|
|
139
|
+
unsupported: [{ id: process.id, type: "process", reason: "no start event" }],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const steps = [];
|
|
143
|
+
const visited = new Set();
|
|
144
|
+
function walkFrom(elementId) {
|
|
145
|
+
let currentId = elementId;
|
|
146
|
+
while (currentId && !visited.has(currentId)) {
|
|
147
|
+
visited.add(currentId);
|
|
148
|
+
const el = byId.get(currentId);
|
|
149
|
+
if (!el)
|
|
150
|
+
return;
|
|
151
|
+
const outgoing = bySource.get(currentId) ?? [];
|
|
152
|
+
const isGatewayType = el.type === "exclusiveGateway" ||
|
|
153
|
+
el.type === "parallelGateway" ||
|
|
154
|
+
el.type === "inclusiveGateway";
|
|
155
|
+
// A gateway with at most one outgoing flow makes no decision — it's a
|
|
156
|
+
// join the compiler auto-inserted (or an equivalent pass-through), not
|
|
157
|
+
// something the plan format needs to represent as a step.
|
|
158
|
+
if (isGatewayType && outgoing.length <= 1) {
|
|
159
|
+
currentId = outgoing[0]?.targetRef;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (isGatewayType) {
|
|
163
|
+
const gatewayType = el.type === "exclusiveGateway"
|
|
164
|
+
? "exclusive"
|
|
165
|
+
: el.type === "parallelGateway"
|
|
166
|
+
? "parallel"
|
|
167
|
+
: "inclusive";
|
|
168
|
+
const branches = [];
|
|
169
|
+
for (const flow of outgoing) {
|
|
170
|
+
const branchSteps = [];
|
|
171
|
+
const branchVisited = new Set();
|
|
172
|
+
let branchCursor = flow.targetRef;
|
|
173
|
+
while (branchCursor && !visited.has(branchCursor) && !branchVisited.has(branchCursor)) {
|
|
174
|
+
const branchEl = byId.get(branchCursor);
|
|
175
|
+
if (!branchEl)
|
|
176
|
+
break;
|
|
177
|
+
const branchOutgoing = bySource.get(branchCursor) ?? [];
|
|
178
|
+
const branchElIsGateway = branchEl.type === "exclusiveGateway" ||
|
|
179
|
+
branchEl.type === "parallelGateway" ||
|
|
180
|
+
branchEl.type === "inclusiveGateway";
|
|
181
|
+
// A gateway with ≤1 outgoing flow here is the join this split
|
|
182
|
+
// reconverges to — stop the branch walk without consuming it, so
|
|
183
|
+
// the outer walk's convergence scan can pick it up.
|
|
184
|
+
if (branchElIsGateway && branchOutgoing.length <= 1)
|
|
185
|
+
break;
|
|
186
|
+
if (branchOutgoing.length > 1) {
|
|
187
|
+
unsupported.push({
|
|
188
|
+
id: branchCursor,
|
|
189
|
+
type: branchEl.type,
|
|
190
|
+
reason: "nested gateway inside a branch is not liftable yet",
|
|
191
|
+
});
|
|
192
|
+
branchVisited.add(branchCursor);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
branchVisited.add(branchCursor);
|
|
196
|
+
const step = extractStep(branchEl, unsupported, defs.errors);
|
|
197
|
+
if (step)
|
|
198
|
+
branchSteps.push(step);
|
|
199
|
+
branchCursor = branchOutgoing[0]?.targetRef;
|
|
200
|
+
}
|
|
201
|
+
for (const id of branchVisited)
|
|
202
|
+
visited.add(id);
|
|
203
|
+
branches.push({
|
|
204
|
+
condition: flow.conditionExpression?.text,
|
|
205
|
+
default: el.type === "exclusiveGateway" || el.type === "inclusiveGateway"
|
|
206
|
+
? flow.id === el.default
|
|
207
|
+
: undefined,
|
|
208
|
+
steps: branchSteps,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
steps.push({ id: el.id, name: el.name, kind: "gateway", gatewayType, branches });
|
|
212
|
+
// All branches reconverge (or dead-end) — continue from the common next element, if any.
|
|
213
|
+
const convergent = [...visited]
|
|
214
|
+
.flatMap((id) => bySource.get(id) ?? [])
|
|
215
|
+
.find((f) => !visited.has(f.targetRef));
|
|
216
|
+
currentId = convergent?.targetRef;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (outgoing.length > 1) {
|
|
220
|
+
unsupported.push({
|
|
221
|
+
id: el.id,
|
|
222
|
+
type: el.type,
|
|
223
|
+
reason: "multiple outgoing flows on a non-gateway element",
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const step = extractStep(el, unsupported, defs.errors);
|
|
228
|
+
if (step)
|
|
229
|
+
steps.push(step);
|
|
230
|
+
currentId = outgoing[0]?.targetRef;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
walkFrom(start.id);
|
|
234
|
+
for (const el of process.flowElements) {
|
|
235
|
+
if (!visited.has(el.id) && el.type !== "boundaryEvent") {
|
|
236
|
+
unsupported.push({
|
|
237
|
+
id: el.id,
|
|
238
|
+
type: el.type,
|
|
239
|
+
reason: "not reachable from the linear/branching walk",
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
plan: { version: 1, process: { id: process.id, name: process.name }, steps },
|
|
245
|
+
unsupported,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { compilePlan, type CompilePlanOptions, type ConnectorApplyResult, type ConnectorResolver, type PlanProblem, type SynthResult, } from "./compile.js";
|
|
2
|
+
export { extractPlan, type ExtractResult, type UnsupportedElement } from "./extract.js";
|
|
3
|
+
export { mergePlan } from "./merge.js";
|
|
4
|
+
export type { PlanAgentTool, PlanAiAgentStep, PlanBranch, PlanBusinessRuleTaskStep, PlanCallActivityStep, PlanConnectorRef, PlanConnectorStep, PlanEndStep, PlanErrorBoundary, PlanGatewayStep, PlanInputVariable, PlanRawStep, PlanReceiveTaskStep, PlanScenario, PlanScriptTaskStep, PlanSendTaskStep, PlanServiceTaskStep, PlanStartStep, PlanStep, PlanSubProcessStep, PlanTimerBoundary, PlanUserTaskStep, PlanWaitStep, ProcessPlan, } from "./types.js";
|
|
5
|
+
export { slugify, uniqueId } from "./slug.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BpmnDefinitions } from "../bpmn/bpmn-model.js";
|
|
2
|
+
import type { CompilePlanOptions, SynthResult } from "./compile.js";
|
|
3
|
+
import type { ProcessPlan } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Compiles `delta` standalone, then merges its elements/flows into
|
|
6
|
+
* `existing`'s matching process by id (matching ids are replaced, new ids
|
|
7
|
+
* are appended). `delta.steps[0]` must still be a `start` step — the merge
|
|
8
|
+
* only uses `delta`'s flow elements and sequence flows, and drops the
|
|
9
|
+
* delta's own start/end events when a same-id start/end already exists in
|
|
10
|
+
* `existing`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function mergePlan(existing: BpmnDefinitions, delta: ProcessPlan, opts?: CompilePlanOptions): SynthResult;
|
|
13
|
+
//# sourceMappingURL=merge.d.ts.map
|