@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.
- package/dist/bpmn/bpmn-expr-check.d.ts +2 -1
- package/dist/bpmn/bpmn-expr-check.js +4 -2
- package/dist/bpmn/bpmn-sdk.js +57 -47
- package/dist/bpmn/connector.js +2 -1
- package/dist/bpmn/human-task-form.js +16 -15
- package/dist/bpmn/intsvc.js +19 -18
- package/dist/bpmn/load.js +4 -3
- package/dist/bpmn/serialize.js +29 -13
- package/dist/bpmn/typed-node.js +8 -7
- package/dist/bpmn/xml-ranges.d.ts +0 -19
- package/dist/bpmn/xml-ranges.js +5 -4
- package/dist/core/refusals.d.ts +130 -1
- package/dist/core/refusals.js +134 -5
- package/package.json +2 -2
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { type ExprDiagnostic } from '../core/expr-check.js';
|
|
25
25
|
import type { BuiltBpmn } from './bpmn-sdk.js';
|
|
26
|
+
import { CompileRefusal } from '../core/compile-refusal.js';
|
|
26
27
|
/** A diagnostic plus the element it was found in. */
|
|
27
28
|
export interface BpmnLocatedDiagnostic extends ExprDiagnostic {
|
|
28
29
|
where: string;
|
|
@@ -35,7 +36,7 @@ export declare function checkBpmnExpressions(built: BuiltBpmn): BpmnLocatedDiagn
|
|
|
35
36
|
* `FlowBuildError`: `message` lists every problem; `diagnostics` carries them
|
|
36
37
|
* structured.
|
|
37
38
|
*/
|
|
38
|
-
export declare class BpmnBuildError extends
|
|
39
|
+
export declare class BpmnBuildError extends CompileRefusal {
|
|
39
40
|
readonly diagnostics: BpmnLocatedDiagnostic[];
|
|
40
41
|
constructor(built: BuiltBpmn, diagnostics: BpmnLocatedDiagnostic[]);
|
|
41
42
|
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { checkExpression } from '../core/expr-check.js';
|
|
25
25
|
import { nodeDeclaredVars } from './declared-vars.js';
|
|
26
|
+
import { CompileRefusal } from '../core/compile-refusal.js';
|
|
26
27
|
const NOUN = 'variable';
|
|
27
28
|
const NAMESPACE = 'vars';
|
|
28
29
|
const BINDING_NOUN = 'binding';
|
|
@@ -213,12 +214,13 @@ function connectorBindingNames(nodes) {
|
|
|
213
214
|
* `FlowBuildError`: `message` lists every problem; `diagnostics` carries them
|
|
214
215
|
* structured.
|
|
215
216
|
*/
|
|
216
|
-
export class BpmnBuildError extends
|
|
217
|
+
export class BpmnBuildError extends CompileRefusal {
|
|
217
218
|
diagnostics;
|
|
218
219
|
constructor(built, diagnostics) {
|
|
219
220
|
const label = built.name || built.id || 'process';
|
|
220
221
|
const lines = diagnostics.map((d) => ` ✗ [${d.code}] in ${d.where}: ${d.message}${d.suggestion ? ` (did you mean "${d.suggestion}"?)` : ''}`);
|
|
221
|
-
|
|
222
|
+
// The first problem's code; `diagnostics` carries every one.
|
|
223
|
+
super(diagnostics[0]?.code ?? 'EXPR_UNDECLARED_REF', `Cannot build BPMN process "${label}" — ${diagnostics.length} expression problem(s):\n${lines.join('\n')}`);
|
|
222
224
|
this.name = 'BpmnBuildError';
|
|
223
225
|
this.diagnostics = diagnostics;
|
|
224
226
|
}
|
package/dist/bpmn/bpmn-sdk.js
CHANGED
|
@@ -5,6 +5,7 @@ import { schemaVersionRefusal } from '../schema-version.js';
|
|
|
5
5
|
import { bindingRequiredFields, REGISTRY_GAPS, registryType } from './typed-node.js';
|
|
6
6
|
import { resolveResourceKeyPattern } from './resource-key-pattern.js';
|
|
7
7
|
import { quickHumanTaskRows } from './human-task-form.js';
|
|
8
|
+
import { CompileRefusal } from '../core/compile-refusal.js';
|
|
8
9
|
import { DATA_SERVICE_ACTIVITY_TYPE, DATA_SERVICE_CONNECTOR_KEY, dataServiceWire } from './data-service.js';
|
|
9
10
|
import { MANAGED_HTTP_CONNECTION_NAME, MANAGED_HTTP_CONNECTOR_KEY, MANAGED_HTTP_FOLDER_NAME, MANAGED_HTTP_TYPE, managedHttpContext, managedHttpInputs, managedHttpOutputs, } from './managed-http.js';
|
|
10
11
|
import { ownEntry } from '../core/own-entry.js';
|
|
@@ -14,6 +15,10 @@ export { connectorOutputVars, nodeDeclaredVars, typedNodeDeclaredVars } from './
|
|
|
14
15
|
function normTimer(t) {
|
|
15
16
|
return typeof t === 'string' ? { duration: t } : t;
|
|
16
17
|
}
|
|
18
|
+
/** Every {@link PlainTaskElement}, for the run-time check in `.plainTask()`. */
|
|
19
|
+
const PLAIN_TASK_ELEMENTS = new Set([
|
|
20
|
+
'bpmn:task', 'bpmn:userTask', 'bpmn:serviceTask', 'bpmn:sendTask', 'bpmn:manualTask', 'bpmn:businessRuleTask', 'bpmn:receiveTask',
|
|
21
|
+
]);
|
|
17
22
|
/** Bookkeeping the nesting sugar shares across every scope of one process. */
|
|
18
23
|
class SugarRegistry {
|
|
19
24
|
/** Flow ids the builder wrote by implication rather than from an explicit `.sequenceFlow()`. */
|
|
@@ -215,7 +220,7 @@ function toErrorDef(defs, error, where) {
|
|
|
215
220
|
// ERROR_END_EVENT_MISSING_EXCEPTION on an end event) after a whole toolchain cycle.
|
|
216
221
|
// `error: true` is the way to say "any error"; a name is the way to say which.
|
|
217
222
|
if (spec.name.trim() === '') {
|
|
218
|
-
throw new
|
|
223
|
+
throw new CompileRefusal('BPMN_ERROR_NAME_EMPTY', `${where}: the error name is empty, so the declared \`bpmn:error\` has nothing to match on. ` +
|
|
219
224
|
'Pass `error: true` to catch any error, or a name — `error: "PaymentFailed"`, or ' +
|
|
220
225
|
'`error: { name: "PaymentFailed", code: "PAYMENT_FAILED" }`.');
|
|
221
226
|
}
|
|
@@ -335,10 +340,10 @@ class ScopeBuilder {
|
|
|
335
340
|
*/
|
|
336
341
|
flowMode(mode) {
|
|
337
342
|
if (this._isArm) {
|
|
338
|
-
throw new
|
|
343
|
+
throw new CompileRefusal('BPMN_FLOW_MODE_IN_BODY', `.flowMode('${mode}') is not available inside an arm or handler body — those always chain in order.`);
|
|
339
344
|
}
|
|
340
345
|
if (this._nodes.length > 0) {
|
|
341
|
-
throw new
|
|
346
|
+
throw new CompileRefusal('BPMN_FLOW_MODE_AFTER_ELEMENT', `.flowMode('${mode}') must come before the scope's first element; "${this._nodes[0].id}" is already placed.`);
|
|
342
347
|
}
|
|
343
348
|
this._mode = mode;
|
|
344
349
|
this._scopeMode = mode;
|
|
@@ -360,7 +365,7 @@ class ScopeBuilder {
|
|
|
360
365
|
// with no output row (the fire-and-forget Async starts) would accept it and emit
|
|
361
366
|
// nothing, so the author reads a variable nothing ever writes.
|
|
362
367
|
if (node.kind === 'typed' && node.outputVar !== undefined && !node.outputRows && !registryType(node.type, node.spec).output) {
|
|
363
|
-
throw new
|
|
368
|
+
throw new CompileRefusal('BPMN_OUTPUT_VAR_NO_OUTPUT_ROW', `"${node.id}" (${node.type}) publishes no output row, so outputVar "${node.outputVar}" would bind nothing. ` +
|
|
364
369
|
(node.type.endsWith('Async')
|
|
365
370
|
? 'The Async start returns before the job does: drop outputVar, or use the waiting form (async: false) to read the response.'
|
|
366
371
|
: 'Drop outputVar, or spell the rows with outputRows.'));
|
|
@@ -381,15 +386,15 @@ class ScopeBuilder {
|
|
|
381
386
|
*/
|
|
382
387
|
place(node, viaSugar = false) {
|
|
383
388
|
if (this._closedBy !== undefined) {
|
|
384
|
-
throw new
|
|
389
|
+
throw new CompileRefusal('BPMN_PATH_ALREADY_LEFT', `"${node.id}" follows ${this._closedBy}, which already left this path — nothing can come after it.`);
|
|
385
390
|
}
|
|
386
391
|
if (this._mode === 'sequence') {
|
|
387
392
|
if (isStartLike(node)) {
|
|
388
393
|
if (this._isArm) {
|
|
389
|
-
throw new
|
|
394
|
+
throw new CompileRefusal('BPMN_START_IN_BODY', `startEvent "${node.id}": a body has no start event of its own — it begins where the enclosing element hands over to it.`);
|
|
390
395
|
}
|
|
391
396
|
if (this._synthesizedStart !== undefined) {
|
|
392
|
-
throw new
|
|
397
|
+
throw new CompileRefusal('BPMN_START_ALREADY_SYNTHESIZED', `startEvent "${node.id}": an event sub-process has exactly one start event, and this one's is ` +
|
|
393
398
|
`synthesized as "${this._synthesizedStart}" — write what follows it instead.`);
|
|
394
399
|
}
|
|
395
400
|
// Explicit wins here as everywhere: a plain exit whose element already has an
|
|
@@ -399,7 +404,7 @@ class ScopeBuilder {
|
|
|
399
404
|
// (a branch with nowhere to go), blocks a new chain.
|
|
400
405
|
const dangling = this._open.filter((e) => e.condition !== undefined || e.defaultOf !== undefined || !this.hasExplicitOutgoing(e.source));
|
|
401
406
|
if (dangling.length > 0) {
|
|
402
|
-
throw new
|
|
407
|
+
throw new CompileRefusal('BPMN_START_WHILE_PATH_OPEN', `startEvent "${node.id}" begins a new chain while "${dangling.map((e) => e.source).join('", "')}" is still ` +
|
|
403
408
|
`open — end the current path with an end event before starting another.`);
|
|
404
409
|
}
|
|
405
410
|
}
|
|
@@ -408,7 +413,7 @@ class ScopeBuilder {
|
|
|
408
413
|
// wired INTO, and its outgoing flows are the author's to write — and whatever
|
|
409
414
|
// follows it is checked at close for an explicit incoming edge.
|
|
410
415
|
if (GATEWAY_NODE_KINDS.has(node.kind) && !viaSugar && this._isArm) {
|
|
411
|
-
throw new
|
|
416
|
+
throw new CompileRefusal('BPMN_GATEWAY_IN_BODY', `${node.kind} "${node.id}": inside a body, branch with .choose() / .fork() / .race() — ` +
|
|
412
417
|
`a bare gateway's outgoing flows cannot be implied.`);
|
|
413
418
|
}
|
|
414
419
|
}
|
|
@@ -416,7 +421,7 @@ class ScopeBuilder {
|
|
|
416
421
|
if (target) {
|
|
417
422
|
if (this._mode === 'sequence' && this._started && this._open.length === 0) {
|
|
418
423
|
if (this._isArm) {
|
|
419
|
-
throw new
|
|
424
|
+
throw new CompileRefusal('BPMN_UNREACHABLE_IN_BODY', `"${node.id}" is unreachable: the path ended before it (an end event, or every arm of a branch), so nothing leads to it.`);
|
|
420
425
|
}
|
|
421
426
|
// Placed with nothing implied into it. Legal only if an explicit flow leads
|
|
422
427
|
// here — which may be written later — so it is checked when the scope closes.
|
|
@@ -520,7 +525,7 @@ class ScopeBuilder {
|
|
|
520
525
|
const clash = here.list.find((o) => o.catchAll === caught.catchAll && o.ref === caught.ref);
|
|
521
526
|
if (clash !== undefined) {
|
|
522
527
|
const where = here.key !== undefined ? `on "${here.key}"` : 'in this scope';
|
|
523
|
-
throw new
|
|
528
|
+
throw new CompileRefusal('BPMN_DUP_ERROR_CATCH', `"${n.id}" catches the same error as "${clash.id}" ${where} ` +
|
|
524
529
|
`(${caught.catchAll ? 'both catch ANY error' : `both catch "${caught.ref}"`}), so one of the two can ` +
|
|
525
530
|
'never run. Catch a specific error in each, or keep the one catch-all.');
|
|
526
531
|
}
|
|
@@ -530,14 +535,14 @@ class ScopeBuilder {
|
|
|
530
535
|
}
|
|
531
536
|
for (const { id, after } of this._unattached) {
|
|
532
537
|
if (!this._flows.some((f) => f.target === id)) {
|
|
533
|
-
throw new
|
|
538
|
+
throw new CompileRefusal('BPMN_SEQUENCE_NO_INCOMING', `"${id}" has no incoming flow. In a sequence scope it was placed after "${after}", which does not ` +
|
|
534
539
|
`continue to it (a bare gateway's outgoing flows are never implied; an end event ends the path). ` +
|
|
535
540
|
`Wire it with .sequenceFlow(…, "${id}"), or place it after an element that continues.`);
|
|
536
541
|
}
|
|
537
542
|
}
|
|
538
543
|
for (const { id, reason } of this._storage.mustClose) {
|
|
539
544
|
if (!this._flows.some((f) => f.source === id)) {
|
|
540
|
-
throw new
|
|
545
|
+
throw new CompileRefusal('BPMN_HANDLER_PATH_NOT_CLOSED', `${reason} ends at "${id}", which has no outgoing flow. A non-interrupting handler runs beside a token ` +
|
|
541
546
|
`that will reach the next statement anyway, so its path cannot rejoin there: end it with an end event, ` +
|
|
542
547
|
`leave it with .goto(), or wire it with .sequenceFlow("${id}", …).`);
|
|
543
548
|
}
|
|
@@ -546,11 +551,11 @@ class ScopeBuilder {
|
|
|
546
551
|
for (const exit of this._open) {
|
|
547
552
|
const who = exit.origin ?? what;
|
|
548
553
|
if (exit.condition !== undefined || exit.defaultOf !== undefined) {
|
|
549
|
-
throw new
|
|
554
|
+
throw new CompileRefusal('BPMN_BRANCH_NOWHERE_TO_GO', `${who} has nowhere to go: nothing follows the branch in ${what}. Add the element that comes ` +
|
|
550
555
|
`next, or end every arm inside its own body.`);
|
|
551
556
|
}
|
|
552
557
|
if (!this._flows.some((f) => f.source === exit.source)) {
|
|
553
|
-
throw new
|
|
558
|
+
throw new CompileRefusal('BPMN_PATH_NOT_CONTINUED', `${who} ends at "${exit.source}", which has no outgoing flow, and nothing follows it in ${what} ` +
|
|
554
559
|
`to continue it. Add the element that comes next, end the path with an end event, or wire it with ` +
|
|
555
560
|
`.sequenceFlow("${exit.source}", …).`);
|
|
556
561
|
}
|
|
@@ -588,10 +593,10 @@ class ScopeBuilder {
|
|
|
588
593
|
const { opts } = spec;
|
|
589
594
|
const id = opts.id ?? `${activityId}_${suffix}`;
|
|
590
595
|
if (this._nodes.some((n) => n.id === id)) {
|
|
591
|
-
throw new
|
|
596
|
+
throw new CompileRefusal('BPMN_DUP_BOUNDARY_ID', `"${activityId}" already has a boundary event "${id}"; a second .${suffix}() on one activity needs its own \`id\`.`);
|
|
592
597
|
}
|
|
593
598
|
if (opts.errorVar !== undefined && opts.payload !== undefined) {
|
|
594
|
-
throw new
|
|
599
|
+
throw new CompileRefusal('BPMN_ERROR_VAR_AND_PAYLOAD', `.${suffix}() on "${activityId}": pass \`errorVar\` or \`payload\`, not both — both write the event's output row.`);
|
|
595
600
|
}
|
|
596
601
|
// Two handlers on one activity catching the SAME error: whichever the runtime picks,
|
|
597
602
|
// the other is dead, and the platform rejects the pair outright
|
|
@@ -607,7 +612,7 @@ class ScopeBuilder {
|
|
|
607
612
|
return other !== undefined && other.catchAll === caught.catchAll && other.ref === caught.ref;
|
|
608
613
|
});
|
|
609
614
|
if (clash !== undefined) {
|
|
610
|
-
throw new
|
|
615
|
+
throw new CompileRefusal('BPMN_DUP_ERROR_CATCH', `.${suffix}() on "${activityId}" catches the same error as "${clash.id}" ` +
|
|
611
616
|
`(${caught.catchAll ? 'both catch ANY error' : `both catch "${caught.ref}"`}), so one of the two can never ` +
|
|
612
617
|
'run. Catch a specific error in each handler, or keep the one catch-all.');
|
|
613
618
|
}
|
|
@@ -661,7 +666,7 @@ class ScopeBuilder {
|
|
|
661
666
|
if (mustWait) {
|
|
662
667
|
const first = ab._first;
|
|
663
668
|
if (!first || !waitsForEvent(first)) {
|
|
664
|
-
throw new
|
|
669
|
+
throw new CompileRefusal('BPMN_RACE_ARM_NOT_WAIT', `${what}: arm ${i + 1} must begin with what it waits for — an intermediateCatchEvent (message or timer), ` +
|
|
665
670
|
`a receiveMessage, or a waitForEvent — but begins with ${first ? `${first.kind} "${first.id}"` : 'nothing'}.`);
|
|
666
671
|
}
|
|
667
672
|
}
|
|
@@ -669,7 +674,7 @@ class ScopeBuilder {
|
|
|
669
674
|
});
|
|
670
675
|
const empties = exits.filter((e) => e.source === split.id);
|
|
671
676
|
if (empties.length > 1) {
|
|
672
|
-
throw new
|
|
677
|
+
throw new CompileRefusal('BPMN_BRANCH_EMPTY_ARMS', `${what} has ${empties.length} arms with no body; at most one arm may be empty.`);
|
|
673
678
|
}
|
|
674
679
|
if (exits.length >= 2) {
|
|
675
680
|
// Only when more than one arm actually reaches the continuation: a single
|
|
@@ -705,15 +710,15 @@ class ScopeBuilder {
|
|
|
705
710
|
*/
|
|
706
711
|
startEvent(id, opts = {}) {
|
|
707
712
|
if (opts.trigger !== undefined && opts.timer === undefined) {
|
|
708
|
-
throw new
|
|
713
|
+
throw new CompileRefusal('BPMN_START_TRIGGER_NO_TIMER', `startEvent "${id}": \`trigger\` needs \`timer\` — the schedule lives in the ` +
|
|
709
714
|
`bpmn:timerEventDefinition, not in the trigger extension, so a trigger without one emits an ` +
|
|
710
715
|
`empty definition and fires never. \`uip maestro bpmn validate\` reports that as Valid.`);
|
|
711
716
|
}
|
|
712
717
|
if (opts.trigger !== undefined && opts.payload !== undefined) {
|
|
713
|
-
throw new
|
|
718
|
+
throw new CompileRefusal('BPMN_START_TRIGGER_AND_PAYLOAD', `startEvent "${id}": \`trigger\` and \`payload\` both write the event's extension element; pass one.`);
|
|
714
719
|
}
|
|
715
720
|
if (opts.interrupting === false && opts.error !== undefined) {
|
|
716
|
-
throw new
|
|
721
|
+
throw new CompileRefusal('BPMN_ERROR_START_NOT_INTERRUPTING', `startEvent "${id}": an error start is always interrupting — the throw has already ended the scope it came from.`);
|
|
717
722
|
}
|
|
718
723
|
return this.push({
|
|
719
724
|
kind: 'startEvent',
|
|
@@ -793,7 +798,7 @@ class ScopeBuilder {
|
|
|
793
798
|
else if (opts.message !== undefined)
|
|
794
799
|
event = { type: 'message', messageRef: this._defs.messageRef(opts.message) };
|
|
795
800
|
else
|
|
796
|
-
throw new
|
|
801
|
+
throw new CompileRefusal('BPMN_BOUNDARY_NO_EVENT', `boundaryEvent "${id}" needs one of { message, timer, error }.`);
|
|
797
802
|
return this.push({
|
|
798
803
|
kind: 'boundaryEvent',
|
|
799
804
|
id,
|
|
@@ -930,11 +935,16 @@ class ScopeBuilder {
|
|
|
930
935
|
* ```
|
|
931
936
|
*/
|
|
932
937
|
plainTask(id, opts = {}, body) {
|
|
938
|
+
const element = opts.element ?? 'bpmn:task';
|
|
939
|
+
// `element` is only type-restricted, and a `.bpmn.ts` loads without a type check.
|
|
940
|
+
if (!PLAIN_TASK_ELEMENTS.has(element)) {
|
|
941
|
+
throw new CompileRefusal('BPMN_PLAIN_TASK_ELEMENT_INVALID', `.plainTask('${id}'): element "${element}" is not a plain task element. Use one of ${[...PLAIN_TASK_ELEMENTS].join(', ')}.`);
|
|
942
|
+
}
|
|
933
943
|
return this.pushActivity({
|
|
934
944
|
kind: 'plainTask',
|
|
935
945
|
id,
|
|
936
946
|
name: opts.name,
|
|
937
|
-
element
|
|
947
|
+
element,
|
|
938
948
|
tags: opts.tags,
|
|
939
949
|
retry: opts.retry,
|
|
940
950
|
errorMapping: opts.errorMapping,
|
|
@@ -952,7 +962,7 @@ class ScopeBuilder {
|
|
|
952
962
|
? makeConnector(a, b, c, bpmnOpts)
|
|
953
963
|
: makeConnector(a, b, bpmnOpts);
|
|
954
964
|
if (spec.kind !== 'connector')
|
|
955
|
-
throw new Error('connector(): unexpected spec kind');
|
|
965
|
+
throw new Error('internal SDK error: connector(): unexpected spec kind');
|
|
956
966
|
return this.pushActivity({
|
|
957
967
|
kind: 'connector',
|
|
958
968
|
id,
|
|
@@ -1013,7 +1023,7 @@ class ScopeBuilder {
|
|
|
1013
1023
|
if (opts.connection !== undefined)
|
|
1014
1024
|
return this.connectorHttp(id, opts, body);
|
|
1015
1025
|
if (opts.folder !== undefined || opts.connectorKey !== undefined) {
|
|
1016
|
-
throw new
|
|
1026
|
+
throw new CompileRefusal('BPMN_HTTP_CONNECTOR_FIELDS_NO_CONNECTION', `http "${id}": \`folder\` and \`connectorKey\` belong to connector mode, which \`connection\` selects — ` +
|
|
1017
1027
|
`pass the connection's binding name too, or drop them for a connectionless request.`);
|
|
1018
1028
|
}
|
|
1019
1029
|
return this.pushActivity({
|
|
@@ -1052,7 +1062,7 @@ class ScopeBuilder {
|
|
|
1052
1062
|
const codeVar = `${id}_code`;
|
|
1053
1063
|
const headersVar = `${id}_headers`;
|
|
1054
1064
|
if (opts.outputVar === codeVar || opts.outputVar === headersVar) {
|
|
1055
|
-
throw new
|
|
1065
|
+
throw new CompileRefusal('BPMN_HTTP_OUTPUT_VAR_RESERVED', `http "${id}": outputVar "${opts.outputVar}" is the variable connector mode writes the status code or headers to. ` +
|
|
1056
1066
|
`Pick another name, or drop outputVar for the default "${id}_response".`);
|
|
1057
1067
|
}
|
|
1058
1068
|
const connection = this.dataServiceBinding(id, 'connection', opts.connection, 'ConnectionId', MANAGED_HTTP_CONNECTION_NAME);
|
|
@@ -1235,7 +1245,7 @@ class ScopeBuilder {
|
|
|
1235
1245
|
decl.resourceKey = keyed.key;
|
|
1236
1246
|
else if (decl.resourceKey !== keyed.key) {
|
|
1237
1247
|
const resource = field.bindingInfo?.resource ?? 'process';
|
|
1238
|
-
throw new
|
|
1248
|
+
throw new CompileRefusal('BPMN_BINDING_RESOURCE_KEY_CONFLICT', `${type} "${id}": its ${resource} key "${keyed.key}" is read through =bindings.${decl.id}, whose resource key is already ` +
|
|
1239
1249
|
`"${decl.resourceKey}" (another node's pair, or the binding's own), so the pair would name two resources. ` +
|
|
1240
1250
|
`A ${resource} binding names one: declare one per node, e.g. ` +
|
|
1241
1251
|
`\`.binding('${decl.id}_${id}', { resource: '${decl.resource}', propertyAttribute: '${decl.propertyAttribute}', value: '${decl.default}' })\`.`);
|
|
@@ -1266,7 +1276,7 @@ class ScopeBuilder {
|
|
|
1266
1276
|
const { field: blockedField, ref } = resolved.blocked;
|
|
1267
1277
|
const refId = ref.slice('=bindings.'.length);
|
|
1268
1278
|
const resource = field.bindingInfo?.resource ?? 'process';
|
|
1269
|
-
throw new
|
|
1279
|
+
throw new CompileRefusal('BPMN_BINDING_READ_BEFORE_DECLARED', `${type} "${id}": ${blockedField} reads ${ref}, which no .binding() declares with a value before this node, ` +
|
|
1270
1280
|
`so the ${resource} key \`${pattern}\` it is paired under cannot be built. Declare ` +
|
|
1271
1281
|
`\`.binding('${refId}', { resource: '${resource}', propertyAttribute: '${blockedField}', value: '…' })\` first, or pass the value itself.`);
|
|
1272
1282
|
}
|
|
@@ -1463,7 +1473,7 @@ class ScopeBuilder {
|
|
|
1463
1473
|
*/
|
|
1464
1474
|
businessRule(id, opts, body) {
|
|
1465
1475
|
if (opts.releaseKey !== undefined || opts.folderKey !== undefined) {
|
|
1466
|
-
throw new
|
|
1476
|
+
throw new CompileRefusal('BPMN_BUSINESS_RULE_KEY_FIELDS', `businessRule('${id}'): a business rule is addressed by name and folder (version v3 binds the ` +
|
|
1467
1477
|
'BusinessRule resource by `name` + `folderPath`); drop `releaseKey` / `folderKey`.');
|
|
1468
1478
|
}
|
|
1469
1479
|
// The default output variable is pinned rather than derived. Derived, it would come
|
|
@@ -1608,7 +1618,7 @@ class ScopeBuilder {
|
|
|
1608
1618
|
const hasApp = opts.app !== undefined;
|
|
1609
1619
|
const hasForm = opts.form !== undefined;
|
|
1610
1620
|
if (hasApp === hasForm) {
|
|
1611
|
-
throw new
|
|
1621
|
+
throw new CompileRefusal('BPMN_HUMAN_TASK_APP_XOR_FORM', `humanTask "${id}": pass exactly one of app (a deployed Action App) or form ({ fields, outcomes }, an inline form that needs no app).`);
|
|
1612
1622
|
}
|
|
1613
1623
|
if (hasForm) {
|
|
1614
1624
|
const o = opts;
|
|
@@ -1616,7 +1626,7 @@ class ScopeBuilder {
|
|
|
1616
1626
|
// outcomes (`actions`) and what the human sees (`input`).
|
|
1617
1627
|
const appOnly = ['appVersion', 'actions', 'key', 'input'].filter((k) => opts[k] !== undefined);
|
|
1618
1628
|
if (appOnly.length) {
|
|
1619
|
-
throw new
|
|
1629
|
+
throw new CompileRefusal('BPMN_HUMAN_TASK_APP_FIELDS_ON_FORM', `humanTask "${id}": ${appOnly.join(', ')} belong to an app-backed task. On an inline form, put the outcomes in form.outcomes and what the human sees in form.fields.`);
|
|
1620
1630
|
}
|
|
1621
1631
|
const rows = quickHumanTaskRows(id, this._sugar.processId, o);
|
|
1622
1632
|
return this.pushActivity({
|
|
@@ -2046,16 +2056,16 @@ class ScopeBuilder {
|
|
|
2046
2056
|
eventSubProcess(id, opts, fn) {
|
|
2047
2057
|
const given = [opts.error !== undefined, opts.message !== undefined, opts.timer !== undefined].filter(Boolean).length;
|
|
2048
2058
|
if (given !== 1) {
|
|
2049
|
-
throw new
|
|
2059
|
+
throw new CompileRefusal('BPMN_EVENT_SUBPROCESS_START_COUNT', `eventSubProcess "${id}" needs exactly one of { error, message, timer } — that is what starts it.`);
|
|
2050
2060
|
}
|
|
2051
2061
|
if (opts.error !== undefined && opts.interrupting === false) {
|
|
2052
|
-
throw new
|
|
2062
|
+
throw new CompileRefusal('BPMN_ERROR_START_NOT_INTERRUPTING', `eventSubProcess "${id}": an error start is always interrupting — the throw has already ended the scope it came from.`);
|
|
2053
2063
|
}
|
|
2054
2064
|
if (opts.errorVar !== undefined && opts.errorVar !== false && opts.error === undefined) {
|
|
2055
|
-
throw new
|
|
2065
|
+
throw new CompileRefusal('BPMN_ERROR_VAR_WITHOUT_ERROR', `eventSubProcess "${id}": \`errorVar\` captures an error, so it needs \`error\`.`);
|
|
2056
2066
|
}
|
|
2057
2067
|
if (typeof opts.errorVar === 'string' && opts.startPayload !== undefined) {
|
|
2058
|
-
throw new
|
|
2068
|
+
throw new CompileRefusal('BPMN_ERROR_VAR_AND_PAYLOAD', `eventSubProcess "${id}": pass \`errorVar\` or \`startPayload\`, not both — both write the start event's output row.`);
|
|
2059
2069
|
}
|
|
2060
2070
|
// The capture row is on by default for an error start: without a declared `Error`
|
|
2061
2071
|
// the platform validator rejects every `vars.Error` read in the net. An explicit
|
|
@@ -2078,7 +2088,7 @@ class ScopeBuilder {
|
|
|
2078
2088
|
return other !== undefined && other.catchAll === (mineRef === undefined) && other.ref === mineRef;
|
|
2079
2089
|
});
|
|
2080
2090
|
if (clash !== undefined) {
|
|
2081
|
-
throw new
|
|
2091
|
+
throw new CompileRefusal('BPMN_DUP_ERROR_CATCH', `eventSubProcess "${id}" catches the same error as "${clash.id}" in this scope ` +
|
|
2082
2092
|
`(${mineRef === undefined ? 'both catch ANY error' : `both catch "${mineRef}"`}), so one of the ` +
|
|
2083
2093
|
'two can never run. Catch a specific error in each net, or keep the one catch-all.');
|
|
2084
2094
|
}
|
|
@@ -2151,13 +2161,13 @@ class ScopeBuilder {
|
|
|
2151
2161
|
*/
|
|
2152
2162
|
choose(id, arms, opts = {}) {
|
|
2153
2163
|
if (!arms.length)
|
|
2154
|
-
throw new
|
|
2164
|
+
throw new CompileRefusal('BPMN_BRANCH_NO_ARMS', `choose "${id}" has no arms.`);
|
|
2155
2165
|
if (arms.filter((a) => a.otherwise).length > 1) {
|
|
2156
|
-
throw new
|
|
2166
|
+
throw new CompileRefusal('BPMN_CHOOSE_MANY_OTHERWISE', `choose "${id}" has more than one \`otherwise\` arm; at most one.`);
|
|
2157
2167
|
}
|
|
2158
2168
|
for (const a of arms) {
|
|
2159
2169
|
if (a.when === undefined && !a.otherwise)
|
|
2160
|
-
throw new
|
|
2170
|
+
throw new CompileRefusal('BPMN_CHOOSE_ARM_NO_WHEN', `choose "${id}": every arm needs \`when\` or \`otherwise: true\`.`);
|
|
2161
2171
|
}
|
|
2162
2172
|
const kind = opts.inclusive ? 'inclusiveGateway' : 'exclusiveGateway';
|
|
2163
2173
|
const split = { kind, id, name: opts.name };
|
|
@@ -2199,7 +2209,7 @@ class ScopeBuilder {
|
|
|
2199
2209
|
*/
|
|
2200
2210
|
fork(id, arms, opts = {}) {
|
|
2201
2211
|
if (!arms.length)
|
|
2202
|
-
throw new
|
|
2212
|
+
throw new CompileRefusal('BPMN_BRANCH_NO_ARMS', `fork "${id}" has no arms.`);
|
|
2203
2213
|
return this.branch({ kind: 'parallelGateway', id, name: opts.name }, 'parallelGateway', arms.map((body, i) => ({ entry: { source: id, origin: `arm ${i + 1} of fork "${id}"` }, body })), `fork "${id}"`, false, opts.join);
|
|
2204
2214
|
}
|
|
2205
2215
|
/**
|
|
@@ -2234,7 +2244,7 @@ class ScopeBuilder {
|
|
|
2234
2244
|
*/
|
|
2235
2245
|
race(id, arms, opts = {}) {
|
|
2236
2246
|
if (!arms.length)
|
|
2237
|
-
throw new
|
|
2247
|
+
throw new CompileRefusal('BPMN_BRANCH_NO_ARMS', `race "${id}" has no arms.`);
|
|
2238
2248
|
return this.branch({ kind: 'eventBasedGateway', id, name: opts.name }, 'exclusiveGateway', arms.map((body, i) => ({ entry: { source: id, origin: `arm ${i + 1} of race "${id}"` }, body })), `race "${id}"`, true, opts.join);
|
|
2239
2249
|
}
|
|
2240
2250
|
// ── Flows ──
|
|
@@ -2487,11 +2497,11 @@ export class SubProcessBuilder extends ScopeBuilder {
|
|
|
2487
2497
|
continue;
|
|
2488
2498
|
const hasDefinition = n.event !== undefined || n.trigger !== undefined;
|
|
2489
2499
|
if (opts.triggeredByEvent === true && !hasDefinition) {
|
|
2490
|
-
throw new
|
|
2500
|
+
throw new CompileRefusal('BPMN_SUBPROCESS_START_NO_EVENT', `sub-process "${id}" is \`triggeredByEvent\`, so its start event "${n.id}" needs the event that starts it — ` +
|
|
2491
2501
|
'an `error`, `message` or `timer`. `.eventSubProcess(id, { error: true }, fn)` writes that start for you.');
|
|
2492
2502
|
}
|
|
2493
2503
|
if (opts.triggeredByEvent !== true && hasDefinition) {
|
|
2494
|
-
throw new
|
|
2504
|
+
throw new CompileRefusal('BPMN_SUBPROCESS_START_HAS_EVENT', `sub-process "${id}" is entered by an incoming flow, so its start event "${n.id}" must not carry an event ` +
|
|
2495
2505
|
'definition. Drop it, or make this an event sub-process with `.eventSubProcess()`.');
|
|
2496
2506
|
}
|
|
2497
2507
|
}
|
|
@@ -2576,7 +2586,7 @@ export class PathBuilder extends ScopeBuilder {
|
|
|
2576
2586
|
*/
|
|
2577
2587
|
goto(target) {
|
|
2578
2588
|
if (this._closedBy !== undefined)
|
|
2579
|
-
throw new
|
|
2589
|
+
throw new CompileRefusal('BPMN_PATH_ALREADY_LEFT', `.goto("${target}") follows ${this._closedBy}; an arm leaves once.`);
|
|
2580
2590
|
for (const exit of this._open)
|
|
2581
2591
|
this.writeImpliedFlow(exit, target);
|
|
2582
2592
|
this._open = [];
|
|
@@ -2753,7 +2763,7 @@ export class BpmnBuilder extends ScopeBuilder {
|
|
|
2753
2763
|
schemaVersion(version) {
|
|
2754
2764
|
const refusal = schemaVersionRefusal(BPMN_SCHEMA_VERSIONS, version);
|
|
2755
2765
|
if (refusal)
|
|
2756
|
-
throw new
|
|
2766
|
+
throw new CompileRefusal('SCHEMA_VERSION_UNSUPPORTED', refusal);
|
|
2757
2767
|
this._metadata = { ...this._metadata, migrationVersion: version };
|
|
2758
2768
|
return this;
|
|
2759
2769
|
}
|
package/dist/bpmn/connector.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { connectorOutputVars } from './declared-vars.js';
|
|
2
2
|
import { EQUALS_EXPR_DIALECT } from '../core/connector-checks.js';
|
|
3
3
|
import { buildConnectorInputs, nestConnectorBody, transportHttpMethod } from '../core/connector-inputs.js';
|
|
4
|
+
import { CompileRefusal } from '../core/compile-refusal.js';
|
|
4
5
|
export function createConnectorTask(ctx, n) {
|
|
5
6
|
const { m } = ctx;
|
|
6
7
|
if (!ctx.library)
|
|
7
|
-
throw new
|
|
8
|
+
throw new CompileRefusal('LIBRARY_REQUIRED', `connector task "${n.id}" needs the connector library — pass { library } to serialize or use uip maestro bpmn compile --library.`);
|
|
8
9
|
const rc = ctx.library.resolve(n.key, n.action, n.version, n.object);
|
|
9
10
|
const outVars = connectorOutputVars(n);
|
|
10
11
|
const op = (rc.entry.operation ?? {});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FORM_FIELD_TYPES, formFieldJsonSchema, formFieldOptionsProblem, formFieldOptionsWhy, formFieldSettingProblems, formFieldSettingsWire, formFieldTypeHint, liftFormFieldSettings, validFormFieldTypesText, wireFormFieldType, } from '../core/form-field-type.js';
|
|
2
2
|
import { outcomeIdOf } from '../core/hitl-routing.js';
|
|
3
3
|
import { schemaId as stableSchemaId } from '../core/stable-id.js';
|
|
4
|
+
import { CompileRefusal } from '../core/compile-refusal.js';
|
|
4
5
|
/** The `uipath:type` version both the converter and Studio Web write for `Actions.HITL`. */
|
|
5
6
|
const QUICK_TYPE_VERSION = 'v2';
|
|
6
7
|
/** The response row the app variant derives from the registry; the quick form keeps it. */
|
|
@@ -56,44 +57,44 @@ function validate(id, input) {
|
|
|
56
57
|
const { form } = input;
|
|
57
58
|
const where = `humanTask "${id}"`;
|
|
58
59
|
if (!Array.isArray(form.fields))
|
|
59
|
-
throw new
|
|
60
|
+
throw new CompileRefusal('BPMN_FORM_FIELDS_NOT_ARRAY', `${where}: form.fields must be an array.`);
|
|
60
61
|
if (!Array.isArray(form.outcomes) || form.outcomes.length === 0) {
|
|
61
|
-
throw new
|
|
62
|
+
throw new CompileRefusal('BPMN_FORM_NO_OUTCOMES', `${where}: the form needs at least one outcome, e.g. outcomes: ['Approve', 'Reject'].`);
|
|
62
63
|
}
|
|
63
64
|
const ids = new Set();
|
|
64
65
|
for (const f of form.fields) {
|
|
65
66
|
if (typeof f.id !== 'string' || f.id === '')
|
|
66
|
-
throw new
|
|
67
|
+
throw new CompileRefusal('BPMN_FORM_FIELD_NO_ID', `${where}: every form field needs an id.`);
|
|
67
68
|
if (ids.has(f.id))
|
|
68
|
-
throw new
|
|
69
|
+
throw new CompileRefusal('BPMN_FORM_DUP_FIELD', `${where}: form field id "${f.id}" is used twice.`);
|
|
69
70
|
ids.add(f.id);
|
|
70
71
|
if (f.id === OUTCOME_KEY) {
|
|
71
|
-
throw new
|
|
72
|
+
throw new CompileRefusal('BPMN_FORM_FIELD_OUTCOME_COLLISION', `${where}: form field "${OUTCOME_KEY}" collides with the response's own "${OUTCOME_KEY}" — the outcome picked. Rename it, e.g. id: '${OUTCOME_KEY}Value'.`);
|
|
72
73
|
}
|
|
73
74
|
if (!IDENTIFIER.test(f.id)) {
|
|
74
|
-
throw new
|
|
75
|
+
throw new CompileRefusal('BPMN_FORM_FIELD_ID_NOT_IDENTIFIER', `${where}: form field "${f.id}" (${f.direction}) is not a name the runtime can read back as =${f.id}. ` +
|
|
75
76
|
`Use an identifier (letters, digits, _ or $), e.g. id: '${f.id.replace(/\W+(\w)?/g, (_m, c) => (c ?? '').toUpperCase())}'.`);
|
|
76
77
|
}
|
|
77
78
|
if (RESERVED_WORDS.has(f.id)) {
|
|
78
|
-
throw new
|
|
79
|
+
throw new CompileRefusal('BPMN_FORM_FIELD_ID_RESERVED', `${where}: form field "${f.id}" is a reserved word, which the runtime cannot bind as a name — it would lose every answer and the outcome. Rename it, e.g. id: '${f.id}Value'.`);
|
|
79
80
|
}
|
|
80
81
|
// The SDK's own spellings only. The product also resolves platform aliases such as
|
|
81
82
|
// `System.String`, but those are not `FormFieldType`s: accepting one here would let a
|
|
82
83
|
// decompile lift write source that does not type-check, so such an import stays verbatim.
|
|
83
84
|
if (!FORM_FIELD_TYPES.includes(String(f.type))) {
|
|
84
|
-
throw new
|
|
85
|
+
throw new CompileRefusal('BPMN_FORM_FIELD_TYPE_UNKNOWN', `${where}: form field "${f.id}" has type "${String(f.type)}". Valid: ${validFormFieldTypesText()}. ${formFieldTypeHint(f.type)}`.trimEnd());
|
|
85
86
|
}
|
|
86
87
|
// The checks Flow's form checker reports as errors: a picker the reviewer cannot
|
|
87
88
|
// pick from, and a setting the platform's schema refuses. (Options on a field that
|
|
88
89
|
// renders no picker is a warning there, and is not refused here.)
|
|
89
90
|
if (formFieldOptionsProblem(f) === 'missing') {
|
|
90
|
-
throw new
|
|
91
|
+
throw new CompileRefusal('BPMN_FORM_FIELD_NO_OPTIONS', `${where}: form field "${f.id}" ${formFieldOptionsWhy('missing', f.type)}. Add options: [{ id: 'o1', value: 'a', label: 'A' }, …].`);
|
|
91
92
|
}
|
|
92
93
|
const settings = formFieldSettingProblems(f);
|
|
93
94
|
if (settings.length)
|
|
94
|
-
throw new
|
|
95
|
+
throw new CompileRefusal('BPMN_FORM_FIELD_SETTING_INVALID', `${where}: form field "${f.id}" sets ${settings.join('; ')}.`);
|
|
95
96
|
if (isShown(f) && (typeof f.value !== 'string' || !f.value.startsWith('='))) {
|
|
96
|
-
throw new
|
|
97
|
+
throw new CompileRefusal('BPMN_FORM_FIELD_NO_VALUE', `${where}: form field "${f.id}" (${f.direction}) needs a value — a =-expression such as '=vars.${f.id}' — or the form shows it empty.`);
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
// One variable, one writer. Two answers landing in one variable, or an `outputs`
|
|
@@ -105,7 +106,7 @@ function validate(id, input) {
|
|
|
105
106
|
const claim = (variable, writer) => {
|
|
106
107
|
const first = writers.get(variable);
|
|
107
108
|
if (first !== undefined) {
|
|
108
|
-
throw new
|
|
109
|
+
throw new CompileRefusal('BPMN_FORM_DUP_VARIABLE_WRITER', `${where}: ${first} and ${writer} both write vars.${variable}, so one would overwrite the other. Give one a different variable.`);
|
|
109
110
|
}
|
|
110
111
|
writers.set(variable, writer);
|
|
111
112
|
};
|
|
@@ -120,9 +121,9 @@ function validate(id, input) {
|
|
|
120
121
|
form.outcomes.forEach((o, i) => {
|
|
121
122
|
const name = typeof o === 'string' ? o : o?.name;
|
|
122
123
|
if (typeof name !== 'string' || name.trim() === '')
|
|
123
|
-
throw new
|
|
124
|
+
throw new CompileRefusal('BPMN_FORM_OUTCOME_NO_NAME', `${where}: every outcome needs a name.`);
|
|
124
125
|
if (names.has(name))
|
|
125
|
-
throw new
|
|
126
|
+
throw new CompileRefusal('BPMN_FORM_DUP_OUTCOME', `${where}: outcome "${name}" is listed twice.`);
|
|
126
127
|
names.add(name);
|
|
127
128
|
// The schema's outcome ids must be unique too, and two names can reach one: an
|
|
128
129
|
// explicit `id` repeated, or `Needs review` and `Needs-review`, which both derive
|
|
@@ -130,7 +131,7 @@ function validate(id, input) {
|
|
|
130
131
|
const outcomeId = outcomeIdOf(o, i);
|
|
131
132
|
const clash = outcomeIds.get(outcomeId);
|
|
132
133
|
if (clash !== undefined) {
|
|
133
|
-
throw new
|
|
134
|
+
throw new CompileRefusal('BPMN_FORM_DUP_OUTCOME_ID', `${where}: outcomes "${clash}" and "${name}" both have the id "${outcomeId}". Give one an explicit, different id: { name: '${name}', id: '…' }.`);
|
|
134
135
|
}
|
|
135
136
|
outcomeIds.set(outcomeId, name);
|
|
136
137
|
});
|