@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,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,5 @@
1
+ export { compilePlan, } from "./compile.js";
2
+ export { extractPlan } from "./extract.js";
3
+ export { mergePlan } from "./merge.js";
4
+ export { slugify, uniqueId } from "./slug.js";
5
+ //# sourceMappingURL=index.js.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
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Applies a `ProcessPlan` delta onto an already-compiled `BpmnDefinitions`
3
+ * process — an id-based structural merge, not a byte-stable patch: elements
4
+ * whose id already exists are replaced, new elements are appended, and
5
+ * auto-layout re-runs over the whole diagram (so untouched elements keep
6
+ * their meaning but not necessarily their exact DI coordinates).
7
+ */
8
+ import { applyAutoLayout } from "../bpmn/auto-layout.js";
9
+ import { Bpmn } from "../bpmn/index.js";
10
+ import { optimize } from "../bpmn/optimize/index.js";
11
+ import { compilePlan } from "./compile.js";
12
+ /**
13
+ * Compiles `delta` standalone, then merges its elements/flows into
14
+ * `existing`'s matching process by id (matching ids are replaced, new ids
15
+ * are appended). `delta.steps[0]` must still be a `start` step — the merge
16
+ * only uses `delta`'s flow elements and sequence flows, and drops the
17
+ * delta's own start/end events when a same-id start/end already exists in
18
+ * `existing`.
19
+ */
20
+ export function mergePlan(existing, delta, opts = {}) {
21
+ const deltaResult = compilePlan(delta, { ...opts, skipAutoFix: true });
22
+ if (!deltaResult.defs)
23
+ return deltaResult;
24
+ const targetProcess = existing.processes.find((p) => p.id === delta.process.id) ?? existing.processes[0];
25
+ const deltaProcess = deltaResult.defs.processes[0];
26
+ if (!targetProcess || !deltaProcess) {
27
+ return { problems: [{ path: "process", message: "Could not resolve a process to merge into" }] };
28
+ }
29
+ const problems = [];
30
+ for (const el of deltaProcess.flowElements) {
31
+ const existingIndex = targetProcess.flowElements.findIndex((e) => e.id === el.id);
32
+ if (existingIndex >= 0)
33
+ targetProcess.flowElements[existingIndex] = el;
34
+ else
35
+ targetProcess.flowElements.push(el);
36
+ }
37
+ // Sequence-flow ids aren't stable across compiles, so merging by flow id
38
+ // would leave stale flows around any element the delta re-wires. Instead:
39
+ // drop every existing flow touching an element the delta redefines, then
40
+ // add the delta's flows fresh. Flows between two untouched elements are
41
+ // left alone.
42
+ const deltaElementIds = new Set(deltaProcess.flowElements.map((e) => e.id));
43
+ targetProcess.sequenceFlows = targetProcess.sequenceFlows.filter((f) => !deltaElementIds.has(f.sourceRef) && !deltaElementIds.has(f.targetRef));
44
+ targetProcess.sequenceFlows.push(...deltaProcess.sequenceFlows);
45
+ // Pull in any new root error/message/signal/escalation definitions the delta introduced.
46
+ for (const err of deltaResult.defs.errors) {
47
+ if (!existing.errors.some((e) => e.id === err.id))
48
+ existing.errors.push(err);
49
+ }
50
+ for (const msg of deltaResult.defs.messages) {
51
+ if (!existing.messages.some((m) => m.id === msg.id))
52
+ existing.messages.push(msg);
53
+ }
54
+ for (const sig of deltaResult.defs.signals) {
55
+ if (!existing.signals.some((s) => s.id === sig.id))
56
+ existing.signals.push(sig);
57
+ }
58
+ for (const esc of deltaResult.defs.escalations) {
59
+ if (!existing.escalations.some((e) => e.id === esc.id))
60
+ existing.escalations.push(esc);
61
+ }
62
+ let laidOut = applyAutoLayout(existing);
63
+ if (!opts.skipAutoFix) {
64
+ const report = optimize(laidOut);
65
+ for (const finding of report.findings)
66
+ finding.applyFix?.(laidOut);
67
+ laidOut = applyAutoLayout(laidOut);
68
+ }
69
+ const finalReport = optimize(laidOut);
70
+ for (const finding of finalReport.findings) {
71
+ if (finding.severity === "error") {
72
+ problems.push({
73
+ path: finding.elementIds.length > 0 ? `element:${finding.elementIds.join(",")}` : "process",
74
+ message: finding.message,
75
+ });
76
+ }
77
+ }
78
+ return { defs: laidOut, xml: Bpmn.export(laidOut), problems };
79
+ }
80
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1,5 @@
1
+ /** Derives a stable, readable element id from a step name, deduping against ids already used in this plan. */
2
+ export declare function slugify(text: string): string;
3
+ /** Returns a unique id: `base`, or `base_2`, `base_3`, … if `base` is already taken. */
4
+ export declare function uniqueId(base: string, taken: Set<string>): string;
5
+ //# sourceMappingURL=slug.d.ts.map
@@ -0,0 +1,22 @@
1
+ /** Derives a stable, readable element id from a step name, deduping against ids already used in this plan. */
2
+ export function slugify(text) {
3
+ const slug = text
4
+ .trim()
5
+ .replace(/[^a-zA-Z0-9]+/g, "_")
6
+ .replace(/^_+|_+$/g, "");
7
+ return slug.length > 0 ? slug : "element";
8
+ }
9
+ /** Returns a unique id: `base`, or `base_2`, `base_3`, … if `base` is already taken. */
10
+ export function uniqueId(base, taken) {
11
+ if (!taken.has(base)) {
12
+ taken.add(base);
13
+ return base;
14
+ }
15
+ let n = 2;
16
+ while (taken.has(`${base}_${n}`))
17
+ n++;
18
+ const id = `${base}_${n}`;
19
+ taken.add(id);
20
+ return id;
21
+ }
22
+ //# sourceMappingURL=slug.js.map
@@ -0,0 +1,225 @@
1
+ /**
2
+ * ProcessPlan — the typed intermediate representation Claude (or any LLM)
3
+ * authors instead of BPMN XML. `compilePlan()` turns a plan into valid,
4
+ * laid-out, executable BPMN via the `@bpmnkit/core` builder; the model never
5
+ * touches XML, DI, element IDs, or connector property keys directly.
6
+ *
7
+ * Every string field documented as FEEL follows this SDK's existing
8
+ * convention throughout: a leading "=" makes it a FEEL expression, its
9
+ * absence makes it a literal string. This matches how `zeebe:input`/`output`
10
+ * `source` values already work in `@bpmnkit/core`.
11
+ */
12
+ /** A connector reference — resolved against `@bpmnkit/connectors` (or an equivalent resolver) at compile time. */
13
+ export interface PlanConnectorRef {
14
+ /** Bundled template id, e.g. "io.camunda.connectors.Slack.v1". */
15
+ template: string;
16
+ /** Values keyed by the template's input keys — see `ConnectorSummary.requiredInputs`/`optionalInputs`. */
17
+ values?: Record<string, string>;
18
+ }
19
+ /** An error boundary attached to a step that can throw a BPMN error. */
20
+ export interface PlanErrorBoundary {
21
+ /**
22
+ * BPMN error code this boundary catches. Required: the underlying builder
23
+ * only emits an `errorEventDefinition` when a code is given, so an
24
+ * omitted `errorCode` produces an untyped (non-functional) boundary event.
25
+ */
26
+ errorCode: string;
27
+ /** Steps to run when the error is caught. */
28
+ steps: PlanStep[];
29
+ /** False = non-interrupting boundary event (default true). */
30
+ interrupting?: boolean;
31
+ }
32
+ /** A timer boundary attached to a step. */
33
+ export interface PlanTimerBoundary {
34
+ duration?: string;
35
+ date?: string;
36
+ cycle?: string;
37
+ steps: PlanStep[];
38
+ interrupting?: boolean;
39
+ }
40
+ interface PlanStepBase {
41
+ /** Stable element id. Auto-derived from `name` (slugified, deduped) if omitted. */
42
+ id?: string;
43
+ name?: string;
44
+ /** Shown as documentation on the element; becomes the AI Agent tool description for tools inside an `aiAgent` step. */
45
+ documentation?: string;
46
+ errorBoundary?: PlanErrorBoundary;
47
+ timerBoundary?: PlanTimerBoundary;
48
+ }
49
+ export interface PlanStartStep extends PlanStepBase {
50
+ kind: "start";
51
+ timer?: {
52
+ duration?: string;
53
+ date?: string;
54
+ cycle?: string;
55
+ };
56
+ message?: {
57
+ name: string;
58
+ };
59
+ /** Inbound connector template (e.g. a webhook start event). */
60
+ connector?: PlanConnectorRef;
61
+ }
62
+ export interface PlanConnectorStep extends PlanStepBase {
63
+ kind: "connector";
64
+ connector: PlanConnectorRef;
65
+ retries?: string;
66
+ }
67
+ export interface PlanServiceTaskStep extends PlanStepBase {
68
+ kind: "serviceTask";
69
+ jobType: string;
70
+ inputs?: Record<string, string>;
71
+ outputs?: Record<string, string>;
72
+ taskHeaders?: Record<string, string>;
73
+ retries?: string;
74
+ }
75
+ export interface PlanUserTaskStep extends PlanStepBase {
76
+ kind: "userTask";
77
+ formId?: string;
78
+ assignee?: string;
79
+ candidateGroups?: string;
80
+ candidateUsers?: string;
81
+ dueDate?: string;
82
+ followUpDate?: string;
83
+ priority?: number;
84
+ }
85
+ export interface PlanBusinessRuleTaskStep extends PlanStepBase {
86
+ kind: "businessRuleTask";
87
+ decisionId: string;
88
+ resultVariable?: string;
89
+ }
90
+ export interface PlanScriptTaskStep extends PlanStepBase {
91
+ kind: "scriptTask";
92
+ expression: string;
93
+ resultVariable: string;
94
+ }
95
+ export interface PlanSendTaskStep extends PlanStepBase {
96
+ kind: "sendTask";
97
+ messageName: string;
98
+ }
99
+ export interface PlanReceiveTaskStep extends PlanStepBase {
100
+ kind: "receiveTask";
101
+ messageName: string;
102
+ correlationKey?: string;
103
+ }
104
+ export interface PlanCallActivityStep extends PlanStepBase {
105
+ kind: "callActivity";
106
+ processId: string;
107
+ propagateAllChildVariables?: boolean;
108
+ }
109
+ /** One tool available to an `aiAgent` step. */
110
+ export interface PlanAgentTool {
111
+ id: string;
112
+ description: string;
113
+ /** A connector-backed tool. */
114
+ connector?: PlanConnectorRef;
115
+ /** A plain job-worker tool (mutually exclusive with `connector`). */
116
+ jobType?: string;
117
+ params?: Array<{
118
+ name: string;
119
+ description: string;
120
+ type?: "string" | "number" | "boolean" | "integer" | "array" | "object";
121
+ required?: boolean;
122
+ schema?: Record<string, unknown>;
123
+ /** Input-mapping target on the tool activity (default: same as `name`). */
124
+ target?: string;
125
+ }>;
126
+ resultExpression?: string;
127
+ }
128
+ export interface PlanAiAgentStep extends PlanStepBase {
129
+ kind: "aiAgent";
130
+ provider: string;
131
+ model: string;
132
+ /** Extra dotted zeebe:input bindings — auth, endpoint, region, etc. Keys match the bundled template's input keys. */
133
+ providerInputs?: Record<string, string>;
134
+ systemPrompt: string;
135
+ userPrompt: string;
136
+ memoryStorageType?: string;
137
+ maxModelCalls?: number;
138
+ outputVariable?: string;
139
+ tools: PlanAgentTool[];
140
+ completionCondition?: string;
141
+ cancelRemainingInstances?: boolean;
142
+ retries?: string;
143
+ }
144
+ export interface PlanBranch {
145
+ name?: string;
146
+ /** FEEL condition (required unless `default` is set). */
147
+ condition?: string;
148
+ default?: boolean;
149
+ steps: PlanStep[];
150
+ }
151
+ export interface PlanGatewayStep extends PlanStepBase {
152
+ kind: "gateway";
153
+ gatewayType: "exclusive" | "parallel" | "inclusive" | "eventBased";
154
+ branches: PlanBranch[];
155
+ }
156
+ export interface PlanSubProcessStep extends PlanStepBase {
157
+ kind: "subProcess";
158
+ steps: PlanStep[];
159
+ multiInstance?: {
160
+ isSequential?: boolean;
161
+ collection: string;
162
+ elementVariable?: string;
163
+ completionCondition?: string;
164
+ };
165
+ }
166
+ export interface PlanWaitStep extends PlanStepBase {
167
+ kind: "wait";
168
+ timer?: {
169
+ duration?: string;
170
+ date?: string;
171
+ cycle?: string;
172
+ };
173
+ message?: {
174
+ name: string;
175
+ correlationKey: string;
176
+ };
177
+ }
178
+ export interface PlanEndStep extends PlanStepBase {
179
+ kind: "end";
180
+ errorCode?: string;
181
+ terminate?: boolean;
182
+ }
183
+ /** Escape hatch for anything the plan format can't express yet — a raw builder-options object, applied as-is. */
184
+ export interface PlanRawStep extends PlanStepBase {
185
+ kind: "raw";
186
+ elementType: string;
187
+ options?: Record<string, unknown>;
188
+ }
189
+ export type PlanStep = PlanStartStep | PlanConnectorStep | PlanServiceTaskStep | PlanUserTaskStep | PlanBusinessRuleTaskStep | PlanScriptTaskStep | PlanSendTaskStep | PlanReceiveTaskStep | PlanCallActivityStep | PlanAiAgentStep | PlanGatewayStep | PlanSubProcessStep | PlanWaitStep | PlanEndStep | PlanRawStep;
190
+ export interface PlanInputVariable {
191
+ name: string;
192
+ type: string;
193
+ required?: boolean;
194
+ description?: string;
195
+ }
196
+ export interface PlanScenario {
197
+ name: string;
198
+ inputs?: Record<string, unknown>;
199
+ /** jobType → { outputs } or { error: { code, message? } } */
200
+ mocks?: Record<string, {
201
+ outputs?: Record<string, unknown>;
202
+ } | {
203
+ error: {
204
+ code: string;
205
+ message?: string;
206
+ };
207
+ }>;
208
+ expect?: {
209
+ path?: string[];
210
+ variables?: Record<string, unknown>;
211
+ };
212
+ }
213
+ export interface ProcessPlan {
214
+ version: 1;
215
+ process: {
216
+ id: string;
217
+ name?: string;
218
+ versionTag?: string;
219
+ };
220
+ inputs?: PlanInputVariable[];
221
+ steps: PlanStep[];
222
+ tests?: PlanScenario[];
223
+ }
224
+ export {};
225
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ProcessPlan — the typed intermediate representation Claude (or any LLM)
3
+ * authors instead of BPMN XML. `compilePlan()` turns a plan into valid,
4
+ * laid-out, executable BPMN via the `@bpmnkit/core` builder; the model never
5
+ * touches XML, DI, element IDs, or connector property keys directly.
6
+ *
7
+ * Every string field documented as FEEL follows this SDK's existing
8
+ * convention throughout: a leading "=" makes it a FEEL expression, its
9
+ * absence makes it a literal string. This matches how `zeebe:input`/`output`
10
+ * `source` values already work in `@bpmnkit/core`.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=types.js.map
@@ -1,12 +1,20 @@
1
1
  const ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
2
2
  const ID_SIZE = 8;
3
+ // Random bytes are drawn in batches: one getRandomValues call per id is
4
+ // dominated by the crypto call overhead, not by the bytes it returns.
5
+ const POOL_SIZE = 1024;
6
+ const pool = new Uint8Array(POOL_SIZE);
7
+ let poolOffset = POOL_SIZE;
3
8
  function nanoId() {
4
- const bytes = new Uint8Array(ID_SIZE);
5
- crypto.getRandomValues(bytes);
9
+ if (poolOffset + ID_SIZE > POOL_SIZE) {
10
+ crypto.getRandomValues(pool);
11
+ poolOffset = 0;
12
+ }
6
13
  let id = "";
7
14
  for (let i = 0; i < ID_SIZE; i++) {
8
- id += ALPHABET[bytes[i] % ALPHABET.length];
15
+ id += ALPHABET[pool[poolOffset + i] % ALPHABET.length];
9
16
  }
17
+ poolOffset += ID_SIZE;
10
18
  return id;
11
19
  }
12
20
  // Counter used only in deterministic test mode (activated by resetIdCounter())
@@ -1,2 +1,4 @@
1
- export { parseXml, serializeXml } from "./xml-parser.js";
1
+ export { parseXml, scanXml, serializeXml } from "./xml-parser.js";
2
+ export { Visit } from "./xml-parser.js";
3
+ export type { XmlSink } from "./xml-parser.js";
2
4
  //# sourceMappingURL=index.d.ts.map
package/dist/xml/index.js CHANGED
@@ -1,2 +1,3 @@
1
- export { parseXml, serializeXml } from "./xml-parser.js";
1
+ export { parseXml, scanXml, serializeXml } from "./xml-parser.js";
2
+ export { Visit } from "./xml-parser.js";
2
3
  //# sourceMappingURL=index.js.map