@bpmnkit/core 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +32 -1
  2. package/dist/bpmn/agentic.d.ts +121 -0
  3. package/dist/bpmn/agentic.js +97 -0
  4. package/dist/bpmn/auto-layout.d.ts +5 -5
  5. package/dist/bpmn/auto-layout.js +592 -36
  6. package/dist/bpmn/bpmn-builder.d.ts +265 -3
  7. package/dist/bpmn/bpmn-builder.js +603 -197
  8. package/dist/bpmn/bpmn-model.d.ts +114 -0
  9. package/dist/bpmn/bpmn-parser.js +1414 -521
  10. package/dist/bpmn/bpmn-serializer.js +107 -19
  11. package/dist/bpmn/compact.d.ts +17 -2
  12. package/dist/bpmn/compact.js +3 -3
  13. package/dist/bpmn/full-operations.d.ts +89 -0
  14. package/dist/bpmn/full-operations.js +478 -0
  15. package/dist/bpmn/index.d.ts +19 -0
  16. package/dist/bpmn/index.js +21 -0
  17. package/dist/bpmn/optimize/agentic.d.ts +10 -0
  18. package/dist/bpmn/optimize/agentic.js +88 -0
  19. package/dist/bpmn/optimize/deploy.d.ts +16 -0
  20. package/dist/bpmn/optimize/deploy.js +143 -0
  21. package/dist/bpmn/optimize/feel-syntax.d.ts +12 -0
  22. package/dist/bpmn/optimize/feel-syntax.js +87 -0
  23. package/dist/bpmn/optimize/feel.js +7 -4
  24. package/dist/bpmn/optimize/flow.js +22 -2
  25. package/dist/bpmn/optimize/index.js +20 -9
  26. package/dist/bpmn/optimize/patterns.js +23 -16
  27. package/dist/bpmn/optimize/tasks.js +30 -7
  28. package/dist/bpmn/optimize/types.d.ts +10 -1
  29. package/dist/bpmn/optimize/utils.js +2 -4
  30. package/dist/bpmn/optimize/variable-flow.js +58 -67
  31. package/dist/bpmn/semantic-hash.d.ts +93 -0
  32. package/dist/bpmn/semantic-hash.js +155 -0
  33. package/dist/bpmn/sha256.d.ts +17 -0
  34. package/dist/bpmn/sha256.js +95 -0
  35. package/dist/bpmn/zeebe-extensions.d.ts +83 -0
  36. package/dist/bpmn/zeebe-extensions.js +117 -0
  37. package/dist/bpmn/zeebe-placement.d.ts +12 -0
  38. package/dist/bpmn/zeebe-placement.js +140 -0
  39. package/dist/errors.d.ts +40 -1
  40. package/dist/errors.js +41 -0
  41. package/dist/index.d.ts +16 -5
  42. package/dist/index.js +9 -3
  43. package/dist/layout/annotations.js +36 -1
  44. package/dist/layout/collaboration/alignment.d.ts +26 -0
  45. package/dist/layout/collaboration/alignment.js +66 -0
  46. package/dist/layout/collaboration/ordering.d.ts +21 -0
  47. package/dist/layout/collaboration/ordering.js +102 -0
  48. package/dist/layout/index.d.ts +1 -0
  49. package/dist/layout/layout-engine.d.ts +13 -3
  50. package/dist/layout/layout-engine.js +9 -4
  51. package/dist/layout/semantic/bands.d.ts +19 -0
  52. package/dist/layout/semantic/bands.js +324 -0
  53. package/dist/layout/semantic/graph.d.ts +37 -0
  54. package/dist/layout/semantic/graph.js +242 -0
  55. package/dist/layout/semantic/index.d.ts +13 -0
  56. package/dist/layout/semantic/index.js +181 -0
  57. package/dist/layout/semantic/place.d.ts +40 -0
  58. package/dist/layout/semantic/place.js +271 -0
  59. package/dist/layout/semantic/route.d.ts +14 -0
  60. package/dist/layout/semantic/route.js +514 -0
  61. package/dist/layout/types.d.ts +17 -0
  62. package/dist/node/index.d.ts +10 -0
  63. package/dist/node/index.js +9 -0
  64. package/dist/node/write.d.ts +81 -0
  65. package/dist/node/write.js +167 -0
  66. package/dist/plan/compile.d.ts +39 -0
  67. package/dist/plan/compile.js +380 -0
  68. package/dist/plan/extract.d.ts +31 -0
  69. package/dist/plan/extract.js +248 -0
  70. package/dist/plan/index.d.ts +6 -0
  71. package/dist/plan/index.js +5 -0
  72. package/dist/plan/merge.d.ts +13 -0
  73. package/dist/plan/merge.js +80 -0
  74. package/dist/plan/slug.d.ts +5 -0
  75. package/dist/plan/slug.js +22 -0
  76. package/dist/plan/types.d.ts +225 -0
  77. package/dist/plan/types.js +13 -0
  78. package/dist/types/id-generator.js +11 -3
  79. package/dist/xml/index.d.ts +3 -1
  80. package/dist/xml/index.js +2 -1
  81. package/dist/xml/xml-parser.d.ts +32 -0
  82. package/dist/xml/xml-parser.js +394 -143
  83. package/package.json +9 -2
@@ -0,0 +1,167 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { chmod, link, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ import { applyAutoLayout } from "../bpmn/auto-layout.js";
5
+ import { parseBpmn } from "../bpmn/bpmn-parser.js";
6
+ import { serializeBpmn } from "../bpmn/bpmn-serializer.js";
7
+ import { diffSemantics, semanticHash } from "../bpmn/semantic-hash.js";
8
+ import { sha256Hex } from "../bpmn/sha256.js";
9
+ import { WriteError, WriteVerificationError } from "../errors.js";
10
+ async function pathExists(path) {
11
+ try {
12
+ await stat(path);
13
+ return true;
14
+ }
15
+ catch (error) {
16
+ if (isErrnoCode(error, "ENOENT"))
17
+ return false;
18
+ throw error;
19
+ }
20
+ }
21
+ function isErrnoCode(error, code) {
22
+ return error instanceof Error && "code" in error && error.code === code;
23
+ }
24
+ /** Codes returned by filesystems that cannot make a hard link. */
25
+ const NO_HARD_LINKS = new Set(["ENOTSUP", "EOPNOTSUPP", "EPERM", "EXDEV", "EMLINK"]);
26
+ /**
27
+ * Serialises a model, verifies it survives a round trip, and writes it
28
+ * atomically.
29
+ *
30
+ * The file appears complete or not at all: the contents go to a temporary file
31
+ * in the destination's own directory and are then renamed into place, so an
32
+ * interrupted write cannot leave a half-written model behind. Without `force`
33
+ * the final step is a hard link, which fails if the destination appeared in the
34
+ * meantime rather than silently replacing it.
35
+ *
36
+ * @param definitions - The model to write.
37
+ * @param options - Destination and write behaviour.
38
+ * @returns Where it went, what it hashes to, and what it changed.
39
+ * @throws {WriteVerificationError} If reading the output back does not
40
+ * reproduce the model. Nothing is written.
41
+ * @throws {WriteError} If the destination exists and `force` was not given, or
42
+ * the filesystem refused the write.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * import { writeBpmn } from "@bpmnkit/core/node"
47
+ *
48
+ * const result = await writeBpmn(definitions, { output: "flow.bpmn" })
49
+ * console.log(result.semanticHash, result.changes?.changed.length ?? 0)
50
+ * ```
51
+ */
52
+ export async function writeBpmn(definitions, options) {
53
+ const destination = resolve(options.output);
54
+ const force = options.force === true;
55
+ const exists = await pathExists(destination);
56
+ // Fail before doing the work, not after it.
57
+ if (exists && !force) {
58
+ throw new WriteError(`Refusing to overwrite ${options.output}. Pass force: true to replace it.`);
59
+ }
60
+ // applyAutoLayout returns a new model, so the caller's stays untouched.
61
+ const model = options.layout === "auto" ? applyAutoLayout(definitions) : definitions;
62
+ const expected = semanticHash(model);
63
+ const xml = serializeBpmn(model);
64
+ let reparsed;
65
+ try {
66
+ reparsed = parseBpmn(xml);
67
+ }
68
+ catch (error) {
69
+ const reason = error instanceof Error ? error.message : String(error);
70
+ throw new WriteVerificationError(`Serialising the model produced BPMN that cannot be parsed back: ${reason}`, { added: [], removed: [], changed: [] });
71
+ }
72
+ const actual = semanticHash(reparsed);
73
+ if (actual !== expected) {
74
+ const changes = diffSemantics(model, reparsed);
75
+ throw new WriteVerificationError([
76
+ "Serialising the model did not reproduce it, so nothing was written.",
77
+ `Lost: ${changes.removed.join(", ") || "none"}.`,
78
+ `Added: ${changes.added.join(", ") || "none"}.`,
79
+ `Altered: ${changes.changed.map((entry) => entry.id).join(", ") || "none"}.`,
80
+ ].join(" "), changes);
81
+ }
82
+ const changes = exists ? await changesAgainst(destination, reparsed) : undefined;
83
+ await writeAtomically(destination, xml, force);
84
+ return {
85
+ destination,
86
+ bytes: Buffer.byteLength(xml, "utf-8"),
87
+ outputSha256: sha256Hex(xml),
88
+ semanticHash: actual,
89
+ changes,
90
+ };
91
+ }
92
+ /**
93
+ * Diffs the model about to be written against the one already on disk. A
94
+ * previous file that cannot be read or parsed yields no report rather than
95
+ * failing the write — the old contents are being replaced either way.
96
+ */
97
+ async function changesAgainst(destination, next) {
98
+ try {
99
+ return diffSemantics(parseBpmn(await readFile(destination, "utf-8")), next);
100
+ }
101
+ catch {
102
+ return undefined;
103
+ }
104
+ }
105
+ async function writeAtomically(destination, contents, force) {
106
+ const temporary = resolve(dirname(destination), `.${basename(destination)}.${randomUUID()}.tmp`);
107
+ try {
108
+ await writeFile(temporary, contents, { encoding: "utf-8", flag: "wx" });
109
+ if (force) {
110
+ // Keep the permissions the file already had; a rename would otherwise
111
+ // hand it whatever the temporary file was created with.
112
+ const mode = await modeOf(destination);
113
+ if (mode !== undefined)
114
+ await chmod(temporary, mode);
115
+ await rename(temporary, destination);
116
+ return;
117
+ }
118
+ await linkOrCreateExclusively(temporary, destination, contents);
119
+ }
120
+ catch (error) {
121
+ if (error instanceof WriteError)
122
+ throw error;
123
+ if (isErrnoCode(error, "EEXIST")) {
124
+ throw new WriteError(`Refusing to overwrite ${destination}: it appeared while writing. Pass force: true to replace it.`);
125
+ }
126
+ const reason = error instanceof Error ? error.message : String(error);
127
+ throw new WriteError(`Unable to write ${destination}: ${reason}`);
128
+ }
129
+ finally {
130
+ await rm(temporary, { force: true });
131
+ }
132
+ }
133
+ /**
134
+ * Creates the destination without replacing anything. `link` fails with EEXIST
135
+ * if the destination is taken, which `rename` would not, and it publishes the
136
+ * already-complete temporary file in one step.
137
+ *
138
+ * Filesystems without hard links fall back to an exclusive create, which is
139
+ * still safe against replacing an existing file but writes in place rather than
140
+ * atomically — an interrupted write there can leave a partial file.
141
+ */
142
+ async function linkOrCreateExclusively(temporary, destination, contents) {
143
+ try {
144
+ await link(temporary, destination);
145
+ }
146
+ catch (error) {
147
+ if (!isErrnoCode(error, "EEXIST") && isNoHardLinkSupport(error)) {
148
+ await writeFile(destination, contents, { encoding: "utf-8", flag: "wx" });
149
+ return;
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+ function isNoHardLinkSupport(error) {
155
+ return (error instanceof Error &&
156
+ "code" in error &&
157
+ NO_HARD_LINKS.has(error.code ?? ""));
158
+ }
159
+ async function modeOf(path) {
160
+ try {
161
+ return (await stat(path)).mode;
162
+ }
163
+ catch {
164
+ return undefined;
165
+ }
166
+ }
167
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1,39 @@
1
+ import type { AdHocSubProcessOptions, BoundaryEventOptions, IntermediateCatchEventOptions, ServiceTaskOptions, StartEventOptions } from "../bpmn/bpmn-builder.js";
2
+ import type { BpmnDefinitions } from "../bpmn/bpmn-model.js";
3
+ import type { ProcessPlan } from "./types.js";
4
+ export interface PlanProblem {
5
+ /** A path into the plan, e.g. "steps[2].branches[0].condition", for pinpointing the offending field. */
6
+ path: string;
7
+ message: string;
8
+ }
9
+ export interface SynthResult {
10
+ defs?: BpmnDefinitions;
11
+ xml?: string;
12
+ problems: PlanProblem[];
13
+ }
14
+ /** What a connector template resolves to — structurally matches `@bpmnkit/connectors`' `ApplyResult`. */
15
+ export interface ConnectorApplyResult {
16
+ serviceTask?: ServiceTaskOptions;
17
+ adHocSubProcess?: Partial<AdHocSubProcessOptions>;
18
+ startEvent?: Partial<StartEventOptions>;
19
+ boundaryEvent?: Partial<BoundaryEventOptions>;
20
+ intermediateEvent?: Partial<IntermediateCatchEventOptions>;
21
+ problems: Array<{
22
+ key?: string;
23
+ message: string;
24
+ }>;
25
+ }
26
+ export type ConnectorResolver = (templateId: string, values: Record<string, string>) => ConnectorApplyResult;
27
+ export interface CompilePlanOptions {
28
+ /** Resolves `PlanConnectorRef`s — pass `applyConnectorTemplate` from `@bpmnkit/connectors`. Without it, connector steps produce a problem. */
29
+ resolveConnector?: ConnectorResolver;
30
+ /** Skip auto-applying safe optimizer fixes (default false). */
31
+ skipAutoFix?: boolean;
32
+ }
33
+ /**
34
+ * Compiles a `ProcessPlan` into laid-out, validated BPMN XML. Deterministic:
35
+ * the same plan always produces the same XML. Problems are collected, not
36
+ * thrown — check `result.problems` before using `result.xml`.
37
+ */
38
+ export declare function compilePlan(plan: ProcessPlan, opts?: CompilePlanOptions): SynthResult;
39
+ //# sourceMappingURL=compile.d.ts.map
@@ -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