@uipath/maestro-builder-sdk 6.8.3 → 6.8.5
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/check.js +377 -118
- package/dist/core/actions.d.ts +20 -20
- package/dist/core/cli-spelling.d.ts +10 -0
- package/dist/core/cli-spelling.js +24 -0
- package/dist/core/hitl-routing.d.ts +129 -34
- package/dist/core/hitl-routing.js +97 -13
- package/dist/core/lookups.js +4 -2
- package/dist/core/step-ports.d.ts +8 -0
- package/dist/core/step-ports.js +37 -30
- package/dist/decompile.js +85 -14
- package/dist/registry/generators/markdown/sections/lookups.js +20 -5
- package/dist/registry/prepare/ledger.d.ts +3 -1
- package/dist/registry/prepare/ledger.js +55 -5
- package/dist/registry/prepare/messages.d.ts +8 -0
- package/dist/registry/prepare/messages.js +17 -0
- package/dist/registry/prepare/pipeline.js +4 -1
- package/dist/registry/prepare/resolve.d.ts +27 -2
- package/dist/registry/prepare/resolve.js +69 -7
- package/dist/serialize.js +259 -66
- package/lib/library-json.samples/index.json +18 -0
- package/lib/library-json.samples/uipath-getresponse-getresponse/send-newsletter@1.0.0.json +159 -0
- package/lib/library-json.samples/uipath-getresponse-getresponse/send-newsletter@1.0.0.v1def.json +323 -0
- package/lib/library-json.samples/uipath-salesforce-slack/create-group-direct-message@1.0.0.json +149 -0
- package/lib/library-json.samples/uipath-salesforce-slack/create-group-direct-message@1.0.0.v1def.json +237 -0
- package/package.json +2 -2
package/dist/serialize.js
CHANGED
|
@@ -14,8 +14,8 @@ import { Expr, toExpr, SCHEDULE_PRESETS, DELAY_PRESETS, parseIxpProjectType, fla
|
|
|
14
14
|
import { FlowAction, FlowTrigger } from './core/node-classes.js';
|
|
15
15
|
import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
16
16
|
import { bindingSelfNameMessage } from './core/binding-messages.js';
|
|
17
|
-
import {
|
|
18
|
-
import { declaredExits, hasExits } from './core/step-ports.js';
|
|
17
|
+
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
|
|
18
|
+
import { declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
|
|
19
19
|
import { buildConfiguration, transportHttpMethod } from './config.js';
|
|
20
20
|
import { stableId } from './core/stable-id.js';
|
|
21
21
|
import { readEventFilter, eventFilterProblem, eventFilterJmes, eventFilterTreeLeaf } from './event-filters.js';
|
|
@@ -2793,14 +2793,13 @@ function bindingPath(value, rename) {
|
|
|
2793
2793
|
// `__hitlInputBindings` the designer reads back stores the binding verbatim.
|
|
2794
2794
|
return path.startsWith('vars.') ? path : `vars.${path}`;
|
|
2795
2795
|
}
|
|
2796
|
-
/**
|
|
2796
|
+
/**
|
|
2797
|
+
* `Approve` → `approve`; a name that slugifies to nothing falls back to its index.
|
|
2798
|
+
* The same rule that names the `outcome-<id>` ports (`outcomeSlug`), so an id
|
|
2799
|
+
* this writes into `schema.outcomes` is always one a port can name.
|
|
2800
|
+
*/
|
|
2797
2801
|
function outcomeId(name, index) {
|
|
2798
|
-
|
|
2799
|
-
.trim()
|
|
2800
|
-
.toLowerCase()
|
|
2801
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
2802
|
-
.replace(/^-+|-+$/g, '');
|
|
2803
|
-
return slug || `outcome-${index + 1}`;
|
|
2802
|
+
return outcomeSlug(name, index);
|
|
2804
2803
|
}
|
|
2805
2804
|
/**
|
|
2806
2805
|
* A deterministic UUID for the form schema, derived from the flow and node ids.
|
|
@@ -3475,8 +3474,18 @@ function eventDetail(rc, sub, nodeId, opts) {
|
|
|
3475
3474
|
errorState: { issues: [] },
|
|
3476
3475
|
};
|
|
3477
3476
|
}
|
|
3478
|
-
/**
|
|
3477
|
+
/**
|
|
3478
|
+
* Recursively render Exprs nested inside objects/arrays (for connector inputs).
|
|
3479
|
+
*
|
|
3480
|
+
* A lookup token is REFUSED, never rendered. `buildConnectorInputs` swaps every
|
|
3481
|
+
* token it can place — a field's whole value, or an element of an array — so a
|
|
3482
|
+
* token that reaches here sits where nothing resolves it: inside an object value,
|
|
3483
|
+
* or in a custom node's inputs. Recursing into it would put `{"__lookup": …}` on
|
|
3484
|
+
* the wire where the connector expects an id.
|
|
3485
|
+
*/
|
|
3479
3486
|
function renderDeep(val, rename = NO_RENAME) {
|
|
3487
|
+
if (isLookupToken(val))
|
|
3488
|
+
throw new Error(misplacedLookupMessage(val));
|
|
3480
3489
|
if (val instanceof Expr)
|
|
3481
3490
|
return renderValue(val, rename);
|
|
3482
3491
|
if (Array.isArray(val))
|
|
@@ -3489,6 +3498,13 @@ function renderDeep(val, rename = NO_RENAME) {
|
|
|
3489
3498
|
}
|
|
3490
3499
|
return val;
|
|
3491
3500
|
}
|
|
3501
|
+
/** Why {@link renderDeep} refused a token: where a lookup CAN go, and what it would have emitted. */
|
|
3502
|
+
function misplacedLookupMessage(token) {
|
|
3503
|
+
const { field, by, value } = token.__lookup;
|
|
3504
|
+
return (`lookup('${field}') (${by}=${JSON.stringify(value)}) is in a position no lookup is resolved from: ` +
|
|
3505
|
+
`a lookup() must be the whole value of a connector input, or an element of an array input. ` +
|
|
3506
|
+
`Emitting it here would send a raw {"__lookup": …} object where the connector expects an id.`);
|
|
3507
|
+
}
|
|
3492
3508
|
function newScope(outputs, vars = []) {
|
|
3493
3509
|
return {
|
|
3494
3510
|
nodes: [],
|
|
@@ -3859,7 +3875,28 @@ export function serialize(built, opts = {}) {
|
|
|
3859
3875
|
scope.stepNodeId.set(stepName, id);
|
|
3860
3876
|
return id;
|
|
3861
3877
|
}
|
|
3878
|
+
/**
|
|
3879
|
+
* Base and quick-form human tasks emitted so far, by node id. `addEdge` holds
|
|
3880
|
+
* every edge out of one to the product's handle rule (`hitlExitViolation`), so
|
|
3881
|
+
* `completed` on a task with outcomes fails at the edge that would write it;
|
|
3882
|
+
* `assertHitlExitInvariant` repeats the check over each finished scope for the
|
|
3883
|
+
* edges that do not come through here (parked `.stepToRef` rejoins).
|
|
3884
|
+
*/
|
|
3885
|
+
const hitlNodes = new Map();
|
|
3886
|
+
/**
|
|
3887
|
+
* Human tasks with an outcome whose path stops at an End while another
|
|
3888
|
+
* outcome continues — read by `.parallel`, whose join would wait forever for
|
|
3889
|
+
* the continuing path (check's HITL_END_OUTCOME_IN_PARALLEL). Two ways in: a
|
|
3890
|
+
* fan-out task's `action: 'End'` outcome, and a `.stepSwitch` outcome with no
|
|
3891
|
+
* arm (it gets an End) or whose arm ends in `.return()`. `why` finishes the
|
|
3892
|
+
* sentence "human task X …" in the refusal.
|
|
3893
|
+
*/
|
|
3894
|
+
const hitlEndExits = [];
|
|
3862
3895
|
function addEdge(scope, from, toNode, toPort = 'input') {
|
|
3896
|
+
const hitlNode = hitlNodes.get(from.node);
|
|
3897
|
+
const refused = hitlNode ? hitlExitViolation(hitlNode, from.port) : undefined;
|
|
3898
|
+
if (refused)
|
|
3899
|
+
throw new Error(`serialize: ${refused}`);
|
|
3863
3900
|
scope.edges.push({ id: `edge_${edgeSeq++}`, sourceNodeId: from.node, sourcePort: from.port, targetNodeId: toNode, targetPort: toPort });
|
|
3864
3901
|
}
|
|
3865
3902
|
let col = 0;
|
|
@@ -3959,6 +3996,7 @@ export function serialize(built, opts = {}) {
|
|
|
3959
3996
|
for (const vd of child.vars)
|
|
3960
3997
|
globals.push({ id: vd.name, name: vd.name, direction: 'inout', type: vd.type, ...varMeta(vd) });
|
|
3961
3998
|
resolveRejoins(scope);
|
|
3999
|
+
assertHitlExitInvariant(scope.nodes, scope.edges);
|
|
3962
4000
|
typeAgentInputSchemas(scope, globals);
|
|
3963
4001
|
emitStickyNotes(child, scope);
|
|
3964
4002
|
const layoutEdges = applyAuthorLayout(child.layout, scope, `subflow "${nodeId}"`);
|
|
@@ -4077,13 +4115,11 @@ export function serialize(built, opts = {}) {
|
|
|
4077
4115
|
// the shipped `sample-flows/queue-operations.flow` wires its edges to it.
|
|
4078
4116
|
if (type === NODE_TYPE.queueCreate || type === NODE_TYPE.queueCreateAndWait)
|
|
4079
4117
|
return 'success';
|
|
4080
|
-
//
|
|
4081
|
-
//
|
|
4082
|
-
//
|
|
4083
|
-
//
|
|
4084
|
-
//
|
|
4085
|
-
// — and all three human-task node types declare the same one, so the port is
|
|
4086
|
-
// the same whichever `variant` was named.
|
|
4118
|
+
// Every human-task definition declares one static source handle, `completed`.
|
|
4119
|
+
// It is the real exit only where the product draws it: action-app,
|
|
4120
|
+
// document-validation, and a task with no outcomes. A base or quick-form task
|
|
4121
|
+
// with outcomes leaves on `outcome-<id>` instead (`hitlExitModel`), and never
|
|
4122
|
+
// reaches this function for its tail.
|
|
4087
4123
|
if (type === NODE_TYPE.hitl || type === NODE_TYPE.hitlQuickForm || type === NODE_TYPE.hitlActionApp
|
|
4088
4124
|
|| type === NODE_TYPE.hitlDocValidation)
|
|
4089
4125
|
return 'completed';
|
|
@@ -5094,28 +5130,31 @@ export function serialize(built, opts = {}) {
|
|
|
5094
5130
|
if (spec.kind === 'hitl') {
|
|
5095
5131
|
const id = stepUid(scope, step.name);
|
|
5096
5132
|
const hitlType = hitlNodeType(spec.inputs);
|
|
5097
|
-
//
|
|
5098
|
-
//
|
|
5099
|
-
// `outcome-<id>` exits) and `exposeError` needs 1.2 (adds the `error`
|
|
5100
|
-
// output; its exits are the same per-outcome handles). A zero- or
|
|
5101
|
-
// one-outcome task stays on the pinned default, byte-identically.
|
|
5133
|
+
// How the task leaves its node — `hitlExitModel` (core/hitl-routing.ts)
|
|
5134
|
+
// has the product rule and what `.step()` means after each shape.
|
|
5102
5135
|
//
|
|
5103
|
-
//
|
|
5104
|
-
//
|
|
5105
|
-
//
|
|
5136
|
+
// Only the BASE node has definition versions to select: routing per
|
|
5137
|
+
// outcome picks 1.1 and `exposeError` picks 1.2 (which adds the `error`
|
|
5138
|
+
// output). quick-form draws the same per-outcome handles on the one
|
|
5139
|
+
// definition it has, so it routes per outcome without moving. An explicit
|
|
5140
|
+
// pin wins over the inferred 1.1 — `flow validate` accepts `outcome-<id>`
|
|
5141
|
+
// edges on base@1.0 — but not over `exposeError`, whose output only 1.2
|
|
5142
|
+
// declares. A zero- or one-outcome task stays on the pinned default.
|
|
5106
5143
|
const pinned = step.options?.version;
|
|
5107
|
-
const
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
+ `definition — the '${spec.inputs?.variant}' variant has no per-outcome version. `
|
|
5112
|
-
+ `Drop the variant, or route on out('${step.name}', 'Action') downstream.`);
|
|
5144
|
+
const exitModel = hitlExitModel(spec.inputs, pinned);
|
|
5145
|
+
if (hitlType !== NODE_TYPE.hitl && spec.inputs?.exposeError === true) {
|
|
5146
|
+
throw new Error(`"${step.name}": exposeError needs the base human task's 1.2 definition, which adds the `
|
|
5147
|
+
+ `error output — the '${spec.inputs?.variant}' variant has no such version. Drop the variant or exposeError.`);
|
|
5113
5148
|
}
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5149
|
+
if (spec.inputs?.outcomePorts === true && !OUTCOME_HANDLE_NODE_TYPES.has(hitlType)) {
|
|
5150
|
+
throw new Error(`"${step.name}": outcomePorts: true, but the '${spec.inputs?.variant}' variant reads its outcomes `
|
|
5151
|
+
+ `from its app and leaves on its single 'completed' exit. Route on out('${step.name}', 'Action') downstream.`);
|
|
5152
|
+
}
|
|
5153
|
+
const neededVersion = hitlType !== NODE_TYPE.hitl ? undefined
|
|
5154
|
+
: spec.inputs?.exposeError ? '1.2' : exitModel.shape === 'per-outcome' ? '1.1' : undefined;
|
|
5155
|
+
if (spec.inputs?.exposeError === true && pinned !== undefined && pinned !== '1.2') {
|
|
5156
|
+
throw new Error(`"${step.name}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
|
|
5157
|
+
+ `Drop the pin or match it.`);
|
|
5119
5158
|
}
|
|
5120
5159
|
const node = makeNode(hitlType, id, step.name, { parentId, requestedVersion: pinned ?? neededVersion });
|
|
5121
5160
|
const { inputs, outputs } = hitlInputs(spec.inputs, id, built.id, step.name, scope.rename);
|
|
@@ -5124,15 +5163,19 @@ export function serialize(built, opts = {}) {
|
|
|
5124
5163
|
// gives their shape (see hitlInputs), so the instance's version wins.
|
|
5125
5164
|
node.outputs = outputs;
|
|
5126
5165
|
scope.nodes.push(node);
|
|
5166
|
+
if (OUTCOME_HANDLE_NODE_TYPES.has(hitlType))
|
|
5167
|
+
hitlNodes.set(id, node);
|
|
5127
5168
|
for (const t of tails)
|
|
5128
5169
|
addEdge(scope, t, id);
|
|
5129
|
-
if (
|
|
5130
|
-
//
|
|
5131
|
-
//
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5170
|
+
if (exitModel.shape === 'per-outcome') {
|
|
5171
|
+
// The PRIMARY (first) outcome continues the main path; the author routes
|
|
5172
|
+
// the rest with .stepToList('outcome-<id>', …) or as .stepSwitch arms.
|
|
5173
|
+
return [{ node: id, port: exitModel.exits[0]?.port ?? `outcome-${outcomeSlug(undefined, 0)}` }];
|
|
5174
|
+
}
|
|
5175
|
+
if (exitModel.shape === 'every-outcome') {
|
|
5176
|
+
// Fan-out: every outcome continues to the next step, each on its own
|
|
5177
|
+
// handle. End outcomes are marked, and `emit` ends them.
|
|
5178
|
+
return exitModel.exits.map((e) => ({ node: id, port: e.port, ...(e.endsProcess ? { endsRun: true } : {}) }));
|
|
5136
5179
|
}
|
|
5137
5180
|
return [{ node: id, port: successPort(hitlType) }];
|
|
5138
5181
|
}
|
|
@@ -5370,6 +5413,34 @@ export function serialize(built, opts = {}) {
|
|
|
5370
5413
|
* are backward — every deployed rejoin is forward convergence, which is why
|
|
5371
5414
|
* `check` refuses a backward one outright.
|
|
5372
5415
|
*/
|
|
5416
|
+
/**
|
|
5417
|
+
* An End node on one exit of `nodeId`, so the run FINISHES there — a
|
|
5418
|
+
* `.stepSwitch` exit with no arm, and a fan-out human task's `action: 'End'`
|
|
5419
|
+
* outcome.
|
|
5420
|
+
*/
|
|
5421
|
+
function endExit(scope, nodeId, port, stepName, parentId) {
|
|
5422
|
+
const endId = uid(scope, `${stepName}${port.charAt(0).toUpperCase()}${port.slice(1)}End`);
|
|
5423
|
+
const end = makeNode(NODE_TYPE.end, endId, 'End', parentId ? { parentId } : {});
|
|
5424
|
+
end.inputs = {};
|
|
5425
|
+
scope.nodes.push(end);
|
|
5426
|
+
addEdge(scope, { node: nodeId, port }, endId);
|
|
5427
|
+
}
|
|
5428
|
+
/**
|
|
5429
|
+
* Refuse a side exit on a human task's port that the step's tails already
|
|
5430
|
+
* continue from: that would put two edges on one port (flow-builder-sdk#741).
|
|
5431
|
+
* After a fan-out task that is EVERY outcome port. `check` reports the same
|
|
5432
|
+
* as HITL_OUTCOME_PORTS_OFF; this is the compile-side half.
|
|
5433
|
+
*/
|
|
5434
|
+
function refuseRewire(method, port, source, tails) {
|
|
5435
|
+
if (!hitlNodes.has(tails[0]?.node ?? '') || !tails.some((t) => t.port === port))
|
|
5436
|
+
return;
|
|
5437
|
+
throw new Error(tails.length > 1
|
|
5438
|
+
? `${method}('${port}', …) after human task "${source}": every outcome already continues to the next `
|
|
5439
|
+
+ `step, '${port}' included, because this task does not route per outcome. Add outcomePorts: true `
|
|
5440
|
+
+ `(the first outcome then continues and the others are routed), or route every outcome with .stepSwitch.`
|
|
5441
|
+
: `${method}('${port}', …) after human task "${source}": '${port}' already continues the main path, so `
|
|
5442
|
+
+ `routing it here too would put two edges on one port (flow-builder-sdk#741).`);
|
|
5443
|
+
}
|
|
5373
5444
|
/**
|
|
5374
5445
|
* A step list hanging off a named PORT of the step just emitted.
|
|
5375
5446
|
*
|
|
@@ -5379,12 +5450,14 @@ export function serialize(built, opts = {}) {
|
|
|
5379
5450
|
*/
|
|
5380
5451
|
function emitPortList(step, source, tails, scope, parentId, breakTarget) {
|
|
5381
5452
|
const handler = step.body;
|
|
5382
|
-
// `emitAction` returns
|
|
5383
|
-
//
|
|
5384
|
-
//
|
|
5385
|
-
|
|
5386
|
-
|
|
5453
|
+
// `emitAction` returns one tail for every action kind — the node's own
|
|
5454
|
+
// success port — except a fan-out human task, which returns one per outcome
|
|
5455
|
+
// from the SAME node. Tails from two nodes mean a new action shape reached
|
|
5456
|
+
// here without being taught about side exits; say so rather than guess.
|
|
5457
|
+
if (tails.length === 0 || tails.some((t) => t.node !== tails[0].node)) {
|
|
5458
|
+
throw new Error(`.stepToList('${step.port}', …) after step "${source}": expected the step to leave from exactly one node, got ${tails.length} tail(s).`);
|
|
5387
5459
|
}
|
|
5460
|
+
refuseRewire('.stepToList', step.port, source, tails);
|
|
5388
5461
|
const nodeId = tails[0].node;
|
|
5389
5462
|
const node = scope.nodes.find((n) => n.id === nodeId);
|
|
5390
5463
|
if (!node)
|
|
@@ -5451,12 +5524,33 @@ export function serialize(built, opts = {}) {
|
|
|
5451
5524
|
throw new Error(`label on "${step.name}": emitted node not found.`);
|
|
5452
5525
|
node.display = { ...(node.display ?? {}), label };
|
|
5453
5526
|
};
|
|
5454
|
-
for (const step of steps) {
|
|
5527
|
+
for (const [stepIndex, step] of steps.entries()) {
|
|
5455
5528
|
if (step.kind === 'action') {
|
|
5456
5529
|
tails = emitAction(step, tails, scope, parentId);
|
|
5457
5530
|
applyUpdates(step);
|
|
5458
5531
|
applyLabel(step);
|
|
5459
5532
|
lastAction = step.name;
|
|
5533
|
+
// A fan-out human task's `action: 'End'` outcomes end the run here: the
|
|
5534
|
+
// flow runtime never reads `action`, so only an End node can. When the
|
|
5535
|
+
// very next step is a `.return()` they join it instead — it is an End
|
|
5536
|
+
// already, and it writes the flow's return values on that path too.
|
|
5537
|
+
if (tails.some((t) => t.endsRun)) {
|
|
5538
|
+
// Side exits attach to the task; the step that CONTINUES it is the
|
|
5539
|
+
// first one after them (check's HITL_UNREACHABLE_AFTER reads the same).
|
|
5540
|
+
const joinsReturn = steps.slice(stepIndex + 1)
|
|
5541
|
+
.find((x) => x.kind !== 'stepToList' && x.kind !== 'stepToRef')?.kind === 'return';
|
|
5542
|
+
const continuing = [];
|
|
5543
|
+
for (const t of tails) {
|
|
5544
|
+
if (t.endsRun && !joinsReturn)
|
|
5545
|
+
endExit(scope, t.node, t.port, step.name, parentId);
|
|
5546
|
+
else
|
|
5547
|
+
continuing.push({ node: t.node, port: t.port });
|
|
5548
|
+
}
|
|
5549
|
+
if (!joinsReturn && continuing.length > 0) {
|
|
5550
|
+
hitlEndExits.push({ scope, parentId, step: step.name, why: "ends the run on an action: 'End' outcome" });
|
|
5551
|
+
}
|
|
5552
|
+
tails = continuing;
|
|
5553
|
+
}
|
|
5460
5554
|
}
|
|
5461
5555
|
else if (step.kind === 'stepToList') {
|
|
5462
5556
|
tails = emitPortList(step, lastAction ?? '?', tails, scope, parentId, breakTarget);
|
|
@@ -5489,6 +5583,9 @@ export function serialize(built, opts = {}) {
|
|
|
5489
5583
|
throw new Error(`.stepSwitch("${step.name}"): emitted node not found.`);
|
|
5490
5584
|
const armTails = [];
|
|
5491
5585
|
const routed = new Set();
|
|
5586
|
+
// Outcomes of a human task whose path stops at an End: an arm that ends
|
|
5587
|
+
// in `.return()` here, an outcome with no arm below.
|
|
5588
|
+
const stopped = [];
|
|
5492
5589
|
for (const c of step.cases) {
|
|
5493
5590
|
const exit = exits.find((e) => e.value === c.value);
|
|
5494
5591
|
if (!exit) {
|
|
@@ -5501,7 +5598,12 @@ export function serialize(built, opts = {}) {
|
|
|
5501
5598
|
// from all of them. (`.stepToList` instead gives each arm a private
|
|
5502
5599
|
// End, which silently drops the flow's outputs on that path —
|
|
5503
5600
|
// flow-builder-sdk#742.)
|
|
5504
|
-
|
|
5601
|
+
const caseTails = emit(c.body, [{ node: nodeId, port: exit.port }], scope, parentId, breakTarget);
|
|
5602
|
+
if (caseTails.length === 0 && stopsAtEnd(c.body))
|
|
5603
|
+
stopped.push(String(c.value));
|
|
5604
|
+
armTails.push(...(hitlNodes.has(nodeId)
|
|
5605
|
+
? caseTails.map((t) => (t.humanSwitch ? t : { ...t, humanSwitch: step.name }))
|
|
5606
|
+
: caseTails));
|
|
5505
5607
|
}
|
|
5506
5608
|
// A declared exit with no arm gets an End, so the run FINISHES there
|
|
5507
5609
|
// rather than reaching a port with no edge and stalling. `check` warns
|
|
@@ -5516,13 +5618,20 @@ export function serialize(built, opts = {}) {
|
|
|
5516
5618
|
// `End`. What the action changes is the WARNING, not the edge: nobody
|
|
5517
5619
|
// needs to be told to write an arm for an outcome that ends the run.
|
|
5518
5620
|
for (const e of exits) {
|
|
5519
|
-
if (routed.has(e.port))
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5621
|
+
if (!routed.has(e.port)) {
|
|
5622
|
+
endExit(scope, nodeId, e.port, step.name, parentId);
|
|
5623
|
+
stopped.push(String(e.value));
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
// On a human task those Ends sit beside the arms that continue, and only
|
|
5627
|
+
// one outcome fires. Inside a `.parallel` arm the join then waits
|
|
5628
|
+
// forever for the continuing path, as for an `action: 'End'` outcome.
|
|
5629
|
+
if (hitlNodes.has(nodeId) && stopped.length > 0 && armTails.length > 0) {
|
|
5630
|
+
hitlEndExits.push({
|
|
5631
|
+
scope, parentId, step: step.name,
|
|
5632
|
+
why: `stops at an End on outcome${stopped.length === 1 ? '' : 's'} ${stopped.join(', ')} of its `
|
|
5633
|
+
+ `.stepSwitch (no arm, or an arm that ends in .return())`,
|
|
5634
|
+
});
|
|
5526
5635
|
}
|
|
5527
5636
|
tails = armTails;
|
|
5528
5637
|
lastAction = step.name;
|
|
@@ -5590,9 +5699,49 @@ export function serialize(built, opts = {}) {
|
|
|
5590
5699
|
// tail and so contributes no incoming edge; that is rejected at rung 1
|
|
5591
5700
|
// too (PARALLEL_ARM_RETURNS), since a BPMN join waits for every branch —
|
|
5592
5701
|
// UNLESS no arm reaches the join at all, which is the next paragraph.
|
|
5702
|
+
// The join waits for a token on EVERY incoming edge, and a fan-out human
|
|
5703
|
+
// task's outcome edges are mutually exclusive — only one fires. So two
|
|
5704
|
+
// of them straight into the join, or an End outcome beside a continuing
|
|
5705
|
+
// one, hang the run with no incident. check reports both
|
|
5706
|
+
// (HITL_FAN_OUT_INTO_JOIN, HITL_END_OUTCOME_IN_PARALLEL); this is the
|
|
5707
|
+
// compile-side half, and it fails closed.
|
|
5593
5708
|
const armTails = [];
|
|
5594
|
-
|
|
5595
|
-
|
|
5709
|
+
const endsBefore = hitlEndExits.length;
|
|
5710
|
+
for (const arm of step.arms) {
|
|
5711
|
+
const t = emit(arm, tails, scope, parentId, breakTarget);
|
|
5712
|
+
// An arm hands the join ONE edge, or several that are alternatives —
|
|
5713
|
+
// `.branch` / `.switch` / `.stepSwitch` arms, a fan-out task's
|
|
5714
|
+
// outcomes — of which only one fires. Refused when any of them is a
|
|
5715
|
+
// human task's outcome edge: however the task got there (at the arm's
|
|
5716
|
+
// end, one empty `.stepSwitch` arm beside a body, one side of a
|
|
5717
|
+
// `.branch`), the join waits for all of them. Rejoining arms with no
|
|
5718
|
+
// human task in them are the older, general case (still emitted).
|
|
5719
|
+
const outcomeTail = t.find((x) => hitlNodes.has(x.node) && x.port.startsWith('outcome-'));
|
|
5720
|
+
// The same holds when each outcome's arm ended on a step of its own:
|
|
5721
|
+
// a complete `.stepSwitch` with nothing after it hands the join one
|
|
5722
|
+
// edge per arm, and still only one arm runs.
|
|
5723
|
+
const switchTail = outcomeTail ? undefined : t.find((x) => x.humanSwitch !== undefined);
|
|
5724
|
+
if (t.length >= 2 && switchTail) {
|
|
5725
|
+
throw new Error(`.parallel("${step.name}"): an arm sends ${t.length} alternative edges into join "${step.name}" — `
|
|
5726
|
+
+ `among them the end of an arm of human task "${switchTail.humanSwitch}"'s .stepSwitch — and only one `
|
|
5727
|
+
+ `of them fires, while the join waits for all of them. Add a step at the end of the arm, after the `
|
|
5728
|
+
+ `.stepSwitch, so its arms meet there first, or move the task out of the .parallel.`);
|
|
5729
|
+
}
|
|
5730
|
+
if (t.length >= 2 && outcomeTail) {
|
|
5731
|
+
throw new Error(`.parallel("${step.name}"): an arm sends ${t.length} alternative edges into join "${step.name}" — `
|
|
5732
|
+
+ `among them outcome edge "${outcomeTail.port}" of human task "${outcomeTail.node}" — and only one `
|
|
5733
|
+
+ `of them fires, while the join waits for all of them. Add a step at the end of the arm so its paths `
|
|
5734
|
+
+ `meet there first, or move the task out of the .parallel.`);
|
|
5735
|
+
}
|
|
5736
|
+
armTails.push(...t);
|
|
5737
|
+
}
|
|
5738
|
+
const starved = hitlEndExits.slice(endsBefore).find((x) => x.scope === scope && x.parentId === parentId);
|
|
5739
|
+
if (starved && armTails.length > 0) {
|
|
5740
|
+
throw new Error(`.parallel("${step.name}"): human task "${starved.step}" ${starved.why} inside an arm, and join `
|
|
5741
|
+
+ `"${step.name}" would wait forever for the path that continues. Give every outcome a path that `
|
|
5742
|
+
+ `continues to the end of the arm (drop action: 'End', add the missing .stepSwitch arm, move the `
|
|
5743
|
+
+ `.return() after the .parallel), or move the task out of the .parallel.`);
|
|
5744
|
+
}
|
|
5596
5745
|
// When EVERY arm is terminal — a terminate, or an End — there is nothing
|
|
5597
5746
|
// left to join, and emitting a Merge would add a node with no incoming
|
|
5598
5747
|
// edge, which is unreachable. The platform's own flows agree:
|
|
@@ -5714,6 +5863,7 @@ export function serialize(built, opts = {}) {
|
|
|
5714
5863
|
const from = tails[0];
|
|
5715
5864
|
if (!from)
|
|
5716
5865
|
throw new Error(`.stepToRef('${step.port}', '${step.target}') has no step to leave from.`);
|
|
5866
|
+
refuseRewire('.stepToRef', step.port, lastAction ?? '?', tails);
|
|
5717
5867
|
if (step.port === 'error') {
|
|
5718
5868
|
const node = scope.nodes.find((n) => n.id === from.node);
|
|
5719
5869
|
if (node)
|
|
@@ -5866,6 +6016,7 @@ export function serialize(built, opts = {}) {
|
|
|
5866
6016
|
}
|
|
5867
6017
|
}
|
|
5868
6018
|
resolveRejoins(root);
|
|
6019
|
+
assertHitlExitInvariant(root.nodes, root.edges);
|
|
5869
6020
|
typeAgentInputSchemas(root, globals);
|
|
5870
6021
|
emitStickyNotes(built, root);
|
|
5871
6022
|
const rootLayoutEdges = applyAuthorLayout(built.layout, root, `flow "${built.id}"`);
|
|
@@ -5984,16 +6135,35 @@ function buildConnectorInputs(rc, inputs, rename = NO_RENAME, resolutions = {},
|
|
|
5984
6135
|
// NOT rendered as a placeholder: emitting something for a value nobody
|
|
5985
6136
|
// resolved would produce a flow that compiles and then talks to the wrong
|
|
5986
6137
|
// record, which is exactly the failure the lookup surface exists to remove.
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
6138
|
+
//
|
|
6139
|
+
// A token is swapped wherever the field's value can hold one: the whole
|
|
6140
|
+
// value, or an element of an array at any depth. A multiselect is
|
|
6141
|
+
// `users: [lookup(…), lookup(…)]` — the form `ConnectorLookupValue`'s array
|
|
6142
|
+
// branch types and the generated `## Example` prints — and until this swapped
|
|
6143
|
+
// only the whole-value case, so every token inside the array reached
|
|
6144
|
+
// validation as an object ("expected string, got object") with every lookup
|
|
6145
|
+
// recorded. An unresolved element under 'ignore' becomes `null`, which
|
|
6146
|
+
// validation skips, so `check` still type-checks the resolved siblings.
|
|
6147
|
+
const unresolved = [];
|
|
6148
|
+
const swapped = swapLookupTokens(nested.value, resolutions, unresolved);
|
|
6149
|
+
if (unresolved.length) {
|
|
6150
|
+
if (policy.unresolvedLookups !== 'ignore') {
|
|
6151
|
+
for (const token of unresolved)
|
|
6152
|
+
errs.push(unresolvedLookupMessage(token));
|
|
5992
6153
|
continue;
|
|
5993
6154
|
}
|
|
5994
|
-
|
|
6155
|
+
if (isLookupToken(nested.value))
|
|
6156
|
+
continue;
|
|
5995
6157
|
}
|
|
5996
|
-
|
|
6158
|
+
nested = { ...nested, value: swapped };
|
|
6159
|
+
// A FLAT key whose wire name ends in `[*]` names one array: an array value
|
|
6160
|
+
// there is checked element by element, as the nested form is. A flat scalar
|
|
6161
|
+
// keeps depth 0 and is emitted as the scalar it is — the product wraps it
|
|
6162
|
+
// into a one-element list (flow-v1 `convertDotNotationToNested`).
|
|
6163
|
+
const depth = flat
|
|
6164
|
+
? (f.name.endsWith('[*]') && Array.isArray(nested.value) ? 1 : 0)
|
|
6165
|
+
: nested.projectedArrays;
|
|
6166
|
+
validateConnectorFieldValue(f, nested.value, depth, errs);
|
|
5997
6167
|
if (f.location === 'multipart') {
|
|
5998
6168
|
partValues.set(f.name, renderDeep(nested.value, rename));
|
|
5999
6169
|
continue;
|
|
@@ -6061,6 +6231,29 @@ function buildConnectorInputs(rc, inputs, rename = NO_RENAME, resolutions = {},
|
|
|
6061
6231
|
}
|
|
6062
6232
|
return slots;
|
|
6063
6233
|
}
|
|
6234
|
+
/**
|
|
6235
|
+
* `value` with every lookup token swapped for its recorded id: the value itself,
|
|
6236
|
+
* or the elements of an array at any depth. An unrecorded token is pushed to
|
|
6237
|
+
* `unresolved` and stands as `null`, so an array keeps its length.
|
|
6238
|
+
*
|
|
6239
|
+
* Objects are NOT descended: a lookup's place is a field's value or an element
|
|
6240
|
+
* of an array field (`ConnectorLookupValue`), and a nested path such as
|
|
6241
|
+
* `contacts[*].id` reaches here already projected to its leaf array. A token
|
|
6242
|
+
* anywhere else is refused by {@link renderDeep}.
|
|
6243
|
+
*/
|
|
6244
|
+
function swapLookupTokens(value, resolutions, unresolved) {
|
|
6245
|
+
if (isLookupToken(value)) {
|
|
6246
|
+
const resolved = resolvedValue(value, resolutions);
|
|
6247
|
+
if (resolved === undefined) {
|
|
6248
|
+
unresolved.push(value);
|
|
6249
|
+
return null;
|
|
6250
|
+
}
|
|
6251
|
+
return resolved;
|
|
6252
|
+
}
|
|
6253
|
+
if (Array.isArray(value))
|
|
6254
|
+
return value.map((item) => swapLookupTokens(item, resolutions, unresolved));
|
|
6255
|
+
return value;
|
|
6256
|
+
}
|
|
6064
6257
|
/**
|
|
6065
6258
|
* `inputMetadata.multipart.bodyFieldName` names the CONTAINER part — the one the
|
|
6066
6259
|
* runtime composes from `bodyParameters`. Measured on the designer's own nodes in
|
|
@@ -100,6 +100,15 @@
|
|
|
100
100
|
"objectName": "create_issue",
|
|
101
101
|
"path": "uipath-atlassian-jira/generic/create_issue/create@1.0.0.json"
|
|
102
102
|
},
|
|
103
|
+
{
|
|
104
|
+
"nodeType": "uipath.connector.uipath-getresponse-getresponse.send-newsletter",
|
|
105
|
+
"version": "1.0.0",
|
|
106
|
+
"connectorKey": "uipath-getresponse-getresponse",
|
|
107
|
+
"label": "Send Newsletter",
|
|
108
|
+
"activityType": "Curated",
|
|
109
|
+
"objectName": "NewslettersSend-draft",
|
|
110
|
+
"path": "uipath-getresponse-getresponse/send-newsletter@1.0.0.json"
|
|
111
|
+
},
|
|
103
112
|
{
|
|
104
113
|
"nodeType": "uipath.connector.uipath-google-vertex.execute-google-vertex-agent",
|
|
105
114
|
"version": "1.0.0",
|
|
@@ -136,6 +145,15 @@
|
|
|
136
145
|
"objectName": "triggerFlow",
|
|
137
146
|
"path": "uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.json"
|
|
138
147
|
},
|
|
148
|
+
{
|
|
149
|
+
"nodeType": "uipath.connector.uipath-salesforce-slack.create-group-direct-message",
|
|
150
|
+
"version": "1.0.0",
|
|
151
|
+
"connectorKey": "uipath-salesforce-slack",
|
|
152
|
+
"label": "Create Group Direct Message",
|
|
153
|
+
"activityType": "Curated",
|
|
154
|
+
"objectName": "conversations_open",
|
|
155
|
+
"path": "uipath-salesforce-slack/create-group-direct-message@1.0.0.json"
|
|
156
|
+
},
|
|
139
157
|
{
|
|
140
158
|
"nodeType": "uipath.connector.uipath-salesforce-slack.send-message-to-user",
|
|
141
159
|
"version": "1.0.0",
|