@uipath/maestro-builder-sdk 6.11.5 → 6.12.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/api-index.md +83 -56
- package/dist/api-members.md +362 -261
- package/dist/bpmn/bpmn-expr-check.js +8 -1
- package/dist/bpmn/bpmn-sdk.d.ts +1026 -27
- package/dist/bpmn/bpmn-sdk.js +1328 -83
- package/dist/bpmn/check-cli.js +19 -4
- package/dist/bpmn/check.d.ts +1 -1
- package/dist/bpmn/check.js +529 -13
- package/dist/bpmn/connector.d.ts +2 -2
- package/dist/bpmn/connector.js +74 -37
- package/dist/bpmn/data-service.d.ts +124 -0
- package/dist/bpmn/data-service.js +98 -0
- package/dist/bpmn/decompile-cli.js +70 -8
- package/dist/bpmn/decompile.d.ts +71 -0
- package/dist/bpmn/decompile.js +845 -31
- package/dist/bpmn/graph.d.ts +69 -0
- package/dist/bpmn/graph.js +98 -0
- package/dist/bpmn/intsvc.js +2 -2
- package/dist/bpmn/known-methods.d.ts +15 -0
- package/dist/bpmn/known-methods.js +271 -0
- package/dist/bpmn/lift-verify.d.ts +40 -0
- package/dist/bpmn/lift-verify.js +133 -0
- package/dist/bpmn/serialize.d.ts +65 -1
- package/dist/bpmn/serialize.js +169 -22
- package/dist/bpmn/typed-node.d.ts +37 -6
- package/dist/bpmn/typed-node.js +81 -19
- package/dist/check-cli.js +2 -1
- package/dist/check.d.ts +13 -0
- package/dist/check.js +11 -0
- package/dist/cli/commands/bpmn.js +7 -1
- package/dist/cli-library.d.ts +7 -0
- package/dist/cli-library.js +19 -0
- package/dist/compile-cli.js +8 -5
- package/dist/core/byoa.d.ts +69 -0
- package/dist/core/byoa.js +98 -0
- package/dist/core/library.d.ts +23 -0
- package/dist/core/library.js +47 -1
- package/dist/core/lookup-store.d.ts +12 -0
- package/dist/core/lookup-store.js +25 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -0
- package/dist/registry/generators/descriptors.d.ts +1 -1
- package/dist/registry/generators/descriptors.js +20 -2
- package/dist/registry/generators/markdown/page.js +3 -1
- package/dist/registry/generators/markdown/sections/event-facts.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/event-facts.js +45 -0
- package/dist/registry/library-build/events.d.ts +32 -3
- package/dist/registry/library-build/events.js +48 -19
- package/dist/registry/prepare/byoa-ledger.d.ts +12 -0
- package/dist/registry/prepare/byoa-ledger.js +26 -0
- package/dist/registry/prepare/event.d.ts +5 -1
- package/dist/registry/prepare/event.js +22 -8
- package/dist/registry/prepare/messages.d.ts +2 -0
- package/dist/registry/prepare/messages.js +4 -0
- package/dist/serialize.js +24 -1
- package/package.json +2 -2
package/dist/bpmn/bpmn-sdk.js
CHANGED
|
@@ -2,11 +2,105 @@ import { connector as makeConnector } from '../core/actions.js';
|
|
|
2
2
|
import { checkBpmnExpressions, BpmnBuildError } from './bpmn-expr-check.js';
|
|
3
3
|
import { BPMN_SCHEMA_VERSIONS } from './format-profile.js';
|
|
4
4
|
import { schemaVersionRefusal } from '../schema-version.js';
|
|
5
|
-
import { bindingRequiredFields, REGISTRY_GAPS, typedNodeOutputVar } from './typed-node.js';
|
|
5
|
+
import { bindingRequiredFields, REGISTRY_GAPS, registryType, typedNodeOutputVar } from './typed-node.js';
|
|
6
|
+
import { DATA_SERVICE_ACTIVITY_TYPE, DATA_SERVICE_CONNECTOR_KEY, dataServiceWire } from './data-service.js';
|
|
6
7
|
export { BpmnBuildError } from './bpmn-expr-check.js';
|
|
7
8
|
function normTimer(t) {
|
|
8
9
|
return typeof t === 'string' ? { duration: t } : t;
|
|
9
10
|
}
|
|
11
|
+
/** Bookkeeping the nesting sugar shares across every scope of one process. */
|
|
12
|
+
class SugarRegistry {
|
|
13
|
+
/** Flow ids the builder wrote by implication rather than from an explicit `.sequenceFlow()`. */
|
|
14
|
+
impliedFlowIds = new Set();
|
|
15
|
+
/**
|
|
16
|
+
* Ids to give implied edges, keyed `source\u0000target` — see {@link ScopeBuilder.flowIds}.
|
|
17
|
+
*
|
|
18
|
+
* An implied edge has no call of its own, so there is nowhere to name it and its id
|
|
19
|
+
* is derived. This is the one place an author (in practice, `decompile`) can say what
|
|
20
|
+
* a derived id should be instead.
|
|
21
|
+
*/
|
|
22
|
+
pinnedFlowIds = new Map();
|
|
23
|
+
/** Variables an `errorVar` asked for — declared at build unless the author declared the id. */
|
|
24
|
+
autoVars = [];
|
|
25
|
+
}
|
|
26
|
+
const GATEWAY_NODE_KINDS = new Set(['exclusiveGateway', 'parallelGateway', 'inclusiveGateway', 'eventBasedGateway']);
|
|
27
|
+
/**
|
|
28
|
+
* Whether the node serializes to a start event — nothing may flow INTO it.
|
|
29
|
+
*
|
|
30
|
+
* Exported because `graph.ts` needs the SAME answer: `check --graph` decides what is
|
|
31
|
+
* `[NO INCOMING]` and what is merely an entry point, and a second copy of this test
|
|
32
|
+
* drifted — it missed the `typed` arm, so a registry type whose element IS a start
|
|
33
|
+
* event was reported unreachable.
|
|
34
|
+
*
|
|
35
|
+
* @param n - The node.
|
|
36
|
+
* @returns True when the node is an entry point of its own.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function isStartLike(n) {
|
|
40
|
+
if (n.kind === 'startEvent')
|
|
41
|
+
return true;
|
|
42
|
+
if (n.kind === 'connectorEvent')
|
|
43
|
+
return n.subscribe === 'trigger';
|
|
44
|
+
if (n.kind !== 'typed')
|
|
45
|
+
return false;
|
|
46
|
+
try {
|
|
47
|
+
return registryType(n.type, n.spec).element === 'bpmn:StartEvent';
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Whether a pending exit may be wired into this node when it is placed. */
|
|
54
|
+
function isContinuationTarget(n) {
|
|
55
|
+
if (isStartLike(n) || n.kind === 'boundaryEvent')
|
|
56
|
+
return false;
|
|
57
|
+
if (n.kind === 'subProcess' && n.triggeredByEvent)
|
|
58
|
+
return false;
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Whether, in a `sequence` scope, the path continues out of this node to the next
|
|
63
|
+
* one placed. An end event ends it; a gateway's fan-out is never implied; a boundary
|
|
64
|
+
* event and an event sub-process start their own paths.
|
|
65
|
+
*/
|
|
66
|
+
function opensPath(n) {
|
|
67
|
+
if (n.kind === 'endEvent' || n.kind === 'boundaryEvent' || GATEWAY_NODE_KINDS.has(n.kind))
|
|
68
|
+
return false;
|
|
69
|
+
if (n.kind === 'subProcess' && n.triggeredByEvent)
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
/** Whether the node is something an event-based gateway can wait on. */
|
|
74
|
+
function waitsForEvent(n) {
|
|
75
|
+
if (n.kind === 'intermediateCatchEvent')
|
|
76
|
+
return true;
|
|
77
|
+
if (n.kind === 'connectorEvent')
|
|
78
|
+
return n.subscribe === 'event';
|
|
79
|
+
if (n.kind !== 'typed')
|
|
80
|
+
return false;
|
|
81
|
+
try {
|
|
82
|
+
const element = registryType(n.type, n.spec).element;
|
|
83
|
+
return element === 'bpmn:IntermediateCatchEvent' || element === 'bpmn:ReceiveTask';
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The product's error-capture row: an output reading `=Error` into a variable.
|
|
91
|
+
*
|
|
92
|
+
* Which extension carries it depends on the element. A boundary event takes a
|
|
93
|
+
* `uipath:activity`, as every error boundary in the real exports does; a start event
|
|
94
|
+
* cannot (`uip maestro bpmn validate`: *"uipath:Activity cannot be attached to
|
|
95
|
+
* bpmn:startEvent"*), so an event sub-process's error start carries the row on a
|
|
96
|
+
* `uipath:mapping` instead — the same element a start event's input mapping uses.
|
|
97
|
+
*/
|
|
98
|
+
function errorCapturePayload(varId, on) {
|
|
99
|
+
return {
|
|
100
|
+
type: on === 'boundaryEvent' ? 'uipath:Activity' : 'BPMN.Variables',
|
|
101
|
+
outputRows: [{ name: 'Error', type: 'jsonSchema', var: varId, source: '=Error' }],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
10
104
|
// ─── Definitions registry (shared across the top process + sub-processes) ─────
|
|
11
105
|
/**
|
|
12
106
|
* Collects `uipath:binding` declarations, de-duped by id.
|
|
@@ -95,13 +189,34 @@ function uniqueId(prefix, index) {
|
|
|
95
189
|
* element id (`Error_1`) — could never match a real failure, so the handler was
|
|
96
190
|
* unreachable. Passing `{ name, code }` still sets them independently.
|
|
97
191
|
*/
|
|
98
|
-
function toErrorDef(defs, error) {
|
|
192
|
+
function toErrorDef(defs, error, where) {
|
|
99
193
|
// `true` is catch-any: no `bpmn:error` is declared and no `errorRef` is written.
|
|
100
194
|
if (error === true)
|
|
101
195
|
return { type: 'error' };
|
|
102
196
|
const spec = typeof error === 'string' ? { name: error } : error;
|
|
197
|
+
// A BLANK name declares a `bpmn:error` with nothing to match on, which the platform
|
|
198
|
+
// rejects (ERROR_BOUNDARY_EVENT_EMPTY_ERROR_REF on a boundary,
|
|
199
|
+
// ERROR_END_EVENT_MISSING_EXCEPTION on an end event) after a whole toolchain cycle.
|
|
200
|
+
// `error: true` is the way to say "any error"; a name is the way to say which.
|
|
201
|
+
if (spec.name.trim() === '') {
|
|
202
|
+
throw new Error(`${where}: the error name is empty, so the declared \`bpmn:error\` has nothing to match on. ` +
|
|
203
|
+
'Pass `error: true` to catch any error, or a name — `error: "PaymentFailed"`, or ' +
|
|
204
|
+
'`error: { name: "PaymentFailed", code: "PAYMENT_FAILED" }`.');
|
|
205
|
+
}
|
|
103
206
|
return { type: 'error', errorRef: defs.errorRef(spec.name, spec.code ?? spec.name) };
|
|
104
207
|
}
|
|
208
|
+
/** The error a boundary event or event sub-process catches: its ref, or catch-all. */
|
|
209
|
+
function errorCaught(n) {
|
|
210
|
+
const event = 'event' in n ? n.event : undefined;
|
|
211
|
+
if (event?.type !== 'error')
|
|
212
|
+
return undefined;
|
|
213
|
+
return { catchAll: event.errorRef === undefined, ref: event.errorRef };
|
|
214
|
+
}
|
|
215
|
+
/** The single start event of a built sub-process scope, when it has exactly one. */
|
|
216
|
+
function loneStart(nodes) {
|
|
217
|
+
const starts = nodes.filter((n) => n.kind === 'startEvent');
|
|
218
|
+
return starts.length === 1 ? starts[0] : undefined;
|
|
219
|
+
}
|
|
105
220
|
// ─── Graph scope (shared by the top process and sub-processes) ────────────────
|
|
106
221
|
/**
|
|
107
222
|
* The set of node/flow/variable methods common to the process root and every
|
|
@@ -110,11 +225,11 @@ function toErrorDef(defs, error) {
|
|
|
110
225
|
*/
|
|
111
226
|
class ScopeBuilder {
|
|
112
227
|
/** @internal */
|
|
113
|
-
_nodes
|
|
228
|
+
_nodes;
|
|
114
229
|
/** @internal */
|
|
115
|
-
_flows
|
|
230
|
+
_flows;
|
|
116
231
|
/** @internal */
|
|
117
|
-
_vars
|
|
232
|
+
_vars;
|
|
118
233
|
// An explicit field, not a constructor parameter property: an inline comment
|
|
119
234
|
// on a parameter property never reaches the emitted `.d.ts` property, so the
|
|
120
235
|
// `@internal` tag would be lost there.
|
|
@@ -122,18 +237,428 @@ class ScopeBuilder {
|
|
|
122
237
|
_defs;
|
|
123
238
|
/** @internal */
|
|
124
239
|
_bindingRegistry;
|
|
240
|
+
/** @internal */
|
|
241
|
+
_storage;
|
|
242
|
+
/** @internal */
|
|
243
|
+
_sugar;
|
|
244
|
+
/** @internal How THIS scope wires siblings — an arm's is always `sequence`. */
|
|
245
|
+
_mode;
|
|
246
|
+
/** @internal The mode of the real scope this builder writes into, which a nested `.subProcess()` inherits. */
|
|
247
|
+
_scopeMode;
|
|
248
|
+
/** @internal Whether this builder is an arm or handler path rather than a real scope. */
|
|
249
|
+
_isArm = false;
|
|
250
|
+
/** @internal The id of a start event the builder synthesized for this scope (an event sub-process body). */
|
|
251
|
+
_synthesizedStart;
|
|
252
|
+
/** @internal Elements placed in a `sequence` scope with nothing leading into them, checked at close. */
|
|
253
|
+
_unattached = [];
|
|
254
|
+
/** @internal Edges waiting for the element that continues them. */
|
|
255
|
+
_open = [];
|
|
256
|
+
/** @internal Whether anything has been placed yet, so an unreachable element can be told from the first. */
|
|
257
|
+
_started = false;
|
|
258
|
+
/** @internal What left this path for good (`.goto()`), after which nothing may follow. */
|
|
259
|
+
_closedBy;
|
|
125
260
|
/**
|
|
126
261
|
* @param defs - Definitions-level message/error declarations, shared with every scope.
|
|
127
262
|
* @param bindings - `uipath:binding` declarations, shared so a typed node can
|
|
128
263
|
* declare one from inside a sub-process.
|
|
264
|
+
* @param storage - The arrays to write into; an arm shares its enclosing scope's,
|
|
265
|
+
* a real scope gets its own.
|
|
266
|
+
* @param mode - How siblings no explicit flow covers are wired — see `WiringMode`.
|
|
267
|
+
* @param sugar - Process-wide bookkeeping for the nesting sugar.
|
|
129
268
|
* @returns The scope builder.
|
|
130
269
|
*/
|
|
131
|
-
constructor(defs, bindings) {
|
|
270
|
+
constructor(defs, bindings, storage, mode = 'explicit', sugar = new SugarRegistry()) {
|
|
132
271
|
this._defs = defs;
|
|
133
272
|
this._bindingRegistry = bindings;
|
|
273
|
+
this._storage = storage ?? { nodes: [], flows: [], vars: [], mustClose: [] };
|
|
274
|
+
this._nodes = this._storage.nodes;
|
|
275
|
+
this._flows = this._storage.flows;
|
|
276
|
+
this._vars = this._storage.vars;
|
|
277
|
+
this._mode = mode;
|
|
278
|
+
this._scopeMode = mode;
|
|
279
|
+
this._sugar = sugar;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Choose how this scope wires the elements no explicit `.sequenceFlow()` covers.
|
|
283
|
+
*
|
|
284
|
+
* @remarks
|
|
285
|
+
* `'explicit'` is the default: siblings are not wired to each other, only the exits
|
|
286
|
+
* of a nested construct continue to the next element. `'sequence'` wires consecutive
|
|
287
|
+
* siblings in order, so a linear process needs no `.sequenceFlow()` at all.
|
|
288
|
+
*
|
|
289
|
+
* Three rules keep `'sequence'` local and reviewable. **Explicit wins**: an element
|
|
290
|
+
* that already has an explicit outgoing flow is not also wired to the next one, so a
|
|
291
|
+
* hand-written edge never gains a silent twin. **A bare gateway implies nothing
|
|
292
|
+
* outward**: it is wired into, but its outgoing flows are yours to write (or use
|
|
293
|
+
* `.choose()` / `.fork()` / `.race()`, which imply theirs). **Nothing dangles
|
|
294
|
+
* silently**: an element nothing leads into, or a path that does not end, is refused
|
|
295
|
+
* at `.build()` rather than emitted. `bpmn check --graph` prints the wiring that
|
|
296
|
+
* resulted, implied edges marked, so a change can be reviewed against what was
|
|
297
|
+
* inferred rather than what was typed.
|
|
298
|
+
*
|
|
299
|
+
* Must come before the scope's first element. A `.subProcess()` body inherits its
|
|
300
|
+
* enclosing scope's mode and may set its own; an arm or handler body is always
|
|
301
|
+
* `sequence` and refuses the call.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* **A linear process with no sequence flows written**
|
|
305
|
+
* ```ts
|
|
306
|
+
* bpmn('intake')
|
|
307
|
+
* .flowMode('sequence')
|
|
308
|
+
* .var('note', 'string', { default: '' })
|
|
309
|
+
* .startEvent('start')
|
|
310
|
+
* .task('assign', { set: { note: 'hello' } })
|
|
311
|
+
* .scriptTask('log', { script: 'return note;', inputs: { note: '=vars.note' } })
|
|
312
|
+
* .endEvent('done')
|
|
313
|
+
* .build();
|
|
314
|
+
* ```
|
|
315
|
+
*
|
|
316
|
+
* @param mode - `'explicit'` or `'sequence'`.
|
|
317
|
+
* @returns This builder, so calls chain.
|
|
318
|
+
* @throws If called inside an arm or handler body, or after an element has been placed.
|
|
319
|
+
*/
|
|
320
|
+
flowMode(mode) {
|
|
321
|
+
if (this._isArm) {
|
|
322
|
+
throw new Error(`.flowMode('${mode}') is not available inside an arm or handler body — those always chain in order.`);
|
|
323
|
+
}
|
|
324
|
+
if (this._nodes.length > 0) {
|
|
325
|
+
throw new Error(`.flowMode('${mode}') must come before the scope's first element; "${this._nodes[0].id}" is already placed.`);
|
|
326
|
+
}
|
|
327
|
+
this._mode = mode;
|
|
328
|
+
this._scopeMode = mode;
|
|
329
|
+
return this;
|
|
134
330
|
}
|
|
135
331
|
push(node) {
|
|
332
|
+
this.place(node);
|
|
333
|
+
return this;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Place an activity and run its body, in which boundary events are declared.
|
|
337
|
+
*
|
|
338
|
+
* @param node - The activity node.
|
|
339
|
+
* @param body - Its boundary handlers, if any.
|
|
340
|
+
* @returns This builder, so calls chain.
|
|
341
|
+
*/
|
|
342
|
+
pushActivity(node, body) {
|
|
343
|
+
this.place(node);
|
|
344
|
+
if (body)
|
|
345
|
+
body(new ActivityBuilder(this, node.id));
|
|
346
|
+
return this;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Place a node: wire every pending exit into it, record it, and — in a
|
|
350
|
+
* `sequence` scope — leave it as the open end the next element continues from.
|
|
351
|
+
*
|
|
352
|
+
* @param node - The node to place.
|
|
353
|
+
* @param viaSugar - Set when the builder itself is placing a gateway it synthesized,
|
|
354
|
+
* which a `sequence` scope otherwise refuses.
|
|
355
|
+
* @internal
|
|
356
|
+
*/
|
|
357
|
+
place(node, viaSugar = false) {
|
|
358
|
+
if (this._closedBy !== undefined) {
|
|
359
|
+
throw new Error(`"${node.id}" follows ${this._closedBy}, which already left this path — nothing can come after it.`);
|
|
360
|
+
}
|
|
361
|
+
if (this._mode === 'sequence') {
|
|
362
|
+
if (isStartLike(node)) {
|
|
363
|
+
if (this._isArm) {
|
|
364
|
+
throw new Error(`startEvent "${node.id}": a body has no start event of its own — it begins where the enclosing element hands over to it.`);
|
|
365
|
+
}
|
|
366
|
+
if (this._synthesizedStart !== undefined) {
|
|
367
|
+
throw new Error(`startEvent "${node.id}": an event sub-process has exactly one start event, and this one's is ` +
|
|
368
|
+
`synthesized as "${this._synthesizedStart}" — write what follows it instead.`);
|
|
369
|
+
}
|
|
370
|
+
// Explicit wins here as everywhere: a plain exit whose element already has an
|
|
371
|
+
// explicit outgoing flow was wired by hand and is not open — the same test
|
|
372
|
+
// `continueTo` applies before implying an edge and `closeScope` applies at the
|
|
373
|
+
// end. Only an exit nothing continues, or one carrying a condition or default
|
|
374
|
+
// (a branch with nowhere to go), blocks a new chain.
|
|
375
|
+
const dangling = this._open.filter((e) => e.condition !== undefined || e.defaultOf !== undefined || !this.hasExplicitOutgoing(e.source));
|
|
376
|
+
if (dangling.length > 0) {
|
|
377
|
+
throw new Error(`startEvent "${node.id}" begins a new chain while "${dangling.map((e) => e.source).join('", "')}" is still ` +
|
|
378
|
+
`open — end the current path with an end event before starting another.`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// Inside an arm a bare gateway is refused outright: its fan-out cannot be implied
|
|
382
|
+
// and nothing after it could be reached. In a real scope it is allowed — it is
|
|
383
|
+
// wired INTO, and its outgoing flows are the author's to write — and whatever
|
|
384
|
+
// follows it is checked at close for an explicit incoming edge.
|
|
385
|
+
if (GATEWAY_NODE_KINDS.has(node.kind) && !viaSugar && this._isArm) {
|
|
386
|
+
throw new Error(`${node.kind} "${node.id}": inside a body, branch with .choose() / .fork() / .race() — ` +
|
|
387
|
+
`a bare gateway's outgoing flows cannot be implied.`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const target = isContinuationTarget(node);
|
|
391
|
+
if (target) {
|
|
392
|
+
if (this._mode === 'sequence' && this._started && this._open.length === 0) {
|
|
393
|
+
if (this._isArm) {
|
|
394
|
+
throw new Error(`"${node.id}" is unreachable: the path ended before it (an end event, or every arm of a branch), so nothing leads to it.`);
|
|
395
|
+
}
|
|
396
|
+
// Placed with nothing implied into it. Legal only if an explicit flow leads
|
|
397
|
+
// here — which may be written later — so it is checked when the scope closes.
|
|
398
|
+
const previous = this._nodes[this._nodes.length - 1];
|
|
399
|
+
this._unattached.push({ id: node.id, after: previous ? previous.id : '' });
|
|
400
|
+
}
|
|
401
|
+
this.continueTo(node.id);
|
|
402
|
+
}
|
|
403
|
+
this._nodes.push(node);
|
|
404
|
+
this._started = true;
|
|
405
|
+
if (this._mode === 'sequence' && opensPath(node))
|
|
406
|
+
this._open = [{ source: node.id }];
|
|
407
|
+
else if (target)
|
|
408
|
+
this._open = [];
|
|
409
|
+
}
|
|
410
|
+
/** Wire every pending exit into `target`, except one the author already wired by hand. */
|
|
411
|
+
continueTo(target) {
|
|
412
|
+
for (const exit of this._open) {
|
|
413
|
+
// Explicit wins: a plain exit whose element already has an explicit outgoing
|
|
414
|
+
// flow was wired by hand, and an implied edge beside it would be a second,
|
|
415
|
+
// silent fan-out. An exit that CARRIES something — a condition, a default —
|
|
416
|
+
// is an edge in its own right and is always written.
|
|
417
|
+
if (exit.condition === undefined && exit.defaultOf === undefined && this.hasExplicitOutgoing(exit.source))
|
|
418
|
+
continue;
|
|
419
|
+
this.writeImpliedFlow(exit, target);
|
|
420
|
+
}
|
|
421
|
+
this._open = [];
|
|
422
|
+
}
|
|
423
|
+
hasExplicitOutgoing(source) {
|
|
424
|
+
return this._flows.some((f) => f.source === source && !this._sugar.impliedFlowIds.has(f.id));
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Write the flow a pending exit stands for, now that its target is known.
|
|
428
|
+
*
|
|
429
|
+
* @param exit - The exit.
|
|
430
|
+
* @param target - The element it flows into.
|
|
431
|
+
* @internal
|
|
432
|
+
*/
|
|
433
|
+
writeImpliedFlow(exit, target) {
|
|
434
|
+
// An id the author pinned for this pair wins over the derived one. Nothing else
|
|
435
|
+
// changes: the edge is still implied, still counted as implied, and a gateway
|
|
436
|
+
// `default` still ends up naming whichever id the edge got.
|
|
437
|
+
let id = this._sugar.pinnedFlowIds.get(`${exit.source}\u0000${target}`) ?? `Flow_${exit.source}_${target}`;
|
|
438
|
+
const existing = this._flows.find((f) => f.id === id);
|
|
439
|
+
if (existing && existing.source === exit.source && existing.target === target) {
|
|
440
|
+
// The same edge, already there — an author's explicit restatement, or two
|
|
441
|
+
// exits converging on one target from one source. One edge, not two.
|
|
442
|
+
if (exit.defaultOf)
|
|
443
|
+
exit.defaultOf.default = id;
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (existing) {
|
|
447
|
+
// A DIFFERENT edge already owns that id. `Flow_<source>_<target>` is ambiguous
|
|
448
|
+
// the moment an element id contains an underscore: `a_b → c` and `a → b_c` both
|
|
449
|
+
// spell `Flow_a_b_c`. Left alone the second edge ships with a duplicate id and
|
|
450
|
+
// `check` reports DUPLICATE_DOCUMENT_ID for a flow the author never named and,
|
|
451
|
+
// because the edge is implied, has no way to rename. So the id is suffixed until
|
|
452
|
+
// it is free. An author who wants a specific id still writes `.sequenceFlow()`.
|
|
453
|
+
let n = 2;
|
|
454
|
+
while (this._flows.some((f) => f.id === `${id}_${n}`))
|
|
455
|
+
n++;
|
|
456
|
+
id = `${id}_${n}`;
|
|
457
|
+
}
|
|
458
|
+
this._flows.push({ id, source: exit.source, target, name: exit.name, condition: exit.condition, implied: true });
|
|
459
|
+
this._sugar.impliedFlowIds.add(id);
|
|
460
|
+
if (exit.defaultOf)
|
|
461
|
+
exit.defaultOf.default = id;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Check the implied wiring of a real scope once its author's function has
|
|
465
|
+
* returned: every path a handler had to end has ended, and every exit a construct
|
|
466
|
+
* left open reached something.
|
|
467
|
+
*
|
|
468
|
+
* @param what - The scope, for the message.
|
|
469
|
+
* @internal
|
|
470
|
+
*/
|
|
471
|
+
closeScope(what) {
|
|
472
|
+
// The duplicate-error refusals again, over the FINISHED node list.
|
|
473
|
+
//
|
|
474
|
+
// `_attach` and `.eventSubProcess()` catch the collision at the call site, which is
|
|
475
|
+
// where the best message lives, but only for the sugar: `.boundaryEvent()` and a
|
|
476
|
+
// hand-rolled `.subProcess(…, { triggeredByEvent: true })` never go through them, so
|
|
477
|
+
// two explicitly-written handlers on one activity, or two hand-rolled nets in one
|
|
478
|
+
// scope, reached `validate`. Order mattered too — the call-site scan sees earlier
|
|
479
|
+
// nodes only. Repeating the test here costs one pass and covers every spelling and
|
|
480
|
+
// both orders.
|
|
481
|
+
const seenOnActivity = new Map();
|
|
482
|
+
const seenNets = [];
|
|
483
|
+
for (const n of this._nodes) {
|
|
484
|
+
const here = n.kind === 'boundaryEvent'
|
|
485
|
+
? { key: n.attachedTo, list: seenOnActivity.get(n.attachedTo) ?? [] }
|
|
486
|
+
: n.kind === 'subProcess' && n.triggeredByEvent === true
|
|
487
|
+
? { key: undefined, list: seenNets }
|
|
488
|
+
: undefined;
|
|
489
|
+
if (!here)
|
|
490
|
+
continue;
|
|
491
|
+
const start = n.kind === 'subProcess' ? loneStart(n.nodes) : undefined;
|
|
492
|
+
const caught = n.kind === 'boundaryEvent' ? errorCaught(n) : start === undefined ? undefined : errorCaught(start);
|
|
493
|
+
if (caught === undefined)
|
|
494
|
+
continue;
|
|
495
|
+
const clash = here.list.find((o) => o.catchAll === caught.catchAll && o.ref === caught.ref);
|
|
496
|
+
if (clash !== undefined) {
|
|
497
|
+
const where = here.key !== undefined ? `on "${here.key}"` : 'in this scope';
|
|
498
|
+
throw new Error(`"${n.id}" catches the same error as "${clash.id}" ${where} ` +
|
|
499
|
+
`(${caught.catchAll ? 'both catch ANY error' : `both catch "${caught.ref}"`}), so one of the two can ` +
|
|
500
|
+
'never run. Catch a specific error in each, or keep the one catch-all.');
|
|
501
|
+
}
|
|
502
|
+
here.list.push({ id: n.id, catchAll: caught.catchAll, ref: caught.ref });
|
|
503
|
+
if (here.key !== undefined)
|
|
504
|
+
seenOnActivity.set(here.key, here.list);
|
|
505
|
+
}
|
|
506
|
+
for (const { id, after } of this._unattached) {
|
|
507
|
+
if (!this._flows.some((f) => f.target === id)) {
|
|
508
|
+
throw new Error(`"${id}" has no incoming flow. In a sequence scope it was placed after "${after}", which does not ` +
|
|
509
|
+
`continue to it (a bare gateway's outgoing flows are never implied; an end event ends the path). ` +
|
|
510
|
+
`Wire it with .sequenceFlow(…, "${id}"), or place it after an element that continues.`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
for (const { id, reason } of this._storage.mustClose) {
|
|
514
|
+
if (!this._flows.some((f) => f.source === id)) {
|
|
515
|
+
throw new Error(`${reason} ends at "${id}", which has no outgoing flow. A non-interrupting handler runs beside a token ` +
|
|
516
|
+
`that will reach the next statement anyway, so its path cannot rejoin there: end it with an end event, ` +
|
|
517
|
+
`leave it with .goto(), or wire it with .sequenceFlow("${id}", …).`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
this._storage.mustClose.length = 0;
|
|
521
|
+
for (const exit of this._open) {
|
|
522
|
+
const who = exit.origin ?? what;
|
|
523
|
+
if (exit.condition !== undefined || exit.defaultOf !== undefined) {
|
|
524
|
+
throw new Error(`${who} has nowhere to go: nothing follows the branch in ${what}. Add the element that comes ` +
|
|
525
|
+
`next, or end every arm inside its own body.`);
|
|
526
|
+
}
|
|
527
|
+
if (!this._flows.some((f) => f.source === exit.source)) {
|
|
528
|
+
throw new Error(`${who} ends at "${exit.source}", which has no outgoing flow, and nothing follows it in ${what} ` +
|
|
529
|
+
`to continue it. Add the element that comes next, end the path with an end event, or wire it with ` +
|
|
530
|
+
`.sequenceFlow("${exit.source}", …).`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
this._open = [];
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* The definitions registry, for the activity builder to intern a message or error.
|
|
537
|
+
*
|
|
538
|
+
* @returns The registry shared by every scope of this process.
|
|
539
|
+
* @internal
|
|
540
|
+
*/
|
|
541
|
+
_defsOf() {
|
|
542
|
+
return this._defs;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* This scope's variable declarations, for an `errorVar` to be declared into.
|
|
546
|
+
*
|
|
547
|
+
* @returns The array the scope declares variables into.
|
|
548
|
+
* @internal
|
|
549
|
+
*/
|
|
550
|
+
_varsOf() {
|
|
551
|
+
return this._vars;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Attach a boundary event to an activity of this scope and run its handler path.
|
|
555
|
+
*
|
|
556
|
+
* @param activityId - The activity the boundary attaches to.
|
|
557
|
+
* @param suffix - `onError` / `onTimer` / `onMessage`, for the derived id.
|
|
558
|
+
* @param spec - The event definition and the options it came with.
|
|
559
|
+
* @param body - The handler path.
|
|
560
|
+
* @internal
|
|
561
|
+
*/
|
|
562
|
+
_attach(activityId, suffix, spec, body) {
|
|
563
|
+
const { opts } = spec;
|
|
564
|
+
const id = opts.id ?? `${activityId}_${suffix}`;
|
|
565
|
+
if (this._nodes.some((n) => n.id === id)) {
|
|
566
|
+
throw new Error(`"${activityId}" already has a boundary event "${id}"; a second .${suffix}() on one activity needs its own \`id\`.`);
|
|
567
|
+
}
|
|
568
|
+
if (opts.errorVar !== undefined && opts.payload !== undefined) {
|
|
569
|
+
throw new Error(`.${suffix}() on "${activityId}": pass \`errorVar\` or \`payload\`, not both — both write the event's output row.`);
|
|
570
|
+
}
|
|
571
|
+
// Two handlers on one activity catching the SAME error: whichever the runtime picks,
|
|
572
|
+
// the other is dead, and the platform rejects the pair outright
|
|
573
|
+
// (MULTIPLE_CATCH_ALL_BOUNDARY_EVENTS_ON_TASK for two catch-alls,
|
|
574
|
+
// DUPLICATE_ERROR_BOUNDARY_EVENT_ON_TASK for two of the same ref). Both handlers are
|
|
575
|
+
// in hand here, so this is a refusal rather than a diagnostic a rung later.
|
|
576
|
+
const caught = errorCaught({ kind: 'boundaryEvent', id, attachedTo: activityId, event: spec.event });
|
|
577
|
+
if (caught !== undefined) {
|
|
578
|
+
const clash = this._nodes.find((n) => {
|
|
579
|
+
if (n.kind !== 'boundaryEvent' || n.attachedTo !== activityId)
|
|
580
|
+
return false;
|
|
581
|
+
const other = errorCaught(n);
|
|
582
|
+
return other !== undefined && other.catchAll === caught.catchAll && other.ref === caught.ref;
|
|
583
|
+
});
|
|
584
|
+
if (clash !== undefined) {
|
|
585
|
+
throw new Error(`.${suffix}() on "${activityId}" catches the same error as "${clash.id}" ` +
|
|
586
|
+
`(${caught.catchAll ? 'both catch ANY error' : `both catch "${caught.ref}"`}), so one of the two can never ` +
|
|
587
|
+
'run. Catch a specific error in each handler, or keep the one catch-all.');
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const node = {
|
|
591
|
+
kind: 'boundaryEvent',
|
|
592
|
+
id,
|
|
593
|
+
name: opts.name,
|
|
594
|
+
attachedTo: activityId,
|
|
595
|
+
cancelActivity: spec.interrupting,
|
|
596
|
+
event: spec.event,
|
|
597
|
+
payload: opts.payload ?? (opts.errorVar !== undefined ? errorCapturePayload(opts.errorVar, 'boundaryEvent') : undefined),
|
|
598
|
+
};
|
|
599
|
+
// Straight into the scope, not through `place`: a boundary event is not a
|
|
600
|
+
// continuation of anything, and must not consume the exits waiting for one.
|
|
136
601
|
this._nodes.push(node);
|
|
602
|
+
if (opts.errorVar !== undefined)
|
|
603
|
+
this._sugar.autoVars.push({ id: opts.errorVar, vars: this._vars, elementId: id });
|
|
604
|
+
const arm = new PathBuilder(this._defs, this._bindingRegistry, this._storage, this._sugar, [{ source: id, origin: `the handler "${id}" on "${activityId}"` }], this._scopeMode);
|
|
605
|
+
body?.(arm);
|
|
606
|
+
const tails = arm._finish();
|
|
607
|
+
if (spec.interrupting) {
|
|
608
|
+
// The activity was cancelled, so the handler's path IS the continuation: it
|
|
609
|
+
// rejoins at the next statement, as the activity's own path would have.
|
|
610
|
+
this._open.push(...tails);
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
// The activity is still running and will reach the next statement itself;
|
|
614
|
+
// the handler's path must end on its own — checked when the scope closes,
|
|
615
|
+
// so an explicit flow written later still counts.
|
|
616
|
+
for (const t of tails)
|
|
617
|
+
this._storage.mustClose.push({ id: t.source, reason: `The non-interrupting handler "${id}" on "${activityId}"` });
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* The shared lowering of `choose` / `fork` / `race`: a splitting gateway, one
|
|
622
|
+
* arm per branch, and a join only when more than one arm falls through.
|
|
623
|
+
*
|
|
624
|
+
* @param split - The gateway that opens the construct.
|
|
625
|
+
* @param joinKind - The gateway kind that closes it, when one is needed.
|
|
626
|
+
* @param arms - Each arm's entry edge and body.
|
|
627
|
+
* @param what - The construct, for messages.
|
|
628
|
+
* @param mustWait - `race`: each arm has to begin with something that waits.
|
|
629
|
+
*/
|
|
630
|
+
branch(split, joinKind, arms, what, mustWait = false, join = {}) {
|
|
631
|
+
this.place(split, true);
|
|
632
|
+
const exits = [];
|
|
633
|
+
arms.forEach((arm, i) => {
|
|
634
|
+
const ab = new PathBuilder(this._defs, this._bindingRegistry, this._storage, this._sugar, [arm.entry], this._scopeMode);
|
|
635
|
+
arm.body?.(ab);
|
|
636
|
+
if (mustWait) {
|
|
637
|
+
const first = ab._first;
|
|
638
|
+
if (!first || !waitsForEvent(first)) {
|
|
639
|
+
throw new Error(`${what}: arm ${i + 1} must begin with what it waits for — an intermediateCatchEvent (message or timer), ` +
|
|
640
|
+
`a receiveMessage, or a waitForEvent — but begins with ${first ? `${first.kind} "${first.id}"` : 'nothing'}.`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
exits.push(...ab._finish());
|
|
644
|
+
});
|
|
645
|
+
const empties = exits.filter((e) => e.source === split.id);
|
|
646
|
+
if (empties.length > 1) {
|
|
647
|
+
throw new Error(`${what} has ${empties.length} arms with no body; at most one arm may be empty.`);
|
|
648
|
+
}
|
|
649
|
+
if (exits.length >= 2) {
|
|
650
|
+
// Only when more than one arm actually reaches the continuation: a single
|
|
651
|
+
// fall-through needs no join, and emitting one would be the SUPERFLUOUS_GATEWAY
|
|
652
|
+
// the construct exists to make unwritable.
|
|
653
|
+
const joinNode = { kind: joinKind, id: join.id ?? `${split.id}_join`, name: join.name };
|
|
654
|
+
for (const e of exits)
|
|
655
|
+
this.writeImpliedFlow(e, joinNode.id);
|
|
656
|
+
this._nodes.push(joinNode);
|
|
657
|
+
this._open = [{ source: joinNode.id, origin: `the join of ${what}` }];
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
this._open = exits;
|
|
661
|
+
}
|
|
137
662
|
return this;
|
|
138
663
|
}
|
|
139
664
|
// ── Events ──
|
|
@@ -162,17 +687,21 @@ class ScopeBuilder {
|
|
|
162
687
|
if (opts.trigger !== undefined && opts.payload !== undefined) {
|
|
163
688
|
throw new Error(`startEvent "${id}": \`trigger\` and \`payload\` both write the event's extension element; pass one.`);
|
|
164
689
|
}
|
|
690
|
+
if (opts.interrupting === false && opts.error !== undefined) {
|
|
691
|
+
throw new Error(`startEvent "${id}": an error start is always interrupting — the throw has already ended the scope it came from.`);
|
|
692
|
+
}
|
|
165
693
|
return this.push({
|
|
166
694
|
kind: 'startEvent',
|
|
167
695
|
id,
|
|
168
696
|
name: opts.name,
|
|
169
697
|
payload: opts.payload,
|
|
698
|
+
interrupting: opts.interrupting === false ? false : undefined,
|
|
170
699
|
// `error` is handled here rather than in `startCatchDef` because it needs the
|
|
171
700
|
// definitions registry to intern the `bpmn:error`, exactly as `endEvent` does —
|
|
172
701
|
// and because an intermediate catch event (the other `startCatchDef` caller)
|
|
173
702
|
// cannot carry one: BPMN catches errors at a boundary or an event sub-process
|
|
174
703
|
// start, never mid-flow.
|
|
175
|
-
event: opts.error !== undefined ? toErrorDef(this._defs, opts.error) : this.startCatchDef(opts),
|
|
704
|
+
event: opts.error !== undefined ? toErrorDef(this._defs, opts.error, `startEvent "${id}"`) : this.startCatchDef(opts),
|
|
176
705
|
trigger: opts.trigger === undefined
|
|
177
706
|
? undefined
|
|
178
707
|
: { type: 'Intsvc.TimerTrigger', outputVar: opts.trigger === true ? undefined : opts.trigger.outputVar },
|
|
@@ -190,7 +719,7 @@ class ScopeBuilder {
|
|
|
190
719
|
if (opts.terminate)
|
|
191
720
|
event = { type: 'terminate' };
|
|
192
721
|
else if (opts.error !== undefined)
|
|
193
|
-
event = toErrorDef(this._defs, opts.error);
|
|
722
|
+
event = toErrorDef(this._defs, opts.error, `endEvent "${id}"`);
|
|
194
723
|
else if (opts.message !== undefined)
|
|
195
724
|
event = { type: 'message', messageRef: this._defs.messageRef(opts.message) };
|
|
196
725
|
return this.push({ kind: 'endEvent', id, name: opts.name, event, payload: opts.payload });
|
|
@@ -232,7 +761,7 @@ class ScopeBuilder {
|
|
|
232
761
|
boundaryEvent(id, opts) {
|
|
233
762
|
let event;
|
|
234
763
|
if (opts.error !== undefined)
|
|
235
|
-
event = toErrorDef(this._defs, opts.error);
|
|
764
|
+
event = toErrorDef(this._defs, opts.error, `boundaryEvent "${id}"`);
|
|
236
765
|
else if (opts.timer !== undefined)
|
|
237
766
|
event = { type: 'timer', timer: normTimer(opts.timer) };
|
|
238
767
|
else if (opts.message !== undefined)
|
|
@@ -303,15 +832,17 @@ class ScopeBuilder {
|
|
|
303
832
|
*
|
|
304
833
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
305
834
|
* @param opts - The Jint JavaScript `script`, and its input/output mappings.
|
|
835
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
306
836
|
* @returns This builder, so calls chain.
|
|
307
837
|
*/
|
|
308
|
-
scriptTask(id, opts) {
|
|
309
|
-
return this.
|
|
838
|
+
scriptTask(id, opts, body) {
|
|
839
|
+
return this.pushActivity({
|
|
310
840
|
kind: 'scriptTask',
|
|
311
841
|
id,
|
|
312
842
|
name: opts.name,
|
|
313
843
|
script: opts.script,
|
|
314
844
|
scriptFormat: opts.scriptFormat ?? 'JavaScript',
|
|
845
|
+
scriptVersion: opts.scriptVersion ?? 'v3',
|
|
315
846
|
inputs: opts.inputs ?? {},
|
|
316
847
|
outputs: opts.outputs ?? {},
|
|
317
848
|
outputRows: opts.outputRows,
|
|
@@ -321,17 +852,18 @@ class ScopeBuilder {
|
|
|
321
852
|
retry: opts.retry,
|
|
322
853
|
errorMapping: opts.errorMapping,
|
|
323
854
|
loop: opts.loop,
|
|
324
|
-
});
|
|
855
|
+
}, body);
|
|
325
856
|
}
|
|
326
857
|
/**
|
|
327
858
|
* A plain task that assigns variables (`BPMN.Variables`).
|
|
328
859
|
*
|
|
329
860
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
330
861
|
* @param opts - `name`, and the variable assignments the task makes.
|
|
862
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
331
863
|
* @returns This builder, so calls chain.
|
|
332
864
|
*/
|
|
333
|
-
task(id, opts = {}) {
|
|
334
|
-
return this.
|
|
865
|
+
task(id, opts = {}, body) {
|
|
866
|
+
return this.pushActivity({
|
|
335
867
|
kind: 'task',
|
|
336
868
|
id,
|
|
337
869
|
name: opts.name,
|
|
@@ -340,7 +872,7 @@ class ScopeBuilder {
|
|
|
340
872
|
retry: opts.retry,
|
|
341
873
|
loop: opts.loop,
|
|
342
874
|
errorMapping: opts.errorMapping,
|
|
343
|
-
});
|
|
875
|
+
}, body);
|
|
344
876
|
}
|
|
345
877
|
/**
|
|
346
878
|
* A task element carrying NO `uipath:*` payload — an abstract task, in BPMN's terms.
|
|
@@ -360,6 +892,7 @@ class ScopeBuilder {
|
|
|
360
892
|
*
|
|
361
893
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
362
894
|
* @param opts - `name`, and which task `element` to emit (default `bpmn:task`).
|
|
895
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
363
896
|
* @returns This builder, so calls chain.
|
|
364
897
|
*
|
|
365
898
|
* @example
|
|
@@ -370,8 +903,8 @@ class ScopeBuilder {
|
|
|
370
903
|
* .endEvent('e')
|
|
371
904
|
* ```
|
|
372
905
|
*/
|
|
373
|
-
plainTask(id, opts = {}) {
|
|
374
|
-
return this.
|
|
906
|
+
plainTask(id, opts = {}, body) {
|
|
907
|
+
return this.pushActivity({
|
|
375
908
|
kind: 'plainTask',
|
|
376
909
|
id,
|
|
377
910
|
name: opts.name,
|
|
@@ -380,18 +913,21 @@ class ScopeBuilder {
|
|
|
380
913
|
retry: opts.retry,
|
|
381
914
|
errorMapping: opts.errorMapping,
|
|
382
915
|
loop: opts.loop,
|
|
383
|
-
});
|
|
916
|
+
}, body);
|
|
384
917
|
}
|
|
385
|
-
connector(id, a, b, c, d) {
|
|
918
|
+
connector(id, a, b, c, d, e) {
|
|
919
|
+
// The two forms put `opts` and `body` one position apart; sort them out first.
|
|
920
|
+
const stringly = typeof a === 'string';
|
|
921
|
+
const bpmnOpts = (stringly ? (typeof d === 'function' ? undefined : d) : c) ?? {};
|
|
922
|
+
const body = stringly ? e : typeof d === 'function' ? d : undefined;
|
|
386
923
|
// Reuse the shared connector() overload to parse descriptor-or-string into a
|
|
387
924
|
// ConnectorSpec; then carry its fields onto the BPMN node.
|
|
388
|
-
const spec =
|
|
389
|
-
? makeConnector(a, b, c,
|
|
390
|
-
: makeConnector(a, b,
|
|
925
|
+
const spec = stringly
|
|
926
|
+
? makeConnector(a, b, c, bpmnOpts)
|
|
927
|
+
: makeConnector(a, b, bpmnOpts);
|
|
391
928
|
if (spec.kind !== 'connector')
|
|
392
929
|
throw new Error('connector(): unexpected spec kind');
|
|
393
|
-
|
|
394
|
-
return this.push({
|
|
930
|
+
return this.pushActivity({
|
|
395
931
|
kind: 'connector',
|
|
396
932
|
id,
|
|
397
933
|
name: bpmnOpts.name,
|
|
@@ -406,7 +942,7 @@ class ScopeBuilder {
|
|
|
406
942
|
skipCondition: bpmnOpts.skipCondition,
|
|
407
943
|
retry: bpmnOpts.retry,
|
|
408
944
|
loop: bpmnOpts.loop,
|
|
409
|
-
});
|
|
945
|
+
}, body);
|
|
410
946
|
}
|
|
411
947
|
/**
|
|
412
948
|
* An **HTTP request** service task (`bpmn:sendTask` +
|
|
@@ -439,11 +975,12 @@ class ScopeBuilder {
|
|
|
439
975
|
*
|
|
440
976
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
441
977
|
* @param opts - The request, plus `name`, `outputVar`, and the shared activity options.
|
|
978
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
442
979
|
* @returns This builder, so calls chain.
|
|
443
980
|
*/
|
|
444
|
-
http(id, opts) {
|
|
445
|
-
const { name, url, method, headers, parameters, body, outputs, outputVar, skipCondition, retry, loop, errorMapping } = opts;
|
|
446
|
-
return this.
|
|
981
|
+
http(id, opts, body) {
|
|
982
|
+
const { name, url, method, headers, parameters, body: requestBody, outputs, outputVar, skipCondition, retry, loop, errorMapping } = opts;
|
|
983
|
+
return this.pushActivity({
|
|
447
984
|
kind: 'typed',
|
|
448
985
|
id,
|
|
449
986
|
name,
|
|
@@ -458,7 +995,7 @@ class ScopeBuilder {
|
|
|
458
995
|
url,
|
|
459
996
|
...(headers !== undefined ? { headers } : {}),
|
|
460
997
|
...(parameters !== undefined ? { parameters } : {}),
|
|
461
|
-
...(
|
|
998
|
+
...(requestBody !== undefined ? { body: requestBody } : {}),
|
|
462
999
|
},
|
|
463
1000
|
inputs: {},
|
|
464
1001
|
outputs,
|
|
@@ -467,7 +1004,102 @@ class ScopeBuilder {
|
|
|
467
1004
|
retry,
|
|
468
1005
|
loop,
|
|
469
1006
|
errorMapping,
|
|
1007
|
+
}, body);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* A UiPath **Data Fabric** (Data Service) record or file operation — create,
|
|
1011
|
+
* update, get, query, delete a record, or download / upload / delete a record's
|
|
1012
|
+
* file field — as an `Intsvc.ActivityExecution` send task in the exact shape the
|
|
1013
|
+
* Flow→BPMN converter and Studio Web write.
|
|
1014
|
+
*
|
|
1015
|
+
* @remarks
|
|
1016
|
+
* The connector library ships one Data Service operation, so `.connector()` cannot
|
|
1017
|
+
* author the rest, and hand-filling `.activity('Intsvc.ActivityExecution', …)` gets
|
|
1018
|
+
* the wire details wrong in ways `validate` does not catch (a `recordId` in the body,
|
|
1019
|
+
* a curated `objectName` on an entity-form path, a folder binding keyed by the folder).
|
|
1020
|
+
* This writes each of the eight operations the way the platform's own tools do:
|
|
1021
|
+
* create and update in the ENTITY form (`POST /<Entity>`, `PUT /<Entity>/{id}`,
|
|
1022
|
+
* `objectName` = the entity), the others in the CURATED form (`entityName` as a
|
|
1023
|
+
* path parameter, `objectName` = the curated operation), every parameter its own
|
|
1024
|
+
* `path` / `query` / `file` row and the record as the one json `body` row. Needs no
|
|
1025
|
+
* connector library at compile time.
|
|
1026
|
+
*
|
|
1027
|
+
* The response lands in `outputVar` (default `<id>_response`) and the error payload in
|
|
1028
|
+
* `<id>_Error`, both declared for you.
|
|
1029
|
+
*
|
|
1030
|
+
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
1031
|
+
* @param opts - The entity, connection and folder, plus the operation and its parameters — see {@link DataServiceOpts} and {@link DataServiceOperation}.
|
|
1032
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
1033
|
+
* @returns This builder, so calls chain.
|
|
1034
|
+
*/
|
|
1035
|
+
dataService(id, opts, body) {
|
|
1036
|
+
const wire = dataServiceWire(opts.entity, opts);
|
|
1037
|
+
const connection = this.dataServiceBinding(id, 'connection', opts.connection, 'ConnectionId', 'Data Fabric connection');
|
|
1038
|
+
const folder = opts.folder !== undefined
|
|
1039
|
+
? this.dataServiceBinding(id, 'folder', opts.folder, 'folderKey', 'Data Fabric folder', connection.key)
|
|
1040
|
+
: undefined;
|
|
1041
|
+
const responseVar = opts.outputVar ?? `${id}_response`;
|
|
1042
|
+
return this.pushActivity({
|
|
1043
|
+
kind: 'typed',
|
|
1044
|
+
id,
|
|
1045
|
+
name: opts.name ?? wire.label,
|
|
1046
|
+
type: DATA_SERVICE_ACTIVITY_TYPE,
|
|
1047
|
+
context: {
|
|
1048
|
+
// `activity` is the selected-operation key the canvas validator requires
|
|
1049
|
+
// (EMPTY_REQUIRED_FIELD without it); the converter omits it and the CLI accepts
|
|
1050
|
+
// both, so it carries the same verb `operation` does — as `.connector()` writes it.
|
|
1051
|
+
activity: wire.context.operation,
|
|
1052
|
+
connectorKey: DATA_SERVICE_CONNECTOR_KEY,
|
|
1053
|
+
connection: connection.ref,
|
|
1054
|
+
...(folder ? { folderKey: folder.ref } : {}),
|
|
1055
|
+
...wire.context,
|
|
1056
|
+
activityConfigurationVersion: 'v1',
|
|
1057
|
+
metadata: {},
|
|
1058
|
+
},
|
|
1059
|
+
inputs: {},
|
|
1060
|
+
inputRows: wire.inputRows,
|
|
1061
|
+
outputRows: [
|
|
1062
|
+
{
|
|
1063
|
+
name: 'response',
|
|
1064
|
+
type: 'jsonSchema',
|
|
1065
|
+
source: '=response',
|
|
1066
|
+
var: responseVar,
|
|
1067
|
+
...(opts.schema !== undefined ? { schema: opts.schema } : {}),
|
|
1068
|
+
},
|
|
1069
|
+
{ name: 'Error', type: 'jsonSchema', source: '=Error', var: `${id}_Error`, schema: errorSchema },
|
|
1070
|
+
],
|
|
1071
|
+
skipCondition: opts.skipCondition,
|
|
1072
|
+
tags: opts.tags,
|
|
1073
|
+
retry: opts.retry,
|
|
1074
|
+
errorMapping: opts.errorMapping,
|
|
1075
|
+
loop: opts.loop,
|
|
1076
|
+
}, body);
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* @internal Resolve a `.dataService()` connection or folder to a `=bindings.<id>`
|
|
1080
|
+
* reference: an existing binding by id, else one already declared for the same
|
|
1081
|
+
* key, else a new one declared on the author's behalf. `companionKey` is the
|
|
1082
|
+
* connection's key a folder binding must carry as its `resourceKey`.
|
|
1083
|
+
*/
|
|
1084
|
+
dataServiceBinding(nodeId, role, given, propertyAttribute, displayName, companionKey) {
|
|
1085
|
+
const byId = given.startsWith('=bindings.') ? given.slice('=bindings.'.length) : given;
|
|
1086
|
+
const same = (b) => b.resource === 'Connection' && b.propertyAttribute.toLowerCase() === propertyAttribute.toLowerCase();
|
|
1087
|
+
const existing = this._bindingRegistry.bindings.find((b) => b.id === byId) ??
|
|
1088
|
+
this._bindingRegistry.bindings.find((b) => same(b) && b.default === given);
|
|
1089
|
+
if (existing) {
|
|
1090
|
+
// A folder given as a binding the author declared is re-keyed at serialize, once
|
|
1091
|
+
// its companion is known — see `folderCompanionKeys` in serialize.ts.
|
|
1092
|
+
return { ref: `=bindings.${existing.id}`, key: existing.resourceKey ?? existing.default ?? existing.id };
|
|
1093
|
+
}
|
|
1094
|
+
const decl = this._bindingRegistry.declare({
|
|
1095
|
+
id: `${nodeId}_${role}`,
|
|
1096
|
+
name: displayName,
|
|
1097
|
+
resource: 'Connection',
|
|
1098
|
+
propertyAttribute,
|
|
1099
|
+
default: given,
|
|
1100
|
+
resourceKey: companionKey ?? given,
|
|
470
1101
|
});
|
|
1102
|
+
return { ref: `=bindings.${decl.id}`, key: decl.resourceKey ?? given };
|
|
471
1103
|
}
|
|
472
1104
|
/**
|
|
473
1105
|
* @internal Turn a literal into a binding reference where the platform demands
|
|
@@ -504,8 +1136,8 @@ class ScopeBuilder {
|
|
|
504
1136
|
return out;
|
|
505
1137
|
}
|
|
506
1138
|
/** @internal Shared body of the Orchestrator process-invocation methods. */
|
|
507
|
-
orchestratorNode(id, type, opts) {
|
|
508
|
-
return this.
|
|
1139
|
+
orchestratorNode(id, type, opts, body) {
|
|
1140
|
+
return this.pushActivity({
|
|
509
1141
|
kind: 'typed',
|
|
510
1142
|
id,
|
|
511
1143
|
name: opts.name,
|
|
@@ -523,7 +1155,7 @@ class ScopeBuilder {
|
|
|
523
1155
|
retry: opts.retry,
|
|
524
1156
|
errorMapping: opts.errorMapping,
|
|
525
1157
|
loop: opts.loop,
|
|
526
|
-
});
|
|
1158
|
+
}, body);
|
|
527
1159
|
}
|
|
528
1160
|
/**
|
|
529
1161
|
* Start an **RPA process** and wait for it (`Orchestrator.StartJob`).
|
|
@@ -541,20 +1173,22 @@ class ScopeBuilder {
|
|
|
541
1173
|
*
|
|
542
1174
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
543
1175
|
* @param opts - Which process, folder, and input arguments.
|
|
1176
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
544
1177
|
* @returns This builder, so calls chain.
|
|
545
1178
|
*/
|
|
546
|
-
startProcess(id, opts) {
|
|
547
|
-
return this.orchestratorNode(id, 'Orchestrator.StartJob', opts);
|
|
1179
|
+
startProcess(id, opts, body) {
|
|
1180
|
+
return this.orchestratorNode(id, 'Orchestrator.StartJob', opts, body);
|
|
548
1181
|
}
|
|
549
1182
|
/**
|
|
550
1183
|
* Start an **agent** and wait for it (`Orchestrator.StartAgentJob`).
|
|
551
1184
|
*
|
|
552
1185
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
553
1186
|
* @param opts - Which agent process, folder, and input arguments.
|
|
1187
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
554
1188
|
* @returns This builder, so calls chain.
|
|
555
1189
|
*/
|
|
556
|
-
startAgent(id, opts) {
|
|
557
|
-
return this.orchestratorNode(id, 'Orchestrator.StartAgentJob', opts);
|
|
1190
|
+
startAgent(id, opts, body) {
|
|
1191
|
+
return this.orchestratorNode(id, 'Orchestrator.StartAgentJob', opts, body);
|
|
558
1192
|
}
|
|
559
1193
|
/**
|
|
560
1194
|
* Invoke an **agentic process** as a call activity
|
|
@@ -562,10 +1196,11 @@ class ScopeBuilder {
|
|
|
562
1196
|
*
|
|
563
1197
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
564
1198
|
* @param opts - Which process, folder, input arguments, and whether to wait.
|
|
1199
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
565
1200
|
* @returns This builder, so calls chain.
|
|
566
1201
|
*/
|
|
567
|
-
startAgenticProcess(id, opts) {
|
|
568
|
-
return this.orchestratorNode(id, opts.async ? 'Orchestrator.StartAgenticProcessAsync' : 'Orchestrator.StartAgenticProcess', opts);
|
|
1202
|
+
startAgenticProcess(id, opts, body) {
|
|
1203
|
+
return this.orchestratorNode(id, opts.async ? 'Orchestrator.StartAgenticProcessAsync' : 'Orchestrator.StartAgenticProcess', opts, body);
|
|
569
1204
|
}
|
|
570
1205
|
/**
|
|
571
1206
|
* Invoke a **case-management process** as a call activity
|
|
@@ -573,10 +1208,11 @@ class ScopeBuilder {
|
|
|
573
1208
|
*
|
|
574
1209
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
575
1210
|
* @param opts - Which case process, folder, input arguments, and whether to wait.
|
|
1211
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
576
1212
|
* @returns This builder, so calls chain.
|
|
577
1213
|
*/
|
|
578
|
-
startCaseProcess(id, opts) {
|
|
579
|
-
return this.orchestratorNode(id, opts.async ? 'Orchestrator.StartCaseMgmtProcessAsync' : 'Orchestrator.StartCaseMgmtProcess', opts);
|
|
1214
|
+
startCaseProcess(id, opts, body) {
|
|
1215
|
+
return this.orchestratorNode(id, opts.async ? 'Orchestrator.StartCaseMgmtProcessAsync' : 'Orchestrator.StartCaseMgmtProcess', opts, body);
|
|
580
1216
|
}
|
|
581
1217
|
/**
|
|
582
1218
|
* Execute an **API workflow**, fire-and-forget
|
|
@@ -588,10 +1224,11 @@ class ScopeBuilder {
|
|
|
588
1224
|
*
|
|
589
1225
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
590
1226
|
* @param opts - Which workflow, folder, and input arguments.
|
|
1227
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
591
1228
|
* @returns This builder, so calls chain.
|
|
592
1229
|
*/
|
|
593
|
-
executeApiWorkflow(id, opts) {
|
|
594
|
-
return this.orchestratorNode(id, 'Orchestrator.ExecuteApiWorkflowAsync', opts);
|
|
1230
|
+
executeApiWorkflow(id, opts, body) {
|
|
1231
|
+
return this.orchestratorNode(id, 'Orchestrator.ExecuteApiWorkflowAsync', opts, body);
|
|
595
1232
|
}
|
|
596
1233
|
/**
|
|
597
1234
|
* Execute a **business rule** and wait for it (`Orchestrator.BusinessRules`, on a
|
|
@@ -636,9 +1273,10 @@ class ScopeBuilder {
|
|
|
636
1273
|
* @param opts - Which rule package, folder, and input arguments. `outputVar` defaults
|
|
637
1274
|
* to `<id>_businessRuleResponse` — see the note in the implementation for why this one
|
|
638
1275
|
* type does not take its default from the spec.
|
|
1276
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
639
1277
|
* @returns This builder, so calls chain.
|
|
640
1278
|
*/
|
|
641
|
-
businessRule(id, opts) {
|
|
1279
|
+
businessRule(id, opts, body) {
|
|
642
1280
|
// The ONE place this type departs from its family, and deliberately.
|
|
643
1281
|
//
|
|
644
1282
|
// Every other member takes its default output variable from the registry's
|
|
@@ -653,10 +1291,7 @@ class ScopeBuilder {
|
|
|
653
1291
|
//
|
|
654
1292
|
// Pinning it here makes the default independent of the label. The output ROW still
|
|
655
1293
|
// carries the registry's own `name`, so the artifact is unchanged.
|
|
656
|
-
return this.orchestratorNode(id, 'Orchestrator.BusinessRules', {
|
|
657
|
-
...opts,
|
|
658
|
-
outputVar: opts.outputVar ?? `${id}_businessRuleResponse`,
|
|
659
|
-
});
|
|
1294
|
+
return this.orchestratorNode(id, 'Orchestrator.BusinessRules', { ...opts, outputVar: opts.outputVar ?? `${id}_businessRuleResponse` }, body);
|
|
660
1295
|
}
|
|
661
1296
|
/**
|
|
662
1297
|
* Add an item to an Orchestrator **queue** (`Orchestrator.CreateQueueItem`, or
|
|
@@ -675,10 +1310,11 @@ class ScopeBuilder {
|
|
|
675
1310
|
*
|
|
676
1311
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
677
1312
|
* @param opts - Which queue and folder, the item content, and whether to wait for it.
|
|
1313
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
678
1314
|
* @returns This builder, so calls chain.
|
|
679
1315
|
*/
|
|
680
|
-
queueItem(id, opts) {
|
|
681
|
-
return this.
|
|
1316
|
+
queueItem(id, opts, body) {
|
|
1317
|
+
return this.pushActivity({
|
|
682
1318
|
kind: 'typed',
|
|
683
1319
|
id,
|
|
684
1320
|
name: opts.name,
|
|
@@ -695,7 +1331,7 @@ class ScopeBuilder {
|
|
|
695
1331
|
retry: opts.retry,
|
|
696
1332
|
errorMapping: opts.errorMapping,
|
|
697
1333
|
loop: opts.loop,
|
|
698
|
-
});
|
|
1334
|
+
}, body);
|
|
699
1335
|
}
|
|
700
1336
|
/**
|
|
701
1337
|
* A **human task** — an Action App task a person completes (`bpmn:userTask` +
|
|
@@ -745,10 +1381,11 @@ class ScopeBuilder {
|
|
|
745
1381
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
746
1382
|
* @param opts - The app, the outcomes offered, what the human sees, and which
|
|
747
1383
|
* response fields to keep.
|
|
1384
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
748
1385
|
* @returns This builder, so calls chain.
|
|
749
1386
|
*/
|
|
750
|
-
humanTask(id, opts) {
|
|
751
|
-
return this.
|
|
1387
|
+
humanTask(id, opts, body) {
|
|
1388
|
+
return this.pushActivity({
|
|
752
1389
|
kind: 'typed',
|
|
753
1390
|
id,
|
|
754
1391
|
name: opts.name,
|
|
@@ -768,7 +1405,7 @@ class ScopeBuilder {
|
|
|
768
1405
|
retry: opts.retry,
|
|
769
1406
|
errorMapping: opts.errorMapping,
|
|
770
1407
|
loop: opts.loop,
|
|
771
|
-
});
|
|
1408
|
+
}, body);
|
|
772
1409
|
}
|
|
773
1410
|
/**
|
|
774
1411
|
* Wait for an internal message from another Maestro process
|
|
@@ -807,10 +1444,11 @@ class ScopeBuilder {
|
|
|
807
1444
|
*
|
|
808
1445
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
809
1446
|
* @param opts - The message name, the correlation reference, and what to keep.
|
|
1447
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
810
1448
|
* @returns This builder, so calls chain.
|
|
811
1449
|
*/
|
|
812
|
-
receiveMessage(id, opts) {
|
|
813
|
-
return this.
|
|
1450
|
+
receiveMessage(id, opts, body) {
|
|
1451
|
+
return this.pushActivity({
|
|
814
1452
|
kind: 'typed',
|
|
815
1453
|
id,
|
|
816
1454
|
name: opts.name,
|
|
@@ -823,7 +1461,7 @@ class ScopeBuilder {
|
|
|
823
1461
|
retry: opts.retry,
|
|
824
1462
|
errorMapping: opts.errorMapping,
|
|
825
1463
|
loop: opts.loop,
|
|
826
|
-
});
|
|
1464
|
+
}, body);
|
|
827
1465
|
}
|
|
828
1466
|
/**
|
|
829
1467
|
* Wait mid-flow for an Integration Service connector event
|
|
@@ -863,10 +1501,11 @@ class ScopeBuilder {
|
|
|
863
1501
|
* @param connector - The connector key, e.g. `'uipath-microsoft-outlook365'`.
|
|
864
1502
|
* @param event - The event name, e.g. `'email-received'`.
|
|
865
1503
|
* @param opts - Connection/folder bindings, the `where` scope, and what to keep.
|
|
1504
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
866
1505
|
* @returns This builder, so calls chain.
|
|
867
1506
|
*/
|
|
868
|
-
waitForEvent(id, connector, event, opts) {
|
|
869
|
-
return this.
|
|
1507
|
+
waitForEvent(id, connector, event, opts, body) {
|
|
1508
|
+
return this.pushActivity({
|
|
870
1509
|
kind: 'connectorEvent',
|
|
871
1510
|
id,
|
|
872
1511
|
name: opts.name,
|
|
@@ -886,7 +1525,7 @@ class ScopeBuilder {
|
|
|
886
1525
|
retry: opts.retry,
|
|
887
1526
|
errorMapping: opts.errorMapping,
|
|
888
1527
|
loop: opts.loop,
|
|
889
|
-
});
|
|
1528
|
+
}, body);
|
|
890
1529
|
}
|
|
891
1530
|
/**
|
|
892
1531
|
* START the process when an Integration Service connector event fires
|
|
@@ -966,10 +1605,11 @@ class ScopeBuilder {
|
|
|
966
1605
|
* @param action - The `AgentExecution` operation name.
|
|
967
1606
|
* @param inputs - The invocation payload.
|
|
968
1607
|
* @param opts - `mode`, the connection/folder bindings, and what to keep.
|
|
1608
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
969
1609
|
* @returns This builder, so calls chain.
|
|
970
1610
|
*/
|
|
971
|
-
externalAgent(id, connector, action, inputs, opts) {
|
|
972
|
-
return this.externalTask('agent', id, connector, action, inputs, opts);
|
|
1611
|
+
externalAgent(id, connector, action, inputs, opts, body) {
|
|
1612
|
+
return this.externalTask('agent', id, connector, action, inputs, opts, body);
|
|
973
1613
|
}
|
|
974
1614
|
/**
|
|
975
1615
|
* Invoke an external workflow through an Integration Service connector
|
|
@@ -984,14 +1624,15 @@ class ScopeBuilder {
|
|
|
984
1624
|
* @param action - The `ProcessExecution` operation name.
|
|
985
1625
|
* @param inputs - The invocation payload.
|
|
986
1626
|
* @param opts - `mode`, the connection/folder bindings, and what to keep.
|
|
1627
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
987
1628
|
* @returns This builder, so calls chain.
|
|
988
1629
|
*/
|
|
989
|
-
externalWorkflow(id, connector, action, inputs, opts) {
|
|
990
|
-
return this.externalTask('workflow', id, connector, action, inputs, opts);
|
|
1630
|
+
externalWorkflow(id, connector, action, inputs, opts, body) {
|
|
1631
|
+
return this.externalTask('workflow', id, connector, action, inputs, opts, body);
|
|
991
1632
|
}
|
|
992
1633
|
/** @internal Shared body of the two external-invocation methods. */
|
|
993
|
-
externalTask(family, id, connector, action, inputs, opts) {
|
|
994
|
-
return this.
|
|
1634
|
+
externalTask(family, id, connector, action, inputs, opts, body) {
|
|
1635
|
+
return this.pushActivity({
|
|
995
1636
|
kind: 'externalTask',
|
|
996
1637
|
id,
|
|
997
1638
|
name: opts.name,
|
|
@@ -1010,7 +1651,7 @@ class ScopeBuilder {
|
|
|
1010
1651
|
retry: opts.retry,
|
|
1011
1652
|
errorMapping: opts.errorMapping,
|
|
1012
1653
|
loop: opts.loop,
|
|
1013
|
-
});
|
|
1654
|
+
}, body);
|
|
1014
1655
|
}
|
|
1015
1656
|
/**
|
|
1016
1657
|
* ANY registry-backed node, by extension type — the generic form the typed
|
|
@@ -1042,17 +1683,20 @@ class ScopeBuilder {
|
|
|
1042
1683
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
1043
1684
|
* @param type - The registry extension type, e.g. `'A2A.AgentExecution'`.
|
|
1044
1685
|
* @param opts - Context values, payload, output mapping and the shared activity options.
|
|
1686
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
1045
1687
|
* @returns This builder, so calls chain.
|
|
1046
1688
|
*/
|
|
1047
|
-
activity(id, type, opts = {}) {
|
|
1048
|
-
return this.
|
|
1689
|
+
activity(id, type, opts = {}, body) {
|
|
1690
|
+
return this.pushActivity({
|
|
1049
1691
|
kind: 'typed',
|
|
1050
1692
|
id,
|
|
1051
1693
|
name: opts.name,
|
|
1052
1694
|
type,
|
|
1053
|
-
context: opts.context ?? {},
|
|
1054
|
-
contextRows: opts.contextRows,
|
|
1695
|
+
context: defaultActivityIdentity(type, opts.context ?? {}, opts.contextRows),
|
|
1696
|
+
contextRows: defaultActivityIdentityRows(type, opts.context ?? {}, opts.contextRows),
|
|
1055
1697
|
inputs: opts.inputs ?? {},
|
|
1698
|
+
inputRows: opts.inputRows,
|
|
1699
|
+
spec: opts.spec,
|
|
1056
1700
|
typeVersion: opts.typeVersion,
|
|
1057
1701
|
inputSchema: opts.inputSchema,
|
|
1058
1702
|
outputVar: opts.outputVar,
|
|
@@ -1064,7 +1708,7 @@ class ScopeBuilder {
|
|
|
1064
1708
|
retry: opts.retry,
|
|
1065
1709
|
errorMapping: opts.errorMapping,
|
|
1066
1710
|
loop: opts.loop,
|
|
1067
|
-
});
|
|
1711
|
+
}, body);
|
|
1068
1712
|
}
|
|
1069
1713
|
// ── Sub-process (nested graph) ──
|
|
1070
1714
|
/**
|
|
@@ -1073,14 +1717,312 @@ class ScopeBuilder {
|
|
|
1073
1717
|
* @param id - The element id, unique in the process and used by `sequenceFlow`.
|
|
1074
1718
|
* @param fn - Receives a sub-builder for the elements inside it.
|
|
1075
1719
|
* @param opts - `name`, and the loop characteristics when it repeats.
|
|
1720
|
+
* @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
|
|
1076
1721
|
* @returns This builder, so calls chain.
|
|
1077
1722
|
*/
|
|
1078
|
-
subProcess(id, fn, opts = {}) {
|
|
1079
|
-
|
|
1723
|
+
subProcess(id, fn, opts = {}, body) {
|
|
1724
|
+
// Inherits the enclosing REAL scope's mode — an arm passes its parent's on, not its
|
|
1725
|
+
// own always-sequence — and the body may still call `.flowMode()` first to differ.
|
|
1726
|
+
const sb = new SubProcessBuilder(this._defs, this._bindingRegistry, undefined, this._scopeMode, this._sugar);
|
|
1080
1727
|
fn(sb);
|
|
1081
|
-
|
|
1728
|
+
sb.closeScope(`sub-process "${id}"`);
|
|
1729
|
+
return this.pushActivity(sb._buildNode(id, opts), body);
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* An EVENT sub-process — a handler that lives inside this scope and starts when
|
|
1733
|
+
* an error, a message, or a timer occurs anywhere in it (`bpmn:subProcess
|
|
1734
|
+
* triggeredByEvent="true"`, with its single start event synthesized).
|
|
1735
|
+
*
|
|
1736
|
+
* @remarks
|
|
1737
|
+
* The construct BPMN has for a safety net: it has no incoming flow, and it
|
|
1738
|
+
* catches what nothing closer to the failing element caught. Distinct from a
|
|
1739
|
+
* boundary handler (`.onError()` on one activity), which guards one element; this
|
|
1740
|
+
* guards the whole container it sits in, so where it sits is what it guards — put
|
|
1741
|
+
* it inside a multi-instance sub-process to fail one iteration rather than the
|
|
1742
|
+
* whole run.
|
|
1743
|
+
*
|
|
1744
|
+
* Its body is written in order: elements chain from the synthesized start, and
|
|
1745
|
+
* branch with `.choose()` / `.fork()` / `.race()`. Every path must end — with an
|
|
1746
|
+
* end event or an explicit `.sequenceFlow()` off its last element — because an
|
|
1747
|
+
* event sub-process has nowhere else to go.
|
|
1748
|
+
*
|
|
1749
|
+
* @example
|
|
1750
|
+
* **A central failure net that classifies on the error it caught**
|
|
1751
|
+
* ```ts
|
|
1752
|
+
* bpmn('payroll')
|
|
1753
|
+
* .var('outcome', 'string', { default: 'RUNNING' })
|
|
1754
|
+
* .startEvent('start')
|
|
1755
|
+
* .scriptTask('calculate', { script: 'return { ok: true };' })
|
|
1756
|
+
* .endEvent('done')
|
|
1757
|
+
* .sequenceFlow('start', 'calculate')
|
|
1758
|
+
* .sequenceFlow('calculate', 'done')
|
|
1759
|
+
* .eventSubProcess('failures', { error: true, name: 'Failure net' }, (h) =>
|
|
1760
|
+
* h.choose('kind', [
|
|
1761
|
+
* {
|
|
1762
|
+
* // The start captures the error into `vars.Error` by default (`errorVar`).
|
|
1763
|
+
* when: '=js:String(vars.Error.code).indexOf("DATA") >= 0',
|
|
1764
|
+
* label: 'Bad data',
|
|
1765
|
+
* body: (b) => b.task('flagData', { set: { outcome: 'FAILED_BAD_DATA' } }).endEvent('badData', { name: 'Bad data handled' }),
|
|
1766
|
+
* },
|
|
1767
|
+
* {
|
|
1768
|
+
* otherwise: true,
|
|
1769
|
+
* label: 'Outage',
|
|
1770
|
+
* body: (b) => b.task('flagOutage', { set: { outcome: 'FAILED_OUTAGE' } }).endEvent('outage', { name: 'Outage escalated' }),
|
|
1771
|
+
* },
|
|
1772
|
+
* ]),
|
|
1773
|
+
* )
|
|
1774
|
+
* .build();
|
|
1775
|
+
* ```
|
|
1776
|
+
*
|
|
1777
|
+
* @param id - The sub-process's element id. The start event is `<id>_start` unless `startId` says otherwise.
|
|
1778
|
+
* @param opts - What starts it — exactly one of `error` / `message` / `timer` — and the rest of {@link EventSubProcessOpts}.
|
|
1779
|
+
* @param fn - Receives the body builder; elements chain in the order they are written.
|
|
1780
|
+
* @returns This builder, so calls chain.
|
|
1781
|
+
* @throws If none or several of `error` / `message` / `timer` are given, if an error start
|
|
1782
|
+
* is asked to be non-interrupting, or if a path in the body is left open.
|
|
1783
|
+
*/
|
|
1784
|
+
eventSubProcess(id, opts, fn) {
|
|
1785
|
+
const given = [opts.error !== undefined, opts.message !== undefined, opts.timer !== undefined].filter(Boolean).length;
|
|
1786
|
+
if (given !== 1) {
|
|
1787
|
+
throw new Error(`eventSubProcess "${id}" needs exactly one of { error, message, timer } — that is what starts it.`);
|
|
1788
|
+
}
|
|
1789
|
+
if (opts.error !== undefined && opts.interrupting === false) {
|
|
1790
|
+
throw new Error(`eventSubProcess "${id}": an error start is always interrupting — the throw has already ended the scope it came from.`);
|
|
1791
|
+
}
|
|
1792
|
+
if (opts.errorVar !== undefined && opts.errorVar !== false && opts.error === undefined) {
|
|
1793
|
+
throw new Error(`eventSubProcess "${id}": \`errorVar\` captures an error, so it needs \`error\`.`);
|
|
1794
|
+
}
|
|
1795
|
+
if (typeof opts.errorVar === 'string' && opts.startPayload !== undefined) {
|
|
1796
|
+
throw new Error(`eventSubProcess "${id}": pass \`errorVar\` or \`startPayload\`, not both — both write the start event's output row.`);
|
|
1797
|
+
}
|
|
1798
|
+
// The capture row is on by default for an error start: without a declared `Error`
|
|
1799
|
+
// the platform validator rejects every `vars.Error` read in the net. An explicit
|
|
1800
|
+
// `startPayload` owns the row instead, and `errorVar: false` writes none.
|
|
1801
|
+
const capture = opts.errorVar === false || opts.startPayload !== undefined
|
|
1802
|
+
? undefined
|
|
1803
|
+
: (opts.errorVar ?? (opts.error !== undefined ? `${id}_Error` : undefined));
|
|
1804
|
+
// The same collision one scope out: two error nets in one container, the platform's
|
|
1805
|
+
// MULTIPLE_CATCH_ALL_ERROR_EVENT_SUBPROCESS / DUPLICATE_ERROR_EVENT_SUBPROCESS. The
|
|
1806
|
+
// error name is interned once, so two nets naming one error share its ref and compare
|
|
1807
|
+
// equal here.
|
|
1808
|
+
if (opts.error !== undefined) {
|
|
1809
|
+
const mine = toErrorDef(this._defs, opts.error, `eventSubProcess "${id}"`);
|
|
1810
|
+
const mineRef = mine.type === 'error' ? mine.errorRef : undefined;
|
|
1811
|
+
const clash = this._nodes.find((n) => {
|
|
1812
|
+
if (n.kind !== 'subProcess' || n.triggeredByEvent !== true)
|
|
1813
|
+
return false;
|
|
1814
|
+
const start = loneStart(n.nodes);
|
|
1815
|
+
const other = start === undefined ? undefined : errorCaught(start);
|
|
1816
|
+
return other !== undefined && other.catchAll === (mineRef === undefined) && other.ref === mineRef;
|
|
1817
|
+
});
|
|
1818
|
+
if (clash !== undefined) {
|
|
1819
|
+
throw new Error(`eventSubProcess "${id}" catches the same error as "${clash.id}" in this scope ` +
|
|
1820
|
+
`(${mineRef === undefined ? 'both catch ANY error' : `both catch "${mineRef}"`}), so one of the ` +
|
|
1821
|
+
'two can never run. Catch a specific error in each net, or keep the one catch-all.');
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
const startId = opts.startId ?? `${id}_start`;
|
|
1825
|
+
let event;
|
|
1826
|
+
if (opts.error !== undefined)
|
|
1827
|
+
event = toErrorDef(this._defs, opts.error, `eventSubProcess "${id}"`);
|
|
1828
|
+
else if (opts.timer !== undefined)
|
|
1829
|
+
event = { type: 'timer', timer: normTimer(opts.timer) };
|
|
1830
|
+
else
|
|
1831
|
+
event = { type: 'message', messageRef: this._defs.messageRef(opts.message) };
|
|
1832
|
+
const sb = new SubProcessBuilder(this._defs, this._bindingRegistry, undefined, 'sequence', this._sugar);
|
|
1833
|
+
sb._begin({
|
|
1834
|
+
kind: 'startEvent',
|
|
1835
|
+
id: startId,
|
|
1836
|
+
name: opts.startName,
|
|
1837
|
+
event,
|
|
1838
|
+
payload: opts.startPayload ?? (capture !== undefined ? errorCapturePayload(capture, 'startEvent') : undefined),
|
|
1839
|
+
interrupting: opts.interrupting === false ? false : undefined,
|
|
1840
|
+
});
|
|
1841
|
+
if (capture !== undefined)
|
|
1842
|
+
this._sugar.autoVars.push({ id: capture, vars: sb._varsOf(), elementId: startId });
|
|
1843
|
+
fn(sb);
|
|
1844
|
+
sb._finishBody(`event sub-process "${id}"`);
|
|
1845
|
+
return this.push(sb._buildNode(id, { name: opts.name, triggeredByEvent: true, tags: opts.tags, payload: opts.payload }));
|
|
1846
|
+
}
|
|
1847
|
+
// ── Branching by nesting ──
|
|
1848
|
+
/**
|
|
1849
|
+
* An exclusive decision whose arms are written in place — the splitting gateway,
|
|
1850
|
+
* each arm's conditioned flow, the arm's elements and the rejoining gateway, from
|
|
1851
|
+
* one call.
|
|
1852
|
+
*
|
|
1853
|
+
* @remarks
|
|
1854
|
+
* Each arm has a `when` condition or is the single `otherwise` fallback, so an arm
|
|
1855
|
+
* with no condition cannot be written, and the fallback is the gateway's default
|
|
1856
|
+
* flow without anyone naming a flow id. Arms rejoin at the next element placed
|
|
1857
|
+
* after the `choose`; a join gateway (`<id>_join`) is emitted only when more than
|
|
1858
|
+
* one arm actually reaches it, so an arm that ends in an end event adds no join.
|
|
1859
|
+
*
|
|
1860
|
+
* Inside an arm, consecutive elements are wired in order, and an arm may nest
|
|
1861
|
+
* further `choose` / `fork` / `race` calls, activities with boundary handlers, or
|
|
1862
|
+
* `.goto()` to jump to an element written elsewhere. An arm with no body is an
|
|
1863
|
+
* empty branch straight to the continuation.
|
|
1864
|
+
*
|
|
1865
|
+
* @example
|
|
1866
|
+
* **Approve or reject, then carry on**
|
|
1867
|
+
* ```ts
|
|
1868
|
+
* bpmn('review')
|
|
1869
|
+
* .var('action', 'string')
|
|
1870
|
+
* .var('status', 'string')
|
|
1871
|
+
* .startEvent('start')
|
|
1872
|
+
* .task('decide', { set: { action: 'Approved' } })
|
|
1873
|
+
* .choose('approved', [
|
|
1874
|
+
* { when: '=vars.action == "Approved"', label: 'Yes', body: (b) => b.task('post', { set: { status: 'posted' } }) },
|
|
1875
|
+
* { otherwise: true, label: 'No', body: (b) => b.task('reject', { set: { status: 'rejected' } }) },
|
|
1876
|
+
* ])
|
|
1877
|
+
* .endEvent('done')
|
|
1878
|
+
* .sequenceFlow('start', 'decide')
|
|
1879
|
+
* .sequenceFlow('decide', 'approved')
|
|
1880
|
+
* .build();
|
|
1881
|
+
* ```
|
|
1882
|
+
*
|
|
1883
|
+
* @param id - The splitting gateway's element id; the join, when emitted, is `<id>_join`.
|
|
1884
|
+
* @param arms - The arms — see {@link ChooseArm}.
|
|
1885
|
+
* @param opts - `name`, `inclusive` for an inclusive gateway, and `join` to name the join gateway.
|
|
1886
|
+
* @returns This builder, so calls chain.
|
|
1887
|
+
* @throws If there are no arms, more than one `otherwise`, or more than one empty arm.
|
|
1888
|
+
*/
|
|
1889
|
+
choose(id, arms, opts = {}) {
|
|
1890
|
+
if (!arms.length)
|
|
1891
|
+
throw new Error(`choose "${id}" has no arms.`);
|
|
1892
|
+
if (arms.filter((a) => a.otherwise).length > 1) {
|
|
1893
|
+
throw new Error(`choose "${id}" has more than one \`otherwise\` arm; at most one.`);
|
|
1894
|
+
}
|
|
1895
|
+
for (const a of arms) {
|
|
1896
|
+
if (a.when === undefined && !a.otherwise)
|
|
1897
|
+
throw new Error(`choose "${id}": every arm needs \`when\` or \`otherwise: true\`.`);
|
|
1898
|
+
}
|
|
1899
|
+
const kind = opts.inclusive ? 'inclusiveGateway' : 'exclusiveGateway';
|
|
1900
|
+
const split = { kind, id, name: opts.name };
|
|
1901
|
+
return this.branch(split, kind, arms.map((a, i) => ({
|
|
1902
|
+
entry: {
|
|
1903
|
+
source: id,
|
|
1904
|
+
condition: a.when,
|
|
1905
|
+
name: a.label,
|
|
1906
|
+
defaultOf: a.otherwise ? split : undefined,
|
|
1907
|
+
origin: `arm ${i + 1}${a.label ? ` ("${a.label}")` : ''} of choose "${id}"`,
|
|
1908
|
+
},
|
|
1909
|
+
body: a.body,
|
|
1910
|
+
})), `choose "${id}"`, false, opts.join);
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* A parallel split whose arms are written in place: every arm runs, and they
|
|
1914
|
+
* rejoin through a parallel join (`<id>_join`) before the next element.
|
|
1915
|
+
*
|
|
1916
|
+
* @example
|
|
1917
|
+
* **Two lookups at once, then one summary**
|
|
1918
|
+
* ```ts
|
|
1919
|
+
* bpmn('enrich')
|
|
1920
|
+
* .startEvent('start')
|
|
1921
|
+
* .fork('gather', [
|
|
1922
|
+
* (b) => b.http('poLookup', { method: 'GET', url: 'https://erp.example.com/po/1' }),
|
|
1923
|
+
* (b) => b.http('vendorLookup', { method: 'GET', url: 'https://erp.example.com/vendor/1' }),
|
|
1924
|
+
* ])
|
|
1925
|
+
* .scriptTask('summarise', { script: 'return { ok: true };' })
|
|
1926
|
+
* .endEvent('done')
|
|
1927
|
+
* .sequenceFlow('start', 'gather')
|
|
1928
|
+
* .sequenceFlow('summarise', 'done')
|
|
1929
|
+
* .build();
|
|
1930
|
+
* ```
|
|
1931
|
+
*
|
|
1932
|
+
* @param id - The splitting gateway's element id; the join is `<id>_join`.
|
|
1933
|
+
* @param arms - One body per parallel arm.
|
|
1934
|
+
* @param opts - `name` for the splitting gateway, and `join` to name the join gateway.
|
|
1935
|
+
* @returns This builder, so calls chain.
|
|
1936
|
+
*/
|
|
1937
|
+
fork(id, arms, opts = {}) {
|
|
1938
|
+
if (!arms.length)
|
|
1939
|
+
throw new Error(`fork "${id}" has no arms.`);
|
|
1940
|
+
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);
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* An event-based gateway whose arms are written in place: each arm begins with
|
|
1944
|
+
* something to wait for, the first to happen wins, and the rest are cancelled.
|
|
1945
|
+
*
|
|
1946
|
+
* @remarks
|
|
1947
|
+
* An arm must begin with an `intermediateCatchEvent` (message or timer), a
|
|
1948
|
+
* `receiveMessage`, or a `waitForEvent` — that is what the gateway waits on, and
|
|
1949
|
+
* the builder refuses anything else in first position. Arms rejoin through an
|
|
1950
|
+
* exclusive join (`<id>_join`) when more than one falls through.
|
|
1951
|
+
*
|
|
1952
|
+
* @example
|
|
1953
|
+
* **A reply, or a three-day timeout**
|
|
1954
|
+
* ```ts
|
|
1955
|
+
* bpmn('vendor')
|
|
1956
|
+
* .startEvent('start')
|
|
1957
|
+
* .race('waitForEither', [
|
|
1958
|
+
* (b) => b.intermediateCatchEvent('reply', { message: 'VendorReplied' }),
|
|
1959
|
+
* (b) => b.intermediateCatchEvent('timeout', { timer: 'P3D' }).endEvent('gaveUp', { name: 'No reply' }),
|
|
1960
|
+
* ])
|
|
1961
|
+
* .endEvent('done')
|
|
1962
|
+
* .sequenceFlow('start', 'waitForEither')
|
|
1963
|
+
* .build();
|
|
1964
|
+
* ```
|
|
1965
|
+
*
|
|
1966
|
+
* @param id - The gateway's element id; the join, when emitted, is `<id>_join`.
|
|
1967
|
+
* @param arms - One body per alternative, each beginning with the event it waits for.
|
|
1968
|
+
* @param opts - `name` for the gateway, and `join` to name the join gateway.
|
|
1969
|
+
* @returns This builder, so calls chain.
|
|
1970
|
+
* @throws If an arm does not begin with something that waits.
|
|
1971
|
+
*/
|
|
1972
|
+
race(id, arms, opts = {}) {
|
|
1973
|
+
if (!arms.length)
|
|
1974
|
+
throw new Error(`race "${id}" has no arms.`);
|
|
1975
|
+
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);
|
|
1082
1976
|
}
|
|
1083
1977
|
// ── Flows ──
|
|
1978
|
+
/**
|
|
1979
|
+
* Give the edges the nesting constructs IMPLY the ids you choose.
|
|
1980
|
+
*
|
|
1981
|
+
* @remarks
|
|
1982
|
+
* A construct's edges have no call of their own — you write `.choose()` or place two
|
|
1983
|
+
* statements in a sequence scope, and the edge between them follows — so there is
|
|
1984
|
+
* nowhere to name one, and its id is derived as `Flow_<source>_<target>`. That is
|
|
1985
|
+
* fine for a new process and wrong for an edited one: a `.bpmn` that came from
|
|
1986
|
+
* somewhere carries ids like `edge_zOoHdO`, and `uip maestro bpmn merge` keys every
|
|
1987
|
+
* element by id, flows included. Re-derive them and the merge treats every flow as
|
|
1988
|
+
* new, deletes the originals, and takes their diagram edges with them — and any
|
|
1989
|
+
* gateway `default` preserved from the original is left naming an id that no longer
|
|
1990
|
+
* exists, which `uip maestro bpmn validate` then rejects.
|
|
1991
|
+
*
|
|
1992
|
+
* So this is how a decompiled process keeps the ids it arrived with while still
|
|
1993
|
+
* being written in the constructs. `bpmn decompile --style nested` emits one call
|
|
1994
|
+
* listing every implied edge whose original id is not the derived one.
|
|
1995
|
+
*
|
|
1996
|
+
* Only implied edges: an explicit `.sequenceFlow()` already takes an `id`, and a
|
|
1997
|
+
* pair listed here that no implied edge ever connects is simply unused.
|
|
1998
|
+
*
|
|
1999
|
+
* @example
|
|
2000
|
+
* **Keep an import's edge ids through a lift**
|
|
2001
|
+
* ```ts
|
|
2002
|
+
* bpmn('orders')
|
|
2003
|
+
* .var('total', 'number', { default: 0 })
|
|
2004
|
+
* .flowIds([
|
|
2005
|
+
* { source: 'Gw_1', target: 'Review', id: 'edge_zOoHdO' },
|
|
2006
|
+
* { source: 'Gw_1', target: 'Pay', id: 'edge_Kp2mQ1' },
|
|
2007
|
+
* ])
|
|
2008
|
+
* .startEvent('Start_1')
|
|
2009
|
+
* .choose('Gw_1', [
|
|
2010
|
+
* { when: '=vars.total > 1000', body: (b) => b.task('Review').goto('Done') },
|
|
2011
|
+
* { otherwise: true, body: (b) => b.task('Pay').goto('Done') },
|
|
2012
|
+
* ])
|
|
2013
|
+
* .endEvent('Done')
|
|
2014
|
+
* .sequenceFlow('Start_1', 'Gw_1')
|
|
2015
|
+
* .build();
|
|
2016
|
+
* ```
|
|
2017
|
+
*
|
|
2018
|
+
* @param entries - The edges to pin, by their endpoints.
|
|
2019
|
+
* @returns This builder, so calls chain.
|
|
2020
|
+
*/
|
|
2021
|
+
flowIds(entries) {
|
|
2022
|
+
for (const e of entries)
|
|
2023
|
+
this._sugar.pinnedFlowIds.set(`${e.source}\u0000${e.target}`, e.id);
|
|
2024
|
+
return this;
|
|
2025
|
+
}
|
|
1084
2026
|
/**
|
|
1085
2027
|
* A sequence flow from `source` to `target` (1-1 with `bpmn:sequenceFlow`).
|
|
1086
2028
|
*
|
|
@@ -1090,13 +2032,35 @@ class ScopeBuilder {
|
|
|
1090
2032
|
* @returns This builder, so calls chain.
|
|
1091
2033
|
*/
|
|
1092
2034
|
sequenceFlow(source, target, opts = {}) {
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
2035
|
+
// An explicit flow out of an element whose exit is still open is how that path
|
|
2036
|
+
// LEAVES — the same thing `.goto()` says, for a source named rather than implied.
|
|
2037
|
+
// Without this the exit stayed open and `continueTo` silently dropped it (its
|
|
2038
|
+
// source now has an explicit outgoing flow), so the next element was placed with
|
|
2039
|
+
// no incoming edge at all and nothing reported it. Dropping the exit here means
|
|
2040
|
+
// the next element meets the existing "unreachable" refusal instead, which says
|
|
2041
|
+
// what is wrong at the call site that caused it.
|
|
2042
|
+
//
|
|
2043
|
+
// Only a PLAIN exit: one carrying a condition or a gateway default is an edge in
|
|
2044
|
+
// its own right and is always written.
|
|
2045
|
+
const openIndex = this._open.findIndex((e) => e.source === source && e.condition === undefined && e.defaultOf === undefined);
|
|
2046
|
+
if (this._mode === 'sequence' && openIndex !== -1)
|
|
2047
|
+
this._open.splice(openIndex, 1);
|
|
2048
|
+
const id = opts.id ?? `Flow_${source}_${target}`;
|
|
2049
|
+
// An explicit flow that restates one the builder implied — a handler's tail
|
|
2050
|
+
// wired to the statement it was going to reach anyway — is the same edge, and
|
|
2051
|
+
// becomes the author's: their label and condition win, and it no longer counts
|
|
2052
|
+
// as implied.
|
|
2053
|
+
const implied = this._flows.find((f) => f.id === id && f.source === source && f.target === target && this._sugar.impliedFlowIds.has(f.id));
|
|
2054
|
+
if (implied) {
|
|
2055
|
+
if (opts.name !== undefined)
|
|
2056
|
+
implied.name = opts.name;
|
|
2057
|
+
if (opts.condition !== undefined)
|
|
2058
|
+
implied.condition = opts.condition;
|
|
2059
|
+
delete implied.implied;
|
|
2060
|
+
this._sugar.impliedFlowIds.delete(id);
|
|
2061
|
+
return this;
|
|
2062
|
+
}
|
|
2063
|
+
this._flows.push({ id, source, target, name: opts.name, condition: opts.condition });
|
|
1100
2064
|
return this;
|
|
1101
2065
|
}
|
|
1102
2066
|
// ── Variables ──
|
|
@@ -1185,9 +2149,52 @@ export function typedNodeDeclaredVars(n) {
|
|
|
1185
2149
|
// Spelled-out rows REPLACE the derived pair, so they are the whole answer.
|
|
1186
2150
|
if (n.outputRows)
|
|
1187
2151
|
return n.outputRows.map((r) => r.var).filter((v) => v !== undefined);
|
|
1188
|
-
const v = typedNodeOutputVar(n.id, n.type, n.outputVar);
|
|
2152
|
+
const v = typedNodeOutputVar(n.id, n.type, n.outputVar, n.spec);
|
|
1189
2153
|
return [...(v ? [v] : []), ...Object.keys(n.outputs ?? {})];
|
|
1190
2154
|
}
|
|
2155
|
+
/**
|
|
2156
|
+
* Fill in `activity` on a connector task from the `operation` beside it.
|
|
2157
|
+
*
|
|
2158
|
+
* `Intsvc.ActivityExecution` must carry `activity` — the platform's required-field
|
|
2159
|
+
* rule reports `EMPTY_REQUIRED_FIELD` without it and the artifact is refused. The two
|
|
2160
|
+
* fields name the same thing: the SDK's own connector emitter writes
|
|
2161
|
+
* `op.name ?? op.objectName ?? n.action` into BOTH, so they are never independently
|
|
2162
|
+
* chosen, and `connector.ts` calls `activity` "the primary-key required field (the
|
|
2163
|
+
* selected op identity)".
|
|
2164
|
+
*
|
|
2165
|
+
* So a hand-rolled `.activity()` that sets `operation` and not `activity` has already
|
|
2166
|
+
* said which operation it means, and making the author say it twice buys nothing. This
|
|
2167
|
+
* is the same shape as the `operation` field's own history, recorded in `connector.ts`:
|
|
2168
|
+
* every connector task the SDK emitted was rejected until the emitter supplied it.
|
|
2169
|
+
*
|
|
2170
|
+
* Only the absent case is filled. An author (or an imported artifact) that states
|
|
2171
|
+
* `activity` explicitly keeps what it stated, even where the two differ.
|
|
2172
|
+
*/
|
|
2173
|
+
function operationToBorrow(type, context, rows) {
|
|
2174
|
+
if (type !== 'Intsvc.ActivityExecution')
|
|
2175
|
+
return undefined;
|
|
2176
|
+
// Context reaches a node two ways and either spelling can carry either field, so
|
|
2177
|
+
// both are read before deciding anything is missing — the same rule `check` applies.
|
|
2178
|
+
const stated = (name) => context[name] !== undefined ? context[name] : rows?.find((r) => r.name === name)?.value;
|
|
2179
|
+
if (stated('activity') !== undefined)
|
|
2180
|
+
return undefined;
|
|
2181
|
+
const operation = stated('operation');
|
|
2182
|
+
return typeof operation === 'string' && operation !== '' ? operation : undefined;
|
|
2183
|
+
}
|
|
2184
|
+
function defaultActivityIdentity(type, context, rows) {
|
|
2185
|
+
const operation = operationToBorrow(type, context, rows);
|
|
2186
|
+
// When the node spells its context out as rows, the filled-in field belongs beside
|
|
2187
|
+
// them rather than in a record the emitter would write separately.
|
|
2188
|
+
if (operation === undefined || rows?.length)
|
|
2189
|
+
return context;
|
|
2190
|
+
return { ...context, activity: operation };
|
|
2191
|
+
}
|
|
2192
|
+
function defaultActivityIdentityRows(type, context, rows) {
|
|
2193
|
+
const operation = operationToBorrow(type, context, rows);
|
|
2194
|
+
if (operation === undefined || !rows?.length)
|
|
2195
|
+
return rows;
|
|
2196
|
+
return [...rows, { name: 'activity', type: 'string', value: operation }];
|
|
2197
|
+
}
|
|
1191
2198
|
/**
|
|
1192
2199
|
* Every variable name a node DECLARES by writing to it — the platform counts a
|
|
1193
2200
|
* node's output `var` as a declaration, so a downstream `=vars.<name>` resolves
|
|
@@ -1214,8 +2221,49 @@ export function nodeDeclaredVars(n) {
|
|
|
1214
2221
|
}
|
|
1215
2222
|
/** A sub-process body: the same graph methods, plus an internal node builder. */
|
|
1216
2223
|
export class SubProcessBuilder extends ScopeBuilder {
|
|
2224
|
+
/**
|
|
2225
|
+
* Open an event sub-process body on its synthesized start event.
|
|
2226
|
+
*
|
|
2227
|
+
* @param start - The start event node.
|
|
2228
|
+
* @internal
|
|
2229
|
+
*/
|
|
2230
|
+
_begin(start) {
|
|
2231
|
+
this._nodes.push(start);
|
|
2232
|
+
this._open = [{ source: start.id, origin: `the start event "${start.id}"` }];
|
|
2233
|
+
this._started = true;
|
|
2234
|
+
this._synthesizedStart = start.id;
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Close a body written in order: every path in it must have ended.
|
|
2238
|
+
*
|
|
2239
|
+
* @param what - The scope, for the message.
|
|
2240
|
+
* @internal
|
|
2241
|
+
*/
|
|
2242
|
+
_finishBody(what) {
|
|
2243
|
+
this.closeScope(what);
|
|
2244
|
+
}
|
|
1217
2245
|
/** @internal */
|
|
1218
2246
|
_buildNode(id, opts) {
|
|
2247
|
+
// An EVENT sub-process is started by its own start event, so that start must carry a
|
|
2248
|
+
// definition; a plain sub-process is started by an incoming flow, so its start must
|
|
2249
|
+
// carry none. The platform rejects each the other way round
|
|
2250
|
+
// (START_EVENT_WITHOUT_DEFINITION_IN_EVENT_SUBPROCESS,
|
|
2251
|
+
// START_EVENT_WITH_DEFINITION_IN_SUBPROCESS). `.eventSubProcess()` always synthesizes
|
|
2252
|
+
// a definition, so this only fires on the hand-rolled `{ triggeredByEvent: true }`
|
|
2253
|
+
// form and on a plain `.subProcess()` whose start was given one.
|
|
2254
|
+
for (const n of this._nodes) {
|
|
2255
|
+
if (n.kind !== 'startEvent')
|
|
2256
|
+
continue;
|
|
2257
|
+
const hasDefinition = n.event !== undefined || n.trigger !== undefined;
|
|
2258
|
+
if (opts.triggeredByEvent === true && !hasDefinition) {
|
|
2259
|
+
throw new Error(`sub-process "${id}" is \`triggeredByEvent\`, so its start event "${n.id}" needs the event that starts it — ` +
|
|
2260
|
+
'an `error`, `message` or `timer`. `.eventSubProcess(id, { error: true }, fn)` writes that start for you.');
|
|
2261
|
+
}
|
|
2262
|
+
if (opts.triggeredByEvent !== true && hasDefinition) {
|
|
2263
|
+
throw new Error(`sub-process "${id}" is entered by an incoming flow, so its start event "${n.id}" must not carry an event ` +
|
|
2264
|
+
'definition. Drop it, or make this an event sub-process with `.eventSubProcess()`.');
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
1219
2267
|
return {
|
|
1220
2268
|
kind: 'subProcess',
|
|
1221
2269
|
id,
|
|
@@ -1232,6 +2280,149 @@ export class SubProcessBuilder extends ScopeBuilder {
|
|
|
1232
2280
|
};
|
|
1233
2281
|
}
|
|
1234
2282
|
}
|
|
2283
|
+
/**
|
|
2284
|
+
* One arm of a `choose` / `fork` / `race`, or one boundary handler's path: the same
|
|
2285
|
+
* graph methods as any scope, written INTO the enclosing scope, with consecutive
|
|
2286
|
+
* elements wired in order.
|
|
2287
|
+
*
|
|
2288
|
+
* @remarks
|
|
2289
|
+
* An arm creates no BPMN scope of its own — its elements and flows belong to the
|
|
2290
|
+
* scope the construct was written in, and variables declared here are that scope's
|
|
2291
|
+
* — so an explicit `.sequenceFlow()` may reach any element of that scope, and
|
|
2292
|
+
* `.goto()` jumps to one without leaving the nesting. Its path ends with an end
|
|
2293
|
+
* event or `.goto()`, or falls through to whatever follows the construct.
|
|
2294
|
+
*
|
|
2295
|
+
* Inside an arm, branch with `.choose()` / `.fork()` / `.race()`; a bare gateway is
|
|
2296
|
+
* refused, because its outgoing flows cannot be implied.
|
|
2297
|
+
*/
|
|
2298
|
+
export class PathBuilder extends ScopeBuilder {
|
|
2299
|
+
/** @internal The first element placed — what a `race` arm waits on. */
|
|
2300
|
+
_first;
|
|
2301
|
+
/**
|
|
2302
|
+
* @param defs - The process's message/error declarations.
|
|
2303
|
+
* @param bindings - The process's bindings.
|
|
2304
|
+
* @param storage - The enclosing scope's arrays, which this arm writes into.
|
|
2305
|
+
* @param sugar - Process-wide bookkeeping for the nesting sugar.
|
|
2306
|
+
* @param entry - The edge(s) this arm continues from.
|
|
2307
|
+
* @param scopeMode - The mode of the real scope the arm writes into, for a nested `.subProcess()` to inherit.
|
|
2308
|
+
* @internal
|
|
2309
|
+
*/
|
|
2310
|
+
constructor(defs, bindings, storage, sugar, entry, scopeMode) {
|
|
2311
|
+
super(defs, bindings, storage, 'sequence', sugar);
|
|
2312
|
+
this._isArm = true;
|
|
2313
|
+
this._scopeMode = scopeMode;
|
|
2314
|
+
this._open = entry;
|
|
2315
|
+
this._started = true;
|
|
2316
|
+
}
|
|
2317
|
+
/** @internal */
|
|
2318
|
+
place(node, viaSugar = false) {
|
|
2319
|
+
super.place(node, viaSugar);
|
|
2320
|
+
this._first ??= node;
|
|
2321
|
+
}
|
|
2322
|
+
/**
|
|
2323
|
+
* Leave this arm for an element written elsewhere — a loop back to an earlier
|
|
2324
|
+
* step, a jump into another arm, a shared exit — writing the edge without leaving
|
|
2325
|
+
* the nesting. Nothing may follow it in the arm.
|
|
2326
|
+
*
|
|
2327
|
+
* @example
|
|
2328
|
+
* **Retry from the top when the check fails**
|
|
2329
|
+
* ```ts
|
|
2330
|
+
* bpmn('retrying')
|
|
2331
|
+
* .var('ok', 'boolean')
|
|
2332
|
+
* .startEvent('start')
|
|
2333
|
+
* .scriptTask('attempt', { script: 'return { ok: true };', outputs: { ok: '=result.ok' } })
|
|
2334
|
+
* .choose('worked', [
|
|
2335
|
+
* { when: '=vars.ok == true', body: (b) => b.endEvent('done') },
|
|
2336
|
+
* { otherwise: true, body: (b) => b.goto('attempt') },
|
|
2337
|
+
* ])
|
|
2338
|
+
* .sequenceFlow('start', 'attempt')
|
|
2339
|
+
* .sequenceFlow('attempt', 'worked')
|
|
2340
|
+
* .build();
|
|
2341
|
+
* ```
|
|
2342
|
+
*
|
|
2343
|
+
* @param target - The id of the element to continue at.
|
|
2344
|
+
* @returns This builder; nothing more may be placed on this arm.
|
|
2345
|
+
*/
|
|
2346
|
+
goto(target) {
|
|
2347
|
+
if (this._closedBy !== undefined)
|
|
2348
|
+
throw new Error(`.goto("${target}") follows ${this._closedBy}; an arm leaves once.`);
|
|
2349
|
+
for (const exit of this._open)
|
|
2350
|
+
this.writeImpliedFlow(exit, target);
|
|
2351
|
+
this._open = [];
|
|
2352
|
+
this._closedBy = `.goto("${target}")`;
|
|
2353
|
+
return this;
|
|
2354
|
+
}
|
|
2355
|
+
/**
|
|
2356
|
+
* Finish the arm and hand its open ends back to the construct that owns it.
|
|
2357
|
+
*
|
|
2358
|
+
* @returns The exits still open — empty when every path ended.
|
|
2359
|
+
* @internal
|
|
2360
|
+
*/
|
|
2361
|
+
_finish() {
|
|
2362
|
+
return this._open;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
/**
|
|
2366
|
+
* The boundary events of one activity, declared on the activity itself — what the
|
|
2367
|
+
* body callback every activity method accepts receives.
|
|
2368
|
+
*
|
|
2369
|
+
* @remarks
|
|
2370
|
+
* `attachedTo` is the receiver: a handler declared here is attached to the activity
|
|
2371
|
+
* whose body it is written in, so a boundary event on nothing, or on a gateway,
|
|
2372
|
+
* cannot be written. Each handler's path is written in place, in order, and
|
|
2373
|
+
* continues as {@link TimerHandlerOpts.interrupting} describes: an interrupting
|
|
2374
|
+
* handler rejoins at the statement after the activity, a non-interrupting one must
|
|
2375
|
+
* end on its own.
|
|
2376
|
+
*
|
|
2377
|
+
* @example
|
|
2378
|
+
* **A human task with a reminder and a failure exit**
|
|
2379
|
+
* ```ts
|
|
2380
|
+
* bpmn('approval')
|
|
2381
|
+
* .var('reminded', 'string')
|
|
2382
|
+
* .startEvent('start')
|
|
2383
|
+
* .humanTask('approve', { app: 'InvoiceApproval', actions: ['Approve', 'Reject'] }, (t) => {
|
|
2384
|
+
* t.onTimer('PT1H', { interrupting: false }, (b) =>
|
|
2385
|
+
* b.task('remind', { set: { reminded: 'true' } }).endEvent('reminded'));
|
|
2386
|
+
* t.onError((b) => b.endEvent('failed', { name: 'Approval failed' }));
|
|
2387
|
+
* })
|
|
2388
|
+
* .endEvent('done')
|
|
2389
|
+
* .sequenceFlow('start', 'approve')
|
|
2390
|
+
* .sequenceFlow('approve', 'done')
|
|
2391
|
+
* .build();
|
|
2392
|
+
* ```
|
|
2393
|
+
*/
|
|
2394
|
+
export class ActivityBuilder {
|
|
2395
|
+
scope;
|
|
2396
|
+
activity;
|
|
2397
|
+
/**
|
|
2398
|
+
* @param scope - The scope the activity was placed in.
|
|
2399
|
+
* @param activity - The activity's id.
|
|
2400
|
+
* @internal
|
|
2401
|
+
*/
|
|
2402
|
+
constructor(scope, activity) {
|
|
2403
|
+
this.scope = scope;
|
|
2404
|
+
this.activity = activity;
|
|
2405
|
+
}
|
|
2406
|
+
onError(a, b, c) {
|
|
2407
|
+
const error = a === undefined || typeof a === 'function' ? true : a;
|
|
2408
|
+
const opts = b !== undefined && typeof b === 'object' ? b : {};
|
|
2409
|
+
const body = typeof a === 'function' ? a : typeof b === 'function' ? b : c;
|
|
2410
|
+
this.scope._attach(this.activity, 'onError', { event: toErrorDef(this.scope._defsOf(), error, `.onError() on "${this.activity}"`), interrupting: true, opts }, body);
|
|
2411
|
+
return this;
|
|
2412
|
+
}
|
|
2413
|
+
onTimer(timer, b, c) {
|
|
2414
|
+
const opts = b !== undefined && typeof b === 'object' ? b : {};
|
|
2415
|
+
const body = typeof b === 'function' ? b : c;
|
|
2416
|
+
this.scope._attach(this.activity, 'onTimer', { event: { type: 'timer', timer: normTimer(timer) }, interrupting: opts.interrupting ?? true, opts }, body);
|
|
2417
|
+
return this;
|
|
2418
|
+
}
|
|
2419
|
+
onMessage(message, b, c) {
|
|
2420
|
+
const opts = b !== undefined && typeof b === 'object' ? b : {};
|
|
2421
|
+
const body = typeof b === 'function' ? b : c;
|
|
2422
|
+
this.scope._attach(this.activity, 'onMessage', { event: { type: 'message', messageRef: this.scope._defsOf().messageRef(message) }, interrupting: opts.interrupting ?? true, opts }, body);
|
|
2423
|
+
return this;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
1235
2426
|
// ─── Top-level process builder ────────────────────────────────────────────────
|
|
1236
2427
|
/**
|
|
1237
2428
|
* The top-level process builder `bpmn()` returns — every graph method of the
|
|
@@ -1377,7 +2568,10 @@ export class BpmnBuilder extends ScopeBuilder {
|
|
|
1377
2568
|
propertyAttribute: opts.propertyAttribute ?? 'value',
|
|
1378
2569
|
resourceSubType: opts.resourceSubType,
|
|
1379
2570
|
default: opts.value,
|
|
1380
|
-
|
|
2571
|
+
// Left unset when not given, rather than collapsed onto `value`: the serializer
|
|
2572
|
+
// fills it — with the companion connection's key for a `folderKey` binding, and
|
|
2573
|
+
// with `value` for every other — see `folderCompanionKeys` in serialize.ts.
|
|
2574
|
+
resourceKey: opts.resourceKey,
|
|
1381
2575
|
});
|
|
1382
2576
|
return this;
|
|
1383
2577
|
}
|
|
@@ -1387,6 +2581,57 @@ export class BpmnBuilder extends ScopeBuilder {
|
|
|
1387
2581
|
* @returns The built process — its elements, flows and variables.
|
|
1388
2582
|
*/
|
|
1389
2583
|
build() {
|
|
2584
|
+
this.closeScope('the process');
|
|
2585
|
+
// A variable an `errorVar` asked for, declared the way the product declares one
|
|
2586
|
+
// — unless the author declared that id themselves, anywhere in the document.
|
|
2587
|
+
const declared = new Set();
|
|
2588
|
+
const collect = (nodes, vars) => {
|
|
2589
|
+
for (const v of vars)
|
|
2590
|
+
declared.add(v.id);
|
|
2591
|
+
for (const n of nodes)
|
|
2592
|
+
if (n.kind === 'subProcess')
|
|
2593
|
+
collect(n.nodes, n.variables);
|
|
2594
|
+
};
|
|
2595
|
+
collect(this._nodes, this._vars);
|
|
2596
|
+
// One `errorVar` id is ONE declaration, wherever the handlers asking for it sit.
|
|
2597
|
+
// A variable id is unique across the whole document — two `uipath:inputOutput`
|
|
2598
|
+
// elements sharing one id is DUPLICATE_DOCUMENT_ID, and the XML parser drops the
|
|
2599
|
+
// second — so "declare it again in the other scope" is not available.
|
|
2600
|
+
//
|
|
2601
|
+
// Declared once and scoped to whichever boundary came first, a second handler's
|
|
2602
|
+
// `=Error` row wrote a variable belonging to an element it has nothing to do with.
|
|
2603
|
+
// So a reused id widens the declaration off that boundary, and when the handlers
|
|
2604
|
+
// asking for it are in different scopes the declaration moves to the root, which
|
|
2605
|
+
// is the one scope all of them can see.
|
|
2606
|
+
const autoDecls = new Map();
|
|
2607
|
+
for (const auto of this._sugar.autoVars) {
|
|
2608
|
+
const prior = autoDecls.get(auto.id);
|
|
2609
|
+
if (prior) {
|
|
2610
|
+
// Shared: no longer one boundary's private variable.
|
|
2611
|
+
prior.decl.elementId = undefined;
|
|
2612
|
+
if (prior.vars !== auto.vars && prior.vars !== this._vars) {
|
|
2613
|
+
// Asked for from two scopes: hoist the single declaration to the root, the
|
|
2614
|
+
// only scope in view of both.
|
|
2615
|
+
prior.vars.splice(prior.vars.indexOf(prior.decl), 1);
|
|
2616
|
+
this._vars.push(prior.decl);
|
|
2617
|
+
prior.vars = this._vars;
|
|
2618
|
+
}
|
|
2619
|
+
continue;
|
|
2620
|
+
}
|
|
2621
|
+
// An id the AUTHOR declared anywhere stays theirs, scoped where they put it.
|
|
2622
|
+
if (declared.has(auto.id))
|
|
2623
|
+
continue;
|
|
2624
|
+
const decl = {
|
|
2625
|
+
id: auto.id,
|
|
2626
|
+
name: 'Error',
|
|
2627
|
+
type: 'jsonSchema',
|
|
2628
|
+
direction: 'inputOutput',
|
|
2629
|
+
elementId: auto.elementId,
|
|
2630
|
+
schema: errorSchema,
|
|
2631
|
+
};
|
|
2632
|
+
auto.vars.push(decl);
|
|
2633
|
+
autoDecls.set(auto.id, { decl, vars: auto.vars });
|
|
2634
|
+
}
|
|
1390
2635
|
const built = {
|
|
1391
2636
|
id: this._id,
|
|
1392
2637
|
name: this._name,
|