@uipath/maestro-builder-sdk 6.65.0 → 6.66.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.
@@ -1,7 +1,8 @@
1
1
  import { createTypedNode, typedNodeOutputVar } from './typed-node.js';
2
2
  import { connectorBindingRef } from './connector.js';
3
- import { eventFilterJmes, eventFilterProblem, jmesString, jmesStringProblem, readEventFilter } from '../core/event-filters.js';
3
+ import { eventFilterJmes, jmesString, jmesStringProblem, readEventFilterLeaf } from '../core/event-filters.js';
4
4
  import { resolveEventWhere } from '../core/lookups.js';
5
+ import { CompileRefusal } from '../core/compile-refusal.js';
5
6
  /**
6
7
  * Which extension type an external invocation lowers to.
7
8
  *
@@ -57,14 +58,14 @@ export function intsvcOutputVar(n) {
57
58
  */
58
59
  export function createConnectorEvent(ctx, n) {
59
60
  if (!ctx.library)
60
- throw new Error(`connector event "${n.id}" needs the connector library — pass { library } to serialize ` +
61
+ throw new CompileRefusal('LIBRARY_REQUIRED', `connector event "${n.id}" needs the connector library — pass { library } to serialize ` +
61
62
  `or use uip maestro bpmn compile --library.`);
62
63
  const rc = ctx.library.resolveEvent(n.subscribe, n.key, n.eventName, n.version, n.object);
63
64
  const op = (rc.entry.operation ?? {});
64
65
  const runtime = rc.entry.runtime ?? {};
65
66
  const type = runtime.serviceType;
66
67
  if (!type) {
67
- throw new Error(`connector event ${n.key}/${n.eventName} has no runtime.serviceType in the library; ` +
68
+ throw new CompileRefusal('BPMN_EVENT_NO_SERVICE_TYPE', `connector event ${n.key}/${n.eventName} has no runtime.serviceType in the library; ` +
68
69
  `it cannot be authored as a BPMN node.`);
69
70
  }
70
71
  // The PLACEMENT decides the extension type — `.eventTrigger()` starts the
@@ -79,13 +80,13 @@ export function createConnectorEvent(ctx, n) {
79
80
  // .eventTrigger() instead", while the unprepared same node compiled.
80
81
  const expected = intsvcServiceType(n);
81
82
  if (type !== 'Intsvc.EventTrigger' && type !== 'Intsvc.WaitForEvent') {
82
- throw new Error(`connector event "${n.id}" was authored as a ${n.subscribe} (${expected}), but the library ` +
83
+ throw new CompileRefusal('BPMN_EVENT_KIND_MISMATCH', `connector event "${n.id}" was authored as a ${n.subscribe} (${expected}), but the library ` +
83
84
  `describes ${n.key}/${n.eventName} as ${type}, which is not a connector event.`);
84
85
  }
85
86
  const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
86
87
  const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
87
88
  if (!conn || !folder) {
88
- throw new Error(`connector event "${n.id}" needs both a connection and a folder binding name — ` +
89
+ throw new CompileRefusal('BPMN_EVENT_BINDINGS_MISSING', `connector event "${n.id}" needs both a connection and a folder binding name — ` +
89
90
  `the type declares connectionId, folderId, folderPath and folderKey all required.`);
90
91
  }
91
92
  // The registry marks `operation` and `objectName` required AND hidden, so an
@@ -144,20 +145,20 @@ export function eventFilterBody(nodeId, scope, filters) {
144
145
  const scopeClauses = scopeEntries.map(([k, v]) => {
145
146
  const problem = jmesStringProblem(String(v));
146
147
  if (problem)
147
- throw new Error(`connector event "${nodeId}": \`filter.${k}\`: ${problem}`);
148
+ throw new CompileRefusal('EVENT_FILTER_BACKSLASH', `connector event "${nodeId}": \`filter.${k}\`: ${problem}`);
148
149
  return `(${k} == ${jmesString(String(v))})`;
149
150
  });
150
151
  const conditions = (filters ?? []).map((leaf, i) => {
151
- const { read } = readEventFilter(leaf);
152
- if (!read) {
153
- throw new Error(`connector event "${nodeId}": filters[${i}] must carry a field and exactly ONE operator ` +
154
- `(contains, startsWith, endsWith, equals, notEquals, lessThan, lessThanOrEqual, greaterThan, ` +
155
- `greaterThanOrEqual) — got ${JSON.stringify(leaf)}.`);
152
+ // The code `check` gives the same leaf (`core/event-checks.ts`).
153
+ const judged = readEventFilterLeaf(leaf);
154
+ if (judged.code !== undefined) {
155
+ throw new CompileRefusal(judged.code, judged.problem !== undefined
156
+ ? `connector event "${nodeId}": filters[${i}] on \`${judged.read?.field}\`: ${judged.problem}`
157
+ : `connector event "${nodeId}": filters[${i}] must carry a field and exactly ONE operator ` +
158
+ `(contains, startsWith, endsWith, equals, notEquals, lessThan, lessThanOrEqual, greaterThan, ` +
159
+ `greaterThanOrEqual) — got ${JSON.stringify(leaf)}.`);
156
160
  }
157
- const problem = eventFilterProblem(read);
158
- if (problem)
159
- throw new Error(`connector event "${nodeId}": filters[${i}] on \`${read.field}\`: ${problem}`);
160
- return `(${eventFilterJmes(read)})`;
161
+ return `(${eventFilterJmes(judged.read)})`;
161
162
  });
162
163
  const expression = [...scopeClauses, ...conditions].join(' && ');
163
164
  const body = {};
@@ -176,7 +177,7 @@ export function eventFilterBody(nodeId, scope, filters) {
176
177
  */
177
178
  export function createExternalTask(ctx, n) {
178
179
  if (!ctx.library)
179
- throw new Error(`external ${n.family} "${n.id}" needs the connector library — pass { library } to serialize ` +
180
+ throw new CompileRefusal('LIBRARY_REQUIRED', `external ${n.family} "${n.id}" needs the connector library — pass { library } to serialize ` +
180
181
  `or use uip maestro bpmn compile --library.`);
181
182
  const rc = ctx.library.resolve(n.key, n.action, n.version);
182
183
  const op = (rc.entry.operation ?? {});
@@ -186,14 +187,14 @@ export function createExternalTask(ctx, n) {
186
187
  // validator accepts and the runtime dispatches to the wrong handler family —
187
188
  // the Case SDK records the same trap in its Phase 4.4 notes.
188
189
  if (op.subType !== wanted) {
189
- throw new Error(`external ${n.family} "${n.id}" needs ${wanted === 'AgentExecution' ? 'an' : 'a'} ${wanted} descriptor, but ` +
190
+ throw new CompileRefusal('EXTERNAL_TASK_DESCRIPTOR_CONTRACT', `external ${n.family} "${n.id}" needs ${wanted === 'AgentExecution' ? 'an' : 'a'} ${wanted} descriptor, but ` +
190
191
  `${n.key}/${n.action} is "${String(op.subType ?? 'untyped')}". ` +
191
192
  `The shipped library has very few of these — ${wanted === 'AgentExecution' ? '2' : '1'} operation(s).`);
192
193
  }
193
194
  const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
194
195
  const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
195
196
  if (!conn || !folder) {
196
- throw new Error(`external ${n.family} "${n.id}" needs both a connection and a folder binding name.`);
197
+ throw new CompileRefusal(conn ? 'EXTERNAL_TASK_FOLDER' : 'EXTERNAL_TASK_CONNECTION', `external ${n.family} "${n.id}" needs both a connection and a folder binding name.`);
197
198
  }
198
199
  return createTypedNode(ctx.m, n.id, EXTERNAL_SERVICE_TYPE[n.family][n.mode], {
199
200
  name: n.name ?? rc.entry.display?.label ?? n.action,
package/dist/bpmn/load.js CHANGED
@@ -3,22 +3,23 @@ import { pathToFileURL } from 'node:url';
3
3
  import { resolve } from 'node:path';
4
4
  import { existsSync } from 'node:fs';
5
5
  import { useThisSdkForAuthoredSource } from '../sdk-resolve.js';
6
+ import { CompileRefusal } from '../core/compile-refusal.js';
6
7
  /** Resolve a user argument (a `.ts` path or a BaseName) to a bpmn source file. */
7
8
  export function resolveBpmnFile(arg) {
8
9
  const candidates = [arg, `${arg}.bpmn.ts`, `${arg}.ts`];
9
10
  for (const c of candidates)
10
11
  if (existsSync(resolve(c)))
11
12
  return resolve(c);
12
- throw new Error(`bpmn source not found: tried ${candidates.join(', ')}`);
13
+ throw new CompileRefusal('BPMN_SOURCE_NOT_FOUND', `bpmn source not found: tried ${candidates.join(', ')}`);
13
14
  }
14
15
  export async function loadBuiltBpmn(file) {
15
16
  useThisSdkForAuthoredSource();
16
17
  const mod = await import(pathToFileURL(file).href);
17
18
  const exp = mod.default ?? mod.bpmn;
18
19
  if (!exp)
19
- throw new Error(`${file} has no default export. Add: export default bpmn('id')...build();`);
20
+ throw new CompileRefusal('BPMN_NO_DEFAULT_EXPORT', `${file} has no default export. Add: export default bpmn('id')...build();`);
20
21
  const built = typeof exp.build === 'function' ? exp.build() : exp;
21
22
  if (!built || !Array.isArray(built.nodes))
22
- throw new Error(`${file} default export is not a built bpmn. Did you call .build()?`);
23
+ throw new CompileRefusal('BPMN_EXPORT_NOT_BUILT', `${file} default export is not a built bpmn. Did you call .build()?`);
23
24
  return built;
24
25
  }
@@ -22,6 +22,7 @@ import { connectorOutputVars, typedNodeDeclaredVars } from './declared-vars.js';
22
22
  import { defaultEntryPointId } from './entry-point-id.js';
23
23
  import { parseXmlRanges } from './xml-ranges.js';
24
24
  import { ownEntry } from '../core/own-entry.js';
25
+ import { CompileRefusal } from '../core/compile-refusal.js';
25
26
  const require = createRequire(import.meta.url);
26
27
  const uipathDescriptor = require('./uipath-moddle.v1.json');
27
28
  const NS = {
@@ -251,7 +252,16 @@ function jsonInputBodiesAsCdata(xml) {
251
252
  collect(node.children);
252
253
  }
253
254
  };
254
- collect(parseXmlRanges(xml));
255
+ let ranges;
256
+ try {
257
+ ranges = parseXmlRanges(xml);
258
+ }
259
+ catch (e) {
260
+ // `xml` is moddle's own output, so malformed XML here is the serializer's
261
+ // defect, not the author's. BPMN_XML_MALFORMED stays for what `bpmn merge` reads.
262
+ throw new Error(`internal SDK error: the serializer wrote malformed XML: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
263
+ }
264
+ collect(ranges);
255
265
  let out = xml;
256
266
  // Splice from the end so the earlier offsets stay valid.
257
267
  for (const input of bodies.sort((a, b) => b.innerStart - a.innerStart)) {
@@ -289,12 +299,12 @@ function cdata(name, text) {
289
299
  value = JSON.parse(text);
290
300
  }
291
301
  catch {
292
- throw new Error(`json input "${name}" contains "]]>" and is not valid JSON, so it cannot be written as the single CDATA section the engine reads.`);
302
+ throw new CompileRefusal('BPMN_JSON_INPUT_CDATA_END', `json input "${name}" contains "]]>" and is not valid JSON, so it cannot be written as the single CDATA section the engine reads.`);
293
303
  }
294
304
  const escaped = text.replaceAll(']]>', ']]\\u003e');
295
305
  // Holds by the argument above; checked because a wrong payload here would be silent.
296
306
  if (JSON.stringify(JSON.parse(escaped)) !== JSON.stringify(value)) {
297
- throw new Error(`json input "${name}": escaping "]]>" changed its value.`);
307
+ throw new Error(`internal SDK error: json input "${name}": escaping "]]>" changed its value.`);
298
308
  }
299
309
  return `<![CDATA[${escaped}]]>`;
300
310
  }
@@ -356,30 +366,35 @@ function stampEntryPointIds(ctx, built, flowElements, rootDefault) {
356
366
  /** Build the flowElements array for one scope, wiring all references within it. */
357
367
  function buildScope(ctx, nodes, flows) {
358
368
  const { m } = ctx;
359
- const byId = new Map();
369
+ const nodeById = new Map();
370
+ const flowById = new Map();
360
371
  const nodeEls = nodes.map((n) => {
361
372
  const el = createNode(ctx, n);
362
- byId.set(n.id, el);
373
+ nodeById.set(n.id, el);
363
374
  return el;
364
375
  });
365
376
  const flowEls = flows.map((f) => {
366
377
  const el = m.create('bpmn:SequenceFlow', { id: f.id, name: f.name });
367
378
  if (f.condition !== undefined)
368
379
  el.conditionExpression = m.create('bpmn:FormalExpression', { body: f.condition });
369
- byId.set(f.id, el);
380
+ flowById.set(f.id, el);
370
381
  return el;
371
382
  });
372
- const ref = (id, where) => {
383
+ // A flow endpoint and a boundary attachment name a node in this scope, as
384
+ // `check` resolves them (DANGLING_FLOW, BOUNDARY_UNATTACHED); a sequence flow
385
+ // is never one. A gateway default names a flow, or any element as before.
386
+ const ref = (byId, id, where, code) => {
373
387
  const el = byId.get(id);
374
388
  if (!el)
375
- throw new Error(`${where} references unknown element id "${id}".`);
389
+ throw new CompileRefusal(code, `${where} references unknown element id "${id}".`);
376
390
  return el;
377
391
  };
392
+ const anyById = new Map([...nodeById, ...flowById]);
378
393
  // Wire sequence flows to source/target and populate incoming/outgoing.
379
394
  flows.forEach((f, i) => {
380
395
  const flowEl = flowEls[i];
381
- const src = ref(f.source, `sequenceFlow "${f.id}"`);
382
- const tgt = ref(f.target, `sequenceFlow "${f.id}"`);
396
+ const src = ref(nodeById, f.source, `sequenceFlow "${f.id}"`, 'DANGLING_FLOW');
397
+ const tgt = ref(nodeById, f.target, `sequenceFlow "${f.id}"`, 'DANGLING_FLOW');
383
398
  flowEl.sourceRef = src;
384
399
  flowEl.targetRef = tgt;
385
400
  (src.outgoing ??= []).push(flowEl);
@@ -389,9 +404,9 @@ function buildScope(ctx, nodes, flows) {
389
404
  nodes.forEach((n, i) => {
390
405
  const el = nodeEls[i];
391
406
  if (n.kind === 'boundaryEvent')
392
- el.attachedToRef = ref(n.attachedTo, `boundaryEvent "${n.id}"`);
407
+ el.attachedToRef = ref(nodeById, n.attachedTo, `boundaryEvent "${n.id}"`, 'BOUNDARY_UNATTACHED');
393
408
  if ((n.kind === 'exclusiveGateway' || n.kind === 'inclusiveGateway') && n.default !== undefined)
394
- el.default = ref(n.default, `gateway "${n.id}" default`);
409
+ el.default = ref(anyById, n.default, `gateway "${n.id}" default`, 'BPMN_GATEWAY_DEFAULT_UNKNOWN');
395
410
  });
396
411
  return [...nodeEls, ...flowEls];
397
412
  }
@@ -729,8 +744,9 @@ function eventDefinition(ctx, def) {
729
744
  */
730
745
  function moddleTaskType(element) {
731
746
  const [ns, local] = element.split(':');
747
+ // `.plainTask()` refuses any element outside `PlainTaskElement` (BPMN_PLAIN_TASK_ELEMENT_INVALID).
732
748
  if (!ns || !local)
733
- throw new Error(`plainTask: "${element}" is not a qualified element name`);
749
+ throw new Error(`internal SDK error: plainTask: "${element}" is not a qualified element name`);
734
750
  return `${ns}:${local.charAt(0).toUpperCase()}${local.slice(1)}`;
735
751
  }
736
752
  function outputRows(ctx, map, custom = false) {
@@ -3,6 +3,7 @@ import { resolveFromRegistry } from './registry-fallback.js';
3
3
  import { nestConnectorBody } from '../core/connector-inputs.js';
4
4
  import { types } from '../core/expr.js';
5
5
  import { ownEntry } from '../core/own-entry.js';
6
+ import { CompileRefusal } from '../core/compile-refusal.js';
6
7
  const require = createRequire(import.meta.url);
7
8
  const REGISTRY = require('./registry-types.json');
8
9
  /** Every extension type in the snapshot, sorted. */
@@ -33,7 +34,7 @@ export function registryType(type, inline) {
33
34
  // for one the snapshot describes wrongly. See `ActivityNodeOpts.spec`.
34
35
  const spec = inline ?? REGISTRY.types[type] ?? resolveFromRegistry(type);
35
36
  if (!spec) {
36
- throw new Error(`unknown extension type "${type}". The committed registry snapshot has ${REGISTRY.provenance.typeCount} types `
37
+ throw new CompileRefusal('BPMN_TYPE_UNKNOWN', `unknown extension type "${type}". The committed registry snapshot has ${REGISTRY.provenance.typeCount} types `
37
38
  + `(uip ${REGISTRY.provenance.uipVersion}) and the registry could not be asked for this one either — `
38
39
  + 'no `uip` on PATH, not logged in, or the type does not exist. '
39
40
  + 'Check the spelling with `uip maestro bpmn registry search <term>`, then refresh the snapshot with '
@@ -306,7 +307,7 @@ function contextRows(m, type, spec, supplied, verbatim = false) {
306
307
  ]);
307
308
  const unknown = Object.keys(supplied).filter((k) => !known.has(k));
308
309
  if (unknown.length) {
309
- throw new Error(`context field(s) ${unknown.map((u) => `"${u}"`).join(', ')} are not declared by this extension type. ` +
310
+ throw new CompileRefusal('BPMN_CONTEXT_FIELD_UNKNOWN', `context field(s) ${unknown.map((u) => `"${u}"`).join(', ')} are not declared by this extension type. ` +
310
311
  `Declared: ${[...known].join(', ') || '(none)'}.`);
311
312
  }
312
313
  // Permitted-but-undeclared fields (a recorded `extraContext` gap) still have to
@@ -355,7 +356,7 @@ function inputRows(m, spec, inputs) {
355
356
  const keys = Object.keys(inputs);
356
357
  if (spec.inputPattern === 'none') {
357
358
  if (keys.length) {
358
- throw new Error(`this extension type takes no inputs, but ${keys.length} were supplied (${keys.join(', ')}).`);
359
+ throw new CompileRefusal('BPMN_TYPE_NO_INPUTS', `this extension type takes no inputs, but ${keys.length} were supplied (${keys.join(', ')}).`);
359
360
  }
360
361
  return [];
361
362
  }
@@ -502,7 +503,7 @@ export function createTypedNode(m, id, type, input,
502
503
  varType) {
503
504
  const spec = registryType(type, input.spec);
504
505
  if (!spec.element || !spec.tag) {
505
- throw new Error(`extension type "${type}" has no element/tag in the registry snapshot; it cannot be authored.`);
506
+ throw new CompileRefusal('BPMN_TYPE_NOT_AUTHORABLE', `extension type "${type}" has no element/tag in the registry snapshot; it cannot be authored.`);
506
507
  }
507
508
  const payload = {
508
509
  version: 'v1',
@@ -512,7 +513,7 @@ varType) {
512
513
  // carry it, and moddle would silently drop it (see ActivityOpts in bpmn-sdk).
513
514
  if (input.skipCondition !== undefined) {
514
515
  if (spec.tag === 'uipath:mapping') {
515
- throw new Error(`extension type "${type}" serializes a uipath:mapping, which cannot carry a skipCondition.`);
516
+ throw new CompileRefusal('BPMN_TYPE_SKIP_UNSUPPORTED', `extension type "${type}" serializes a uipath:mapping, which cannot carry a skipCondition.`);
516
517
  }
517
518
  payload.skipCondition = input.skipCondition;
518
519
  }
@@ -636,7 +637,7 @@ export function verbatimOutputRows(m, rows) {
636
637
  export function createExtensionPayload(m, type, rows) {
637
638
  const spec = registryType(type);
638
639
  if (!spec.tag) {
639
- throw new Error(`extension type "${type}" has no tag in the registry snapshot; it cannot be authored.`);
640
+ throw new CompileRefusal('BPMN_TYPE_NOT_AUTHORABLE', `extension type "${type}" has no tag in the registry snapshot; it cannot be authored.`);
640
641
  }
641
642
  const payload = {
642
643
  version: 'v1',
@@ -664,7 +665,7 @@ function finish(m, spec, id, name, payload) {
664
665
  /** `bpmn:SendTask` is already the moddle type name; keep it, but validate the shape. */
665
666
  function moddleElementOf(element) {
666
667
  if (!/^bpmn:[A-Z]/.test(element)) {
667
- throw new Error(`registry element "${element}" is not a moddle type name (expected e.g. bpmn:SendTask).`);
668
+ throw new CompileRefusal('BPMN_TYPE_ELEMENT_INVALID', `registry element "${element}" is not a moddle type name (expected e.g. bpmn:SendTask).`);
668
669
  }
669
670
  return element;
670
671
  }
@@ -1,22 +1,3 @@
1
- /**
2
- * xml-ranges — locate elements by their SOURCE RANGE in an XML document.
3
- *
4
- * A tolerant scanner, not a parser: it reports where each element's text begins and
5
- * ends and nothing about what it means. That is all a merge needs, and it is the only
6
- * shape that can preserve an element **byte for byte** — the merged document is the
7
- * original text with a few ranges spliced, so anything untouched is untouched in the
8
- * literal sense, down to attribute order, quoting and indentation.
9
- *
10
- * Why not `bpmn-moddle`, which the SDK already depends on: a moddle round trip
11
- * normalises. It drops attributes its descriptor does not declare, and the whole point
12
- * of a brownfield merge is to carry through exactly the payloads the builder does NOT
13
- * model. Reading with moddle would lose the thing being preserved.
14
- *
15
- * Correspondingly, this file must not be mistaken for a general XML implementation. It
16
- * handles what a `.bpmn` contains — elements, attributes, comments, CDATA, processing
17
- * instructions, a doctype — and it assumes well-formed input, because every caller has
18
- * either just written the file or is about to hand it to a real parser anyway.
19
- */
20
1
  /** One element's place in the source text. */
21
2
  export interface XmlRange {
22
3
  /** Qualified name as written, e.g. `bpmn:scriptTask`. */
@@ -17,6 +17,7 @@
17
17
  * instructions, a doctype — and it assumes well-formed input, because every caller has
18
18
  * either just written the file or is about to hand it to a real parser anyway.
19
19
  */
20
+ import { CompileRefusal } from '../core/compile-refusal.js';
20
21
  const ATTRIBUTE = /([^\s=/>]+)\s*=\s*("([^"]*)"|'([^']*)')/g;
21
22
  function parseAttributes(source) {
22
23
  const attributes = {};
@@ -44,13 +45,13 @@ function endOfTag(xml, from) {
44
45
  return i;
45
46
  }
46
47
  }
47
- throw new Error(`xml-ranges: unterminated tag at offset ${from}`);
48
+ throw new CompileRefusal('BPMN_XML_MALFORMED', `xml-ranges: unterminated tag at offset ${from}`);
48
49
  }
49
50
  /** Skip a construct that ends with a known terminator; returns the offset past it. */
50
51
  function skipUntil(xml, from, terminator) {
51
52
  const at = xml.indexOf(terminator, from);
52
53
  if (at < 0)
53
- throw new Error(`xml-ranges: unterminated ${terminator.trim()} at offset ${from}`);
54
+ throw new CompileRefusal('BPMN_XML_MALFORMED', `xml-ranges: unterminated ${terminator.trim()} at offset ${from}`);
54
55
  return at + terminator.length;
55
56
  }
56
57
  function* rawTags(xml) {
@@ -122,7 +123,7 @@ export function parseXmlRanges(xml) {
122
123
  if (tag.closing) {
123
124
  const node = stack.pop();
124
125
  if (!node)
125
- throw new Error(`xml-ranges: stray </${tag.name}> at offset ${tag.start}`);
126
+ throw new CompileRefusal('BPMN_XML_MALFORMED', `xml-ranges: stray </${tag.name}> at offset ${tag.start}`);
126
127
  finish(tag, node, tag.end);
127
128
  continue;
128
129
  }
@@ -134,7 +135,7 @@ export function parseXmlRanges(xml) {
134
135
  stack.push({ tag, children: [] });
135
136
  }
136
137
  if (stack.length > 0) {
137
- throw new Error(`xml-ranges: unclosed <${stack[stack.length - 1].tag.name}>`);
138
+ throw new CompileRefusal('BPMN_XML_MALFORMED', `xml-ranges: unclosed <${stack[stack.length - 1].tag.name}>`);
138
139
  }
139
140
  return roots;
140
141
  }