@uipath/maestro-builder-sdk 6.60.0 → 6.61.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/README.md +10 -12
- package/dist/api-index.md +5 -4
- package/dist/check-cli.js +8 -7
- package/dist/check.d.ts +11 -0
- package/dist/check.js +192 -783
- package/dist/cli-library.d.ts +2 -1
- package/dist/cli-library.js +7 -2
- package/dist/cli-result.d.ts +14 -0
- package/dist/cli-result.js +18 -1
- package/dist/compile-cli.js +2 -2
- package/dist/core/compile-refusal.d.ts +7 -1
- package/dist/core/compile-refusal.js +9 -1
- package/dist/core/connector-checks.d.ts +8 -30
- package/dist/core/connector-checks.js +11 -60
- package/dist/core/connector-inputs.js +9 -1
- package/dist/core/event-checks.d.ts +15 -2
- package/dist/core/event-checks.js +28 -13
- package/dist/core/hitl-routing.d.ts +10 -1
- package/dist/core/hitl-routing.js +20 -5
- package/dist/core/lookups.d.ts +13 -0
- package/dist/core/lookups.js +26 -0
- package/dist/core/refusals.d.ts +2 -0
- package/dist/core/refusals.js +5 -1
- package/dist/flow-node-type.d.ts +35 -21
- package/dist/flow-parallel-hitl.d.ts +105 -0
- package/dist/flow-parallel-hitl.js +214 -0
- package/dist/flow-sdk.d.ts +4 -4
- package/dist/flow-sdk.js +2 -2
- package/dist/flow-terminate-names.d.ts +2 -2
- package/dist/flow-terminate-names.js +2 -2
- package/dist/merge-compile.js +2 -1
- package/dist/nodes/agent/flow/check.js +13 -88
- package/dist/nodes/agent/flow/inline-agent.js +41 -14
- package/dist/nodes/connector/flow/node-type.js +45 -18
- package/dist/nodes/document/flow/ixp.d.ts +1 -1
- package/dist/nodes/document/flow/ixp.js +22 -26
- package/dist/nodes/hitl/flow/check.js +4 -11
- package/dist/nodes/hitl/flow/node-type.js +16 -7
- package/dist/nodes/http/flow/check.js +5 -31
- package/dist/nodes/http/flow/node-type.js +7 -4
- package/dist/nodes/queue/flow/check.d.ts +4 -3
- package/dist/nodes/queue/flow/check.js +7 -6
- package/dist/nodes/queue/flow/node-type.js +1 -1
- package/dist/nodes/transform/flow/check.d.ts +2 -0
- package/dist/nodes/transform/flow/check.js +3 -6
- package/dist/nodes/transform/flow/node-type.js +6 -6
- package/dist/raw-node.js +8 -8
- package/dist/serialize.d.ts +38 -3
- package/dist/serialize.js +757 -322
- package/package.json +2 -2
package/dist/core/refusals.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* stops `compile` from writing. A serializer warning (`CONNECTION_STUB`) is
|
|
7
7
|
* `lint` too: it never blocks.
|
|
8
8
|
* - `lowering`: `compile` cannot write a correct `.flow`. It always stops
|
|
9
|
-
* `compile`; `check`
|
|
9
|
+
* `compile`; `check` reports it too, from the serializer it runs in collect
|
|
10
|
+
* mode (`SerializeOpts.collect`), so the rule lives in one place.
|
|
10
11
|
* - `draft`: an unfinished canvas node (no connection yet, a required input
|
|
11
12
|
* left empty, a generic operation with no object). These are the codes a
|
|
12
13
|
* draft-tolerant compile collects instead of throwing (`DraftCode` in
|
|
@@ -92,6 +93,8 @@ export const REFUSALS = {
|
|
|
92
93
|
CARRIED_DEFINITION_CONFLICT: 'lowering',
|
|
93
94
|
CARRIED_DEFINITION_INVALID: 'lowering',
|
|
94
95
|
CEQL_UNPARSEABLE: 'lowering',
|
|
96
|
+
// A throw that is no refusal, which `compile` cannot get past: check reports it on its step.
|
|
97
|
+
COMPILE_FAILED: 'lowering',
|
|
95
98
|
CONNECTION_CONNECTOR_CONFLICT: 'draft',
|
|
96
99
|
CONNECTION_STUB: 'lint',
|
|
97
100
|
CONNECTOR_IMPORT_NOT_A_FILE: 'lowering',
|
|
@@ -246,6 +249,7 @@ export const REFUSALS = {
|
|
|
246
249
|
HITL_OUTCOME_ACTION: 'lint',
|
|
247
250
|
HITL_OUTCOME_BAD_ID: 'lint',
|
|
248
251
|
HITL_OUTCOME_NO_NAME: 'lint',
|
|
252
|
+
HITL_OUTCOME_PORTS_NONE: 'lowering',
|
|
249
253
|
HITL_OUTCOME_PORTS_OFF: 'lowering',
|
|
250
254
|
HITL_OUTCOME_PORTS_VARIANT: 'lowering',
|
|
251
255
|
HITL_OUTCOME_PORT_MISPLACED: 'lint',
|
package/dist/flow-node-type.d.ts
CHANGED
|
@@ -59,6 +59,17 @@ export interface FlowNodeContribution extends Omit<NodeContribution, 'definition
|
|
|
59
59
|
* guard that refuses a `completed` edge on such a task (`hitlExitViolation`).
|
|
60
60
|
*/
|
|
61
61
|
legacyCompleted?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The node is a human task with no per-outcome exits (no outcomes, an
|
|
64
|
+
* app-backed variant, `legacyCompleted: true`): why, as a clause ("it declares
|
|
65
|
+
* no outcomes"), and what to write instead. An `outcome-<id>` edge out of it
|
|
66
|
+
* would leave a handle it does not have, so it is refused
|
|
67
|
+
* (HITL_OUTCOME_PORTS_NONE).
|
|
68
|
+
*/
|
|
69
|
+
noOutcomePorts?: {
|
|
70
|
+
readonly why: string;
|
|
71
|
+
readonly suggestion: string;
|
|
72
|
+
};
|
|
62
73
|
/**
|
|
63
74
|
* `outputs` REPLACES the definition's `outputDefinition` on the node instead of
|
|
64
75
|
* being merged over it — for an instance whose output slots are its own (a
|
|
@@ -195,13 +206,16 @@ export interface FlowSerializeContext {
|
|
|
195
206
|
* Write a file beside the `.flow` (an agent's `agent.json`), at a path relative
|
|
196
207
|
* to it. The same file twice is not a clash (a subflow body emitted for two
|
|
197
208
|
* callers); two different contents at a path an explicit `source` chose are
|
|
198
|
-
* refused (`INLINE_AGENT_RESOURCE_SOURCE_DUP`).
|
|
199
|
-
*
|
|
209
|
+
* refused (`INLINE_AGENT_RESOURCE_SOURCE_DUP`). `owner` names whoever chose
|
|
210
|
+
* that `source`, as the refusal names it (`inline agent step "answer"'s
|
|
211
|
+
* context index "SOP"`); omit it for a derived one. `true` (the older form)
|
|
212
|
+
* marks an explicit `source` without naming its owner. The `json` object is
|
|
213
|
+
* kept, not copied, so `typeAgentInputs` can still fill it in.
|
|
200
214
|
*/
|
|
201
215
|
sidecar(file: {
|
|
202
216
|
path: string;
|
|
203
217
|
json: unknown;
|
|
204
|
-
},
|
|
218
|
+
}, owner?: string | boolean): void;
|
|
205
219
|
/**
|
|
206
220
|
* Type an agent sidecar's `inputSchema` once this scope's variables are known:
|
|
207
221
|
* each ref's `flatName` property gets the JSON Schema of the variable its
|
|
@@ -375,10 +389,12 @@ export interface FlowCheckContext {
|
|
|
375
389
|
readonly options: CheckOpts;
|
|
376
390
|
/**
|
|
377
391
|
* Check a step's symbolic `connection` / `folder` labels against bindings.json,
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
392
|
+
* reporting `BINDING_SELF_NAME` / `BINDING_UNDECLARED`.
|
|
393
|
+
*
|
|
394
|
+
* @deprecated `check` runs the serializer, which refuses a label passed to
|
|
395
|
+
* `FlowSerializeContext.resolveBinding` under the same codes; a node type that
|
|
396
|
+
* resolves its labels there needs no call here, and a call here reports the
|
|
397
|
+
* label twice. Kept so a node-type package written against it still builds.
|
|
382
398
|
*/
|
|
383
399
|
bindingLabels(where: string, labels: {
|
|
384
400
|
connection?: unknown;
|
|
@@ -393,17 +409,12 @@ export interface FlowCheckContext {
|
|
|
393
409
|
action?: string;
|
|
394
410
|
}): void;
|
|
395
411
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
* Run the rules every Integration Service operation shares — its lookups, its
|
|
403
|
-
* schema readiness, its contract against the library facts check was given
|
|
404
|
-
* (`options`), and BINDING_MISSING — on this step. Their codes are the
|
|
405
|
-
* pipeline's, shared with an event subscription's, so they belong to no
|
|
406
|
-
* family catalog; each is reported at its own severity.
|
|
412
|
+
* Run the rules every Integration Service operation shares that compile does
|
|
413
|
+
* not: its lookups and its schema readiness, against the library facts check
|
|
414
|
+
* was given (`options`). The operation, its field contract and its binding
|
|
415
|
+
* labels are the serializer's refusals, reported from `check`'s serializer
|
|
416
|
+
* run. Their codes are the pipeline's, shared with an event subscription's, so
|
|
417
|
+
* they belong to no family catalog; each is reported at its own severity.
|
|
407
418
|
*/
|
|
408
419
|
connector(spec: {
|
|
409
420
|
key: string;
|
|
@@ -414,15 +425,18 @@ export interface FlowCheckContext {
|
|
|
414
425
|
folder?: unknown;
|
|
415
426
|
inputs?: unknown;
|
|
416
427
|
}): void;
|
|
428
|
+
/**
|
|
429
|
+
* @deprecated Compile claims node ids and folders file-wide and refuses a clash
|
|
430
|
+
* (INLINE_AGENT_RESOURCE_NODE_ID / INLINE_AGENT_RESOURCE_SOURCE_DUP), which
|
|
431
|
+
* `check` reports from its serializer run. These record nothing and answer
|
|
432
|
+
* `undefined`; kept so a node-type package written against them still builds.
|
|
433
|
+
*/
|
|
417
434
|
readonly resources: {
|
|
418
|
-
/** An id compile derives for one of this step's resources, and what it is, for that report. */
|
|
419
435
|
derivedNodeId(id: string, what: string): void;
|
|
420
|
-
/** Claim an explicit node id: the earlier claimant when one already has it (and nothing is recorded), else undefined. */
|
|
421
436
|
claimNodeId(id: string, what: string): {
|
|
422
437
|
step: string;
|
|
423
438
|
what: string;
|
|
424
439
|
} | undefined;
|
|
425
|
-
/** Claim an agent's folder (its `source`, compared lower-cased): the other step that has it, else undefined. */
|
|
426
440
|
claimFolder(source: string): string | undefined;
|
|
427
441
|
};
|
|
428
442
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a `.parallel` arm hands its join its edges, read from the step tree the
|
|
3
|
+
* way serialize's `emit` wires them, and the two refusals a human task inside
|
|
4
|
+
* an arm can earn: alternative outcome edges that each enter the join
|
|
5
|
+
* (HITL_FAN_OUT_INTO_JOIN), and an End outcome beside a continuing one
|
|
6
|
+
* (HITL_END_OUTCOME_IN_PARALLEL). serialize refuses both on the graph it
|
|
7
|
+
* builds and words them here, naming the outcomes; `check` reads `armTails` for
|
|
8
|
+
* its own join rules.
|
|
9
|
+
*/
|
|
10
|
+
import type { Step } from './flow-sdk.js';
|
|
11
|
+
import { type HitlRoutingInputs } from './core/hitl-routing.js';
|
|
12
|
+
/**
|
|
13
|
+
* Whether a `.parallel` arm hands its join an edge, as serialize wires it: it
|
|
14
|
+
* has a step, and `armTails` (serialize's `emit` tail for tail) leaves it at
|
|
15
|
+
* least one tail. So an arm that ends by `.return`, `.terminate`, `.break`,
|
|
16
|
+
* `.continue` or a `.stepToRef` on the default port does not, and neither does
|
|
17
|
+
* one that ends in a `.branch` / `.switch` / `.stepSwitch` every arm of which
|
|
18
|
+
* ends, or in a nested `.parallel` none of whose arms reaches its own join.
|
|
19
|
+
* When no arm hands on a tail, serialize emits no Merge at all (#1267), so the
|
|
20
|
+
* join rules (PARALLEL_ARM_RETURNS, REF_INTO_PARALLEL_ARM,
|
|
21
|
+
* REF_OUT_OF_PARALLEL_ARM) have no join to protect.
|
|
22
|
+
*/
|
|
23
|
+
export declare function armReachesJoin(arm: Step[]): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* One edge a `.parallel` arm hands its join, as `emit` wires it: the step it
|
|
26
|
+
* leaves from, and the human-task outcome it leaves on, if it is one.
|
|
27
|
+
*/
|
|
28
|
+
export interface ArmTail {
|
|
29
|
+
from: string;
|
|
30
|
+
outcome?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The human task whose `.stepSwitch` arm this path runs on, and the outcome
|
|
33
|
+
* of that arm. Set on every tail a human-task `.stepSwitch` hands on, body
|
|
34
|
+
* arm or empty arm, so an arm that ends on two of them is seen for what it
|
|
35
|
+
* is: alternatives, of which only one fires.
|
|
36
|
+
*/
|
|
37
|
+
humanSwitch?: {
|
|
38
|
+
step: string;
|
|
39
|
+
outcome: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The edges a step list hands on, mirroring serialize's `emit` tail for tail.
|
|
44
|
+
*
|
|
45
|
+
* `.branch` / `.switch` / `.stepSwitch` hand on every arm's tails (an empty arm
|
|
46
|
+
* hands on the port it started from), a `.parallel` its Merge when an arm
|
|
47
|
+
* reaches it and nothing otherwise, a fan-out human task one per continuing
|
|
48
|
+
* outcome, a routed one its primary outcome; `.return` / `.terminate` / `.break`
|
|
49
|
+
* and a `stepToRef('output', …)` hand on none, `.stepToList` leaves the path as
|
|
50
|
+
* it was, and every other step hands on its own single edge.
|
|
51
|
+
*/
|
|
52
|
+
export declare function armTails(steps: readonly Step[], incoming: ArmTail[]): ArmTail[];
|
|
53
|
+
export declare function actionTails(name: string, spec: {
|
|
54
|
+
kind?: string;
|
|
55
|
+
inputs?: HitlRoutingInputs;
|
|
56
|
+
}, version?: string): ArmTail[];
|
|
57
|
+
/**
|
|
58
|
+
* The tails of a `.parallel` arm when they would starve its join: two or more
|
|
59
|
+
* alternative edges — only one fires — at least one of which is a human task's
|
|
60
|
+
* outcome edge, or the end of a human-task `.stepSwitch` arm. The join waits
|
|
61
|
+
* for a token on every incoming flow.
|
|
62
|
+
*
|
|
63
|
+
* However the task got there, it is the same graph: a fan-out task at the arm's
|
|
64
|
+
* end (its outcomes are the alternatives), a `.stepSwitch` with an empty arm
|
|
65
|
+
* beside a body arm, a complete `.stepSwitch` with nothing after it (one edge
|
|
66
|
+
* per arm's last step), a task on one side of a `.branch`. serialize refuses the
|
|
67
|
+
* same condition, edge for edge. Rejoining arms with NO human task in them are
|
|
68
|
+
* the older, general case, and not refused here.
|
|
69
|
+
*/
|
|
70
|
+
export declare function hitlFanInto(steps: readonly Step[]): ArmTail[] | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* The HITL_FAN_OUT_INTO_JOIN refusal for arm `arm` of `.parallel` `join`, whose
|
|
73
|
+
* `tails` ({@link hitlFanInto}) would each enter the join, and the human task it
|
|
74
|
+
* is about.
|
|
75
|
+
*/
|
|
76
|
+
export declare function fanInRefusal(join: string, arm: number, steps: readonly Step[], tails: ArmTail[]): {
|
|
77
|
+
message: string;
|
|
78
|
+
suggestion: string;
|
|
79
|
+
task: string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A human task inside a `.parallel` arm with an outcome whose path stops at an
|
|
83
|
+
* End, which would starve the join: an `action: 'End'` outcome of a fan-out
|
|
84
|
+
* task, or a `.stepSwitch` outcome with no arm or whose arm ends in `.return()`.
|
|
85
|
+
*/
|
|
86
|
+
export interface HitlEndHazard {
|
|
87
|
+
step: string;
|
|
88
|
+
outcomes: string[];
|
|
89
|
+
via: 'action-end' | 'step-switch';
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Human tasks in one `.parallel` arm that continue every outcome while some of
|
|
93
|
+
* them are `action: 'End'`. The End outcome's path stops at its End node, so the
|
|
94
|
+
* join waits for the continuing path that did not run.
|
|
95
|
+
*
|
|
96
|
+
* Recurses into `.branch` / `.switch` / `.stepSwitch` bodies, which reach the
|
|
97
|
+
* join too. Not into a loop body (the loop node is what reaches the join) or a
|
|
98
|
+
* nested `.parallel` (its own join is checked on its own).
|
|
99
|
+
*/
|
|
100
|
+
export declare function hitlEndHazards(steps: readonly Step[]): HitlEndHazard[];
|
|
101
|
+
/** The HITL_END_OUTCOME_IN_PARALLEL refusal for hazard `h` ({@link hitlEndHazards}) inside `.parallel` `join`. */
|
|
102
|
+
export declare function endOutcomeRefusal(join: string, h: HitlEndHazard): {
|
|
103
|
+
message: string;
|
|
104
|
+
suggestion?: string;
|
|
105
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { commentText } from './core/comment-text.js';
|
|
2
|
+
import { OUTCOME_HANDLE_NODE_TYPES, hitlExitModel } from './core/hitl-routing.js';
|
|
3
|
+
import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
4
|
+
import { hasExits } from './core/step-ports.js';
|
|
5
|
+
import { stopsAtEnd } from './flow-step-tree.js';
|
|
6
|
+
import { stepExits } from './flow-step-exits.js';
|
|
7
|
+
/**
|
|
8
|
+
* Whether a `.parallel` arm hands its join an edge, as serialize wires it: it
|
|
9
|
+
* has a step, and `armTails` (serialize's `emit` tail for tail) leaves it at
|
|
10
|
+
* least one tail. So an arm that ends by `.return`, `.terminate`, `.break`,
|
|
11
|
+
* `.continue` or a `.stepToRef` on the default port does not, and neither does
|
|
12
|
+
* one that ends in a `.branch` / `.switch` / `.stepSwitch` every arm of which
|
|
13
|
+
* ends, or in a nested `.parallel` none of whose arms reaches its own join.
|
|
14
|
+
* When no arm hands on a tail, serialize emits no Merge at all (#1267), so the
|
|
15
|
+
* join rules (PARALLEL_ARM_RETURNS, REF_INTO_PARALLEL_ARM,
|
|
16
|
+
* REF_OUT_OF_PARALLEL_ARM) have no join to protect.
|
|
17
|
+
*/
|
|
18
|
+
export function armReachesJoin(arm) {
|
|
19
|
+
return arm.length > 0 && armTails(arm, [{ from: '' }]).length > 0;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The edges a step list hands on, mirroring serialize's `emit` tail for tail.
|
|
23
|
+
*
|
|
24
|
+
* `.branch` / `.switch` / `.stepSwitch` hand on every arm's tails (an empty arm
|
|
25
|
+
* hands on the port it started from), a `.parallel` its Merge when an arm
|
|
26
|
+
* reaches it and nothing otherwise, a fan-out human task one per continuing
|
|
27
|
+
* outcome, a routed one its primary outcome; `.return` / `.terminate` / `.break`
|
|
28
|
+
* and a `stepToRef('output', …)` hand on none, `.stepToList` leaves the path as
|
|
29
|
+
* it was, and every other step hands on its own single edge.
|
|
30
|
+
*/
|
|
31
|
+
export function armTails(steps, incoming) {
|
|
32
|
+
let tails = incoming;
|
|
33
|
+
for (const st of steps) {
|
|
34
|
+
if (st.kind === 'stepToList')
|
|
35
|
+
continue;
|
|
36
|
+
if (st.kind === 'stepToRef') {
|
|
37
|
+
if (st.port === 'output')
|
|
38
|
+
tails = [];
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (st.kind === 'return' || st.kind === 'terminate' || st.kind === 'break' || st.kind === 'continue')
|
|
42
|
+
return [];
|
|
43
|
+
if (st.kind === 'action') {
|
|
44
|
+
tails = actionTails(st.name, st.spec, st.options?.version);
|
|
45
|
+
}
|
|
46
|
+
else if (st.kind === 'stepSwitch') {
|
|
47
|
+
const spec = st.spec;
|
|
48
|
+
const outcomes = outcomeHandleTask(spec);
|
|
49
|
+
tails = st.cases.flatMap((c) => armTails(c.body, [{ from: st.name, ...(outcomes ? { outcome: String(c.value) } : {}) }])
|
|
50
|
+
.map((t) => (outcomes && !t.humanSwitch ? { ...t, humanSwitch: { step: st.name, outcome: String(c.value) } } : t)));
|
|
51
|
+
}
|
|
52
|
+
else if (st.kind === 'branch') {
|
|
53
|
+
tails = [...armTails(st.then, [{ from: st.name }]), ...armTails(st.otherwise, [{ from: st.name }])];
|
|
54
|
+
}
|
|
55
|
+
else if (st.kind === 'switch') {
|
|
56
|
+
tails = [
|
|
57
|
+
...st.cases.flatMap((c) => armTails(c.body, [{ from: st.name }])),
|
|
58
|
+
...(st.default !== undefined ? armTails(st.default, [{ from: st.name }]) : []),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
else if (st.kind === 'parallel') {
|
|
62
|
+
// serialize joins a nested `.parallel` at its Merge, the one edge it hands
|
|
63
|
+
// on, and emits no Merge, so no edge, when no arm hands on a tail (#1267).
|
|
64
|
+
// An empty arm hands on the path it started from, which the Merge joins.
|
|
65
|
+
const arms = Array.isArray(st.arms) ? st.arms : [];
|
|
66
|
+
tails = arms.some((arm) => armTails(arm, tails).length > 0) ? [{ from: st.name }] : [];
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
tails = [{ from: st.name ?? st.kind }];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return tails;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Whether `spec` is a human task the product draws one handle per outcome on
|
|
76
|
+
* (the base node type and quick-form), as its registered node type names it.
|
|
77
|
+
*/
|
|
78
|
+
function outcomeHandleTask(spec) {
|
|
79
|
+
const kind = spec?.kind;
|
|
80
|
+
const entry = typeof kind === 'string' ? FLOW_NODE_TYPES.byKind(kind, spec) : undefined;
|
|
81
|
+
return entry !== undefined && OUTCOME_HANDLE_NODE_TYPES.has(entry.type.typeName);
|
|
82
|
+
}
|
|
83
|
+
export function actionTails(name, spec, version) {
|
|
84
|
+
if (!outcomeHandleTask(spec))
|
|
85
|
+
return [{ from: name }];
|
|
86
|
+
const model = hitlExitModel(spec.inputs, version);
|
|
87
|
+
if (model.shape === 'placeholder')
|
|
88
|
+
return [{ from: name }];
|
|
89
|
+
if (model.shape === 'per-outcome')
|
|
90
|
+
return model.exits.slice(0, 1).map((e) => ({ from: name, outcome: e.value }));
|
|
91
|
+
return model.exits.filter((e) => !e.endsProcess).map((e) => ({ from: name, outcome: e.value }));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The tails of a `.parallel` arm when they would starve its join: two or more
|
|
95
|
+
* alternative edges — only one fires — at least one of which is a human task's
|
|
96
|
+
* outcome edge, or the end of a human-task `.stepSwitch` arm. The join waits
|
|
97
|
+
* for a token on every incoming flow.
|
|
98
|
+
*
|
|
99
|
+
* However the task got there, it is the same graph: a fan-out task at the arm's
|
|
100
|
+
* end (its outcomes are the alternatives), a `.stepSwitch` with an empty arm
|
|
101
|
+
* beside a body arm, a complete `.stepSwitch` with nothing after it (one edge
|
|
102
|
+
* per arm's last step), a task on one side of a `.branch`. serialize refuses the
|
|
103
|
+
* same condition, edge for edge. Rejoining arms with NO human task in them are
|
|
104
|
+
* the older, general case, and not refused here.
|
|
105
|
+
*/
|
|
106
|
+
export function hitlFanInto(steps) {
|
|
107
|
+
const tails = armTails(steps, [{ from: '(fork)' }]);
|
|
108
|
+
return tails.length >= 2 && tails.some((t) => t.outcome !== undefined || t.humanSwitch !== undefined) ? tails : undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The HITL_FAN_OUT_INTO_JOIN refusal for arm `arm` of `.parallel` `join`, whose
|
|
112
|
+
* `tails` ({@link hitlFanInto}) would each enter the join, and the human task it
|
|
113
|
+
* is about.
|
|
114
|
+
*/
|
|
115
|
+
export function fanInRefusal(join, arm, steps, tails) {
|
|
116
|
+
const tagged = tails.find((t) => t.outcome !== undefined || t.humanSwitch !== undefined);
|
|
117
|
+
const task = tagged.outcome !== undefined ? tagged.from : tagged.humanSwitch.step;
|
|
118
|
+
if (tails.every((t) => t.from === task && t.outcome !== undefined)) {
|
|
119
|
+
return {
|
|
120
|
+
task,
|
|
121
|
+
message: `Human task "${task}" ends its path inside parallel "${join}", so its outcomes `
|
|
122
|
+
+ `${tails.map((t) => t.outcome).join(', ')} each get their own edge into the join. Only one outcome ever `
|
|
123
|
+
+ `fires, and the join waits for a token on every incoming edge, so the run never gets past "${join}" — with `
|
|
124
|
+
+ `no incident, and \`flow validate\` answers Valid. Add a step after "${task}" inside the arm, so every `
|
|
125
|
+
+ `outcome continues to that step and only its one edge reaches the join (the decision is `
|
|
126
|
+
+ `out('${task}', 'Action')), or move the task out of the \`.parallel(…)\`.`,
|
|
127
|
+
suggestion: `a.step('${task}', hitl(…)).step('record', …)`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const edges = tails.map((t) => (t.outcome !== undefined ? `outcome ${t.outcome} of "${t.from}"`
|
|
131
|
+
: t.humanSwitch ? `"${t.from}" (on outcome ${t.humanSwitch.outcome} of "${t.humanSwitch.step}")` : `"${t.from}"`));
|
|
132
|
+
const last = [...steps].reverse().find((x) => x.kind !== 'stepToList' && x.kind !== 'stepToRef');
|
|
133
|
+
return {
|
|
134
|
+
task,
|
|
135
|
+
message: `Parallel "${join}" arm ${arm + 1} ends on ${tails.length} alternative paths, and each gets its own edge `
|
|
136
|
+
+ `into the join: ${edges.join(', ')}. Only one of them runs, and the join waits for a token on every incoming `
|
|
137
|
+
+ `edge, so the run never gets past "${join}" — with no incident, and \`flow validate\` answers Valid. Add a `
|
|
138
|
+
+ `step at the end of the arm${last?.name ? `, after "${last.name}"` : ''}, so the paths meet on that step and `
|
|
139
|
+
+ `only its one edge reaches the join, or move human task "${task}" out of the \`.parallel(…)\`.`,
|
|
140
|
+
suggestion: `….step('record', …) // last step of arm ${commentText(arm + 1)}`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Human tasks in one `.parallel` arm that continue every outcome while some of
|
|
145
|
+
* them are `action: 'End'`. The End outcome's path stops at its End node, so the
|
|
146
|
+
* join waits for the continuing path that did not run.
|
|
147
|
+
*
|
|
148
|
+
* Recurses into `.branch` / `.switch` / `.stepSwitch` bodies, which reach the
|
|
149
|
+
* join too. Not into a loop body (the loop node is what reaches the join) or a
|
|
150
|
+
* nested `.parallel` (its own join is checked on its own).
|
|
151
|
+
*/
|
|
152
|
+
export function hitlEndHazards(steps) {
|
|
153
|
+
const found = [];
|
|
154
|
+
for (const st of steps) {
|
|
155
|
+
if (st.kind === 'action' && outcomeHandleTask(st.spec)) {
|
|
156
|
+
const model = hitlExitModel(st.spec.inputs, st.options?.version);
|
|
157
|
+
if (model.shape !== 'every-outcome')
|
|
158
|
+
continue;
|
|
159
|
+
const ending = model.exits.filter((e) => e.endsProcess).map((e) => e.value);
|
|
160
|
+
if (ending.length > 0 && ending.length < model.exits.length)
|
|
161
|
+
found.push({ step: st.name, outcomes: ending, via: 'action-end' });
|
|
162
|
+
}
|
|
163
|
+
else if (st.kind === 'branch') {
|
|
164
|
+
found.push(...hitlEndHazards(st.then), ...hitlEndHazards(st.otherwise));
|
|
165
|
+
}
|
|
166
|
+
else if (st.kind === 'switch') {
|
|
167
|
+
for (const c of st.cases)
|
|
168
|
+
found.push(...hitlEndHazards(c.body));
|
|
169
|
+
if (st.default)
|
|
170
|
+
found.push(...hitlEndHazards(st.default));
|
|
171
|
+
}
|
|
172
|
+
else if (st.kind === 'stepSwitch') {
|
|
173
|
+
// An outcome with no arm gets an End (serialize's `endExit`), and so does
|
|
174
|
+
// an arm that ends in `.return()`. Beside an arm that continues, that End
|
|
175
|
+
// is where this arm's token stops on that outcome.
|
|
176
|
+
const spec = st.spec;
|
|
177
|
+
const exits = outcomeHandleTask(spec) ? stepExits(st.spec, st.options?.version) : undefined;
|
|
178
|
+
if (exits && hasExits(exits)) {
|
|
179
|
+
const stopped = exits.filter((e) => {
|
|
180
|
+
const c = st.cases.find((x) => x.value === e.value);
|
|
181
|
+
return !c || (armTails(c.body, [{ from: st.name }]).length === 0 && stopsAtEnd(c.body));
|
|
182
|
+
}).map((e) => e.value);
|
|
183
|
+
if (stopped.length > 0 && armTails([st], [{ from: '(fork)' }]).length > 0) {
|
|
184
|
+
found.push({ step: st.name, outcomes: stopped, via: 'step-switch' });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const c of st.cases)
|
|
188
|
+
found.push(...hitlEndHazards(c.body));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return found;
|
|
192
|
+
}
|
|
193
|
+
/** The HITL_END_OUTCOME_IN_PARALLEL refusal for hazard `h` ({@link hitlEndHazards}) inside `.parallel` `join`. */
|
|
194
|
+
export function endOutcomeRefusal(join, h) {
|
|
195
|
+
const list = h.outcomes.join(', ');
|
|
196
|
+
if (h.via === 'step-switch') {
|
|
197
|
+
return {
|
|
198
|
+
message: `Human task "${h.step}" is a .stepSwitch inside parallel "${join}", and ${h.outcomes.length === 1
|
|
199
|
+
? `outcome ${list} has` : `outcomes ${list} have`} no arm, or an arm that ends in .return(). That path `
|
|
200
|
+
+ `stops at an End node, while the join waits for a token from this arm on the path that continues, so the `
|
|
201
|
+
+ `run never gets past "${join}" — with no incident, and \`flow validate\` answers Valid. Give every outcome `
|
|
202
|
+
+ `an arm that continues (and move a .return() after the \`.parallel(…)\`), or move the task out of the `
|
|
203
|
+
+ `\`.parallel(…)\`.`,
|
|
204
|
+
suggestion: `{ value: '${h.outcomes[0]}', body: (b) => b.step(…) }`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
message: `Human task "${h.step}" is inside parallel "${join}", and ${h.outcomes.length === 1
|
|
209
|
+
? `outcome ${list} is` : `outcomes ${list} are`} action: 'End'. `
|
|
210
|
+
+ `That path stops at its own End node, while the join waits for a token from this arm on its `
|
|
211
|
+
+ `continuing path, so the run never gets past "${join}". Drop action: 'End' there and act on `
|
|
212
|
+
+ `out('${h.step}', 'Action') after the join, or move the task out of the \`.parallel(…)\`.`,
|
|
213
|
+
};
|
|
214
|
+
}
|
package/dist/flow-sdk.d.ts
CHANGED
|
@@ -501,8 +501,8 @@ export interface ReturnOptions extends NodeOptions {
|
|
|
501
501
|
* for what the canvas shows. Two Ends in one scope asking for `done` with the
|
|
502
502
|
* same values, label, version and updates are one End reached from both paths.
|
|
503
503
|
* Two that differ in any of these, or that sit in different scopes (a loop body
|
|
504
|
-
* and the main path), are refused (
|
|
505
|
-
*
|
|
504
|
+
* and the main path), are refused (DUP_END_NAME, by `compile` and so by
|
|
505
|
+
* `check`) rather than renamed `done2`. The name is never renamed to make it
|
|
506
506
|
* unique: a name that gives the node id of a step, the trigger or an entry
|
|
507
507
|
* point is refused too (STEP_ID_COLLISION), as is a second End whose name gives
|
|
508
508
|
* the same id (`a b` and `a_b`, DUP_END_NAME).
|
|
@@ -1265,8 +1265,8 @@ declare class StepList {
|
|
|
1265
1265
|
* own, from anywhere in the flow, including another port's path. To stop
|
|
1266
1266
|
* several paths at one Terminate, spell its `.terminate` once and end the
|
|
1267
1267
|
* other paths with `.stepToRef('<name>')`. Two `.terminate`s asking for the
|
|
1268
|
-
* same name are refused (
|
|
1269
|
-
*
|
|
1268
|
+
* same name are refused (DUP_STEP, by `compile` and so by `check`) rather than
|
|
1269
|
+
* renamed `<name>2`.
|
|
1270
1270
|
*
|
|
1271
1271
|
* @example
|
|
1272
1272
|
* **Abort the sibling arm from inside a parallel**
|
package/dist/flow-sdk.js
CHANGED
|
@@ -899,8 +899,8 @@ class StepList {
|
|
|
899
899
|
* own, from anywhere in the flow, including another port's path. To stop
|
|
900
900
|
* several paths at one Terminate, spell its `.terminate` once and end the
|
|
901
901
|
* other paths with `.stepToRef('<name>')`. Two `.terminate`s asking for the
|
|
902
|
-
* same name are refused (
|
|
903
|
-
*
|
|
902
|
+
* same name are refused (DUP_STEP, by `compile` and so by `check`) rather than
|
|
903
|
+
* renamed `<name>2`.
|
|
904
904
|
*
|
|
905
905
|
* @example
|
|
906
906
|
* **Abort the sibling arm from inside a parallel**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The words for a `.terminate('<name>')` whose name is already taken (#1239).
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* The serializer refuses the clash with them (DUP_STEP), and `check` reports
|
|
4
|
+
* that refusal from its serializer run.
|
|
5
5
|
*/
|
|
6
6
|
/** Two `.terminate('<name>')` calls: keep one and end the other paths at it. */
|
|
7
7
|
export declare function terminateTwiceMessage(name: string): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The words for a `.terminate('<name>')` whose name is already taken (#1239).
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* The serializer refuses the clash with them (DUP_STEP), and `check` reports
|
|
4
|
+
* that refusal from its serializer run.
|
|
5
5
|
*/
|
|
6
6
|
/** Two `.terminate('<name>')` calls: keep one and end the other paths at it. */
|
|
7
7
|
export function terminateTwiceMessage(name) {
|
package/dist/merge-compile.js
CHANGED
|
@@ -81,5 +81,6 @@ function baselineDirectory(originalPath) {
|
|
|
81
81
|
* load-time cycle.
|
|
82
82
|
*/
|
|
83
83
|
export function checkEdited(built, editedFlow, opts) {
|
|
84
|
-
|
|
84
|
+
// Merge compiles the flow itself (draft-tolerant), so check's serializer pass is skipped.
|
|
85
|
+
return placeDiagnostics(check(built, { ...opts, serializer: false }), editedFlow);
|
|
85
86
|
}
|