@uipath/maestro-builder-sdk 6.15.1 → 6.15.3
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 +166 -166
- package/dist/api-members.md +409 -409
- package/dist/check.js +24 -14
- package/dist/core/actions.d.ts +18 -7
- package/dist/core/actions.js +3 -2
- package/dist/core/binding-messages.d.ts +22 -1
- package/dist/core/binding-messages.js +27 -0
- package/dist/core/connector-checks.d.ts +11 -0
- package/dist/core/connector-checks.js +13 -1
- package/dist/core/expr.d.ts +3 -4
- package/dist/core/expr.js +3 -4
- package/dist/core/global-names.d.ts +39 -0
- package/dist/core/global-names.js +60 -0
- package/dist/decompile.js +24 -2
- package/dist/flow-sdk.d.ts +6 -4
- package/dist/flow-sdk.js +5 -4
- package/dist/serialize.js +46 -22
- package/package.json +2 -2
package/dist/check.js
CHANGED
|
@@ -9,9 +9,10 @@ import { Expr, SCHEDULE_PRESETS, DELAY_PRESETS, VARIANT_OPERATION, parseIxpProje
|
|
|
9
9
|
import { FlowNode } from './core/node-classes.js';
|
|
10
10
|
import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
|
|
11
11
|
import { checkEventResolution, checkEventSubscription, eventParamsFor, } from './core/event-checks.js';
|
|
12
|
-
import { FLOW_EXPR_DIALECT, checkBindingLabels, checkConnectorContract as coreCheckConnectorContract, checkConnectorLookups as coreCheckConnectorLookups, checkConnectorSchema as coreCheckConnectorSchema, collectConnectionUses, connectionConnectorConflicts, } from './core/connector-checks.js';
|
|
12
|
+
import { FLOW_EXPR_DIALECT, checkBindingLabels, checkBindingPresence, checkConnectorContract as coreCheckConnectorContract, checkConnectorLookups as coreCheckConnectorLookups, checkConnectorSchema as coreCheckConnectorSchema, collectConnectionUses, connectionConnectorConflicts, } from './core/connector-checks.js';
|
|
13
13
|
import { prepareCommand } from './core/cli-spelling.js';
|
|
14
14
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
15
|
+
import { globalNameCollisionMessage, globalNameCollisions } from './core/global-names.js';
|
|
15
16
|
import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome } from './core/hitl-routing.js';
|
|
16
17
|
import { childStepLists, declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
|
|
17
18
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
@@ -36,6 +37,11 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
36
37
|
diags.push({ level: 'error', code: 'NO_ID', message: 'flow() requires an id.' });
|
|
37
38
|
checkSchemaVersion(built, ancestors, diags);
|
|
38
39
|
checkNodeUpdates(built, diags);
|
|
40
|
+
// Inputs, outputs and vars are one namespace in the .flow (variables.globals[]);
|
|
41
|
+
// `serialize` throws the same sentence (flow-builder-sdk#890).
|
|
42
|
+
for (const collision of globalNameCollisions(built.inputs ?? [], built.outputs ?? [], built.vars ?? [])) {
|
|
43
|
+
diags.push({ level: 'error', code: 'VARIABLE_NAME_COLLISION', message: globalNameCollisionMessage(collision) });
|
|
44
|
+
}
|
|
39
45
|
const maybeCustomTrigger = built.trigger;
|
|
40
46
|
if (maybeCustomTrigger !== undefined && !('kind' in maybeCustomTrigger) && maybeCustomTrigger instanceof FlowNode) {
|
|
41
47
|
// A custom FlowTrigger subclass reports through its own diagnostics() hook;
|
|
@@ -543,6 +549,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
543
549
|
checkEventSubscription(s.spec.subscription, `waitForEvent "${stepName}"`, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
|
|
544
550
|
checkEventResolution(s.spec.subscription, `waitForEvent "${stepName}"`, 'event', opts, push('error'), push('warning'), eventParamsFor(s.spec.subscription, opts));
|
|
545
551
|
checkBindingLabels(`waitForEvent "${stepName}"`, s.spec.subscription, opts, push('error'), eventTarget(s.spec.subscription), { id: stepName, connector: eventTarget(s.spec.subscription)?.key });
|
|
552
|
+
checkBindingPresence(`waitForEvent "${stepName}"`, s.spec.subscription, push('error'), eventTarget(s.spec.subscription));
|
|
546
553
|
}
|
|
547
554
|
if (s.spec.kind === 'subflow') {
|
|
548
555
|
// Only a real child goes in the map: a missing one is reported by
|
|
@@ -848,7 +855,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
848
855
|
}
|
|
849
856
|
}
|
|
850
857
|
else {
|
|
851
|
-
checkTrigger(ep.trigger, diags, opts);
|
|
858
|
+
checkTrigger(ep.trigger, diags, opts, `onEvent() entry point "${ep.id}"`);
|
|
852
859
|
}
|
|
853
860
|
for (const inp of ep.inputs) {
|
|
854
861
|
const collides = built.inputs.some((g) => g.name === inp.name)
|
|
@@ -894,20 +901,17 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
894
901
|
// Both forms resolve at run time — measured, `$vars.call["output"].x` and
|
|
895
902
|
// `$vars["call"].output.x` both returned the value — so this is not a broken
|
|
896
903
|
// read. It is an unrewritable one. Every reference the SDK emits goes through
|
|
897
|
-
// `renameRefs`, whose regex matches the DOTTED form only, and
|
|
904
|
+
// `renameRefs`, whose regex matches the DOTTED form only, and two separate
|
|
898
905
|
// rewrites ride on it:
|
|
899
906
|
//
|
|
900
907
|
// * a subflow copy renames node ids (`rename.nodeIds`), so a bracketed read
|
|
901
908
|
// keeps pointing at the uncopied name;
|
|
902
|
-
// * the queue family publishes its result as `<step>.response`, not
|
|
903
|
-
// `<step>.output` (`rename.runtimeOutputs`), so the two spellings read
|
|
904
|
-
// DIFFERENT variables;
|
|
905
909
|
// * a failure envelope resolves per node family (`rename.errorEnvelopes`).
|
|
906
910
|
//
|
|
907
|
-
// So the bracketed spelling is correct only for as long as
|
|
911
|
+
// So the bracketed spelling is correct only for as long as neither of those
|
|
908
912
|
// applies to the step — and it fails silently when one starts to. This is the
|
|
909
913
|
// shape an eval agent actually wrote (`$vars.enqueue["output"].Key` on a queue
|
|
910
|
-
// step,
|
|
914
|
+
// step, at a time when the SDK still aliased queue reads to `.response`, #891).
|
|
911
915
|
//
|
|
912
916
|
// Deeper brackets are untouched: `$vars.rows.output["odd key"]` is ordinary
|
|
913
917
|
// data access, it is the only way to spell a key that is not an identifier,
|
|
@@ -916,16 +920,15 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
916
920
|
const flag = (name, read, key) => {
|
|
917
921
|
const suggestion = key === 'error'
|
|
918
922
|
? `err('${name}', 'message')`
|
|
919
|
-
: key && key !== 'output'
|
|
923
|
+
: key && key !== 'output'
|
|
920
924
|
? `out('${name}', '${key}')`
|
|
921
925
|
: `out('${name}')`;
|
|
922
926
|
diags.push({
|
|
923
927
|
level: 'error',
|
|
924
928
|
code: 'VARS_BRACKET_READ',
|
|
925
929
|
message: `"${read}" in "${step}" reaches step "${name}" through a bracket. It resolves today, but only the `
|
|
926
|
-
+ `DOTTED form is rewritten on the way out: a subflow copy renames node ids,
|
|
927
|
-
+ `
|
|
928
|
-
+ `family. A bracketed read opts out of all three and goes silently stale when one applies.`,
|
|
930
|
+
+ `DOTTED form is rewritten on the way out: a subflow copy renames node ids, and a failure envelope `
|
|
931
|
+
+ `resolves per node family. A bracketed read opts out of both and goes silently stale when one applies.`,
|
|
929
932
|
step,
|
|
930
933
|
suggestion,
|
|
931
934
|
});
|
|
@@ -1261,9 +1264,15 @@ function checkConnectorSchema(step, spec, opts, diags) {
|
|
|
1261
1264
|
const { where, report } = connectorSite(step, diags);
|
|
1262
1265
|
coreCheckConnectorSchema(where, spec, opts, FLOW_EXPR_DIALECT, report);
|
|
1263
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* The core contract (fields with a library, names with a bindings.json), plus
|
|
1269
|
+
* BINDING_MISSING, which needs neither: an absent label is wrong whatever the
|
|
1270
|
+
* file says.
|
|
1271
|
+
*/
|
|
1264
1272
|
function checkConnectorContract(step, spec, opts, diags) {
|
|
1265
1273
|
const { where, report } = connectorSite(step, diags);
|
|
1266
1274
|
coreCheckConnectorContract(where, spec, opts, report, step);
|
|
1275
|
+
checkBindingPresence(`connector "${step}"`, spec, (code, message) => report({ level: 'error', code, message }), { key: spec.key, action: spec.action });
|
|
1267
1276
|
}
|
|
1268
1277
|
/** Reject a wrapper hop when the runtime shape of a list has been measured. */
|
|
1269
1278
|
function checkLoopListEnvelope(loop, connectors, opts, diags) {
|
|
@@ -1651,7 +1660,7 @@ function checkNodeUpdates(built, diags) {
|
|
|
1651
1660
|
};
|
|
1652
1661
|
visit(built.steps);
|
|
1653
1662
|
}
|
|
1654
|
-
function checkTrigger(spec, diags, opts = {}) {
|
|
1663
|
+
function checkTrigger(spec, diags, opts = {}, where) {
|
|
1655
1664
|
const push = (level) => (code, message, suggestion) => {
|
|
1656
1665
|
diags.push({ level, code, message, step: 'trigger', ...(suggestion ? { suggestion } : {}) });
|
|
1657
1666
|
};
|
|
@@ -1663,6 +1672,7 @@ function checkTrigger(spec, diags, opts = {}) {
|
|
|
1663
1672
|
checkEventSubscription(sub, 'trigger', at, push('warning'), eventParamsFor(sub, opts));
|
|
1664
1673
|
checkEventResolution(sub, 'trigger', 'trigger', opts, at, push('warning'), eventParamsFor(sub, opts));
|
|
1665
1674
|
checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub), { id: 'trigger', connector: eventTarget(sub)?.key });
|
|
1675
|
+
checkBindingPresence(where ?? 'event trigger', sub, at, eventTarget(sub));
|
|
1666
1676
|
return;
|
|
1667
1677
|
}
|
|
1668
1678
|
// manual() is the explicit spelling of the default — nothing to validate.
|
|
@@ -1679,7 +1689,7 @@ function checkTrigger(spec, diags, opts = {}) {
|
|
|
1679
1689
|
if (spec?.kind !== 'scheduled') {
|
|
1680
1690
|
at('TRIGGER_UNKNOWN_KIND', `.trigger() was given ${JSON.stringify(spec?.kind)}, which is not a trigger. `
|
|
1681
1691
|
+ `Wrap it in a factory: scheduled({ every: 'R/PT1H' }) for a schedule, `
|
|
1682
|
-
+ `onEvent({ connector, event, where }) to start on a connector event, formTrigger() to start from a `
|
|
1692
|
+
+ `onEvent({ connector, event, where, connection, folder }) to start on a connector event, formTrigger() to start from a `
|
|
1683
1693
|
+ `submitted form, or conversationTrigger() to start on a new conversation. Omit .trigger() entirely `
|
|
1684
1694
|
+ `for the manual trigger (the default).`, `scheduled({ every: 'R/PT1H' })`);
|
|
1685
1695
|
return;
|
package/dist/core/actions.d.ts
CHANGED
|
@@ -187,9 +187,12 @@ export interface ScriptInputs {
|
|
|
187
187
|
export interface ConnectorOpts {
|
|
188
188
|
/** Connector action version (defaults to the library's newest match). */
|
|
189
189
|
version?: string;
|
|
190
|
-
/**
|
|
190
|
+
/**
|
|
191
|
+
* Symbolic connection name declared in bindings.json (→ ConnectionId). Required with
|
|
192
|
+
* `folder`: `check` refuses a connector step without both (BINDING_MISSING).
|
|
193
|
+
*/
|
|
191
194
|
connection?: string;
|
|
192
|
-
/** Symbolic folder name declared in bindings.json (→ connectionFolderKey). */
|
|
195
|
+
/** Symbolic folder name declared in bindings.json (→ connectionFolderKey). Required with `connection`. */
|
|
193
196
|
folder?: string;
|
|
194
197
|
/**
|
|
195
198
|
* Which OBJECT a **generic** operation addresses — e.g. ServiceNow's "List All
|
|
@@ -1593,7 +1596,8 @@ export interface InlineAgentInputs {
|
|
|
1593
1596
|
* ```ts
|
|
1594
1597
|
* tools: [
|
|
1595
1598
|
* { kind: 'builtin', tool: 'summarize' },
|
|
1596
|
-
* { kind: 'connector', connector: 'uipath-atlassian-jira', operation: 'create-issue'
|
|
1599
|
+
* { kind: 'connector', connector: 'uipath-atlassian-jira', operation: 'create-issue',
|
|
1600
|
+
* connection: 'jira', folder: 'shared' }, // bindings.json labels, both required
|
|
1597
1601
|
* { kind: 'process', key: '6235d64e-b190-4b7a-8918-723df964c068',
|
|
1598
1602
|
* name: 'Create TestCase', folderPath: 'Shared',
|
|
1599
1603
|
* inputs: { title: 'string' }, returns: { id: 'string' } },
|
|
@@ -3420,9 +3424,12 @@ export interface TriggerOptions<W = Record<string, string>> {
|
|
|
3420
3424
|
object?: string;
|
|
3421
3425
|
/** Optional filters on the payload. Omit to take every event in scope. */
|
|
3422
3426
|
filters?: EventFilter[];
|
|
3423
|
-
/**
|
|
3427
|
+
/**
|
|
3428
|
+
* bindings.json id for the connection. Required with `folder`: `check` refuses an event
|
|
3429
|
+
* node without both (BINDING_MISSING) — it would compile to `connectionId: null`.
|
|
3430
|
+
*/
|
|
3424
3431
|
connection?: string;
|
|
3425
|
-
/** bindings.json id for the folder key. */
|
|
3432
|
+
/** bindings.json id for the folder key. Required with `connection` (BINDING_MISSING). */
|
|
3426
3433
|
folder?: string;
|
|
3427
3434
|
/** Override the descriptor's version. */
|
|
3428
3435
|
version?: string;
|
|
@@ -3460,9 +3467,12 @@ export interface EventSubscription {
|
|
|
3460
3467
|
object?: string;
|
|
3461
3468
|
/** Optional filters on the payload. Omit to take every event in scope. */
|
|
3462
3469
|
filters?: EventFilter[];
|
|
3463
|
-
/**
|
|
3470
|
+
/**
|
|
3471
|
+
* bindings.json id for the connection. Required with `folder`: `check` refuses an event
|
|
3472
|
+
* node without both (BINDING_MISSING) — it would compile to `connectionId: null`.
|
|
3473
|
+
*/
|
|
3464
3474
|
connection?: string;
|
|
3465
|
-
/** bindings.json id for the folder key. */
|
|
3475
|
+
/** bindings.json id for the folder key. Required with `connection` (BINDING_MISSING). */
|
|
3466
3476
|
folder?: string;
|
|
3467
3477
|
version?: string;
|
|
3468
3478
|
}
|
|
@@ -3493,6 +3503,7 @@ export declare function subscriptionFrom<W>(a: EventSubscription | TriggerDescri
|
|
|
3493
3503
|
* event: 'email-received',
|
|
3494
3504
|
* where: { parentFolderId: inboxId },
|
|
3495
3505
|
* filters: [{ field: 'subject', contains: 'TestWaitFor' }],
|
|
3506
|
+
* connection: 'outlook', folder: 'shared', // bindings.json labels, both required
|
|
3496
3507
|
* }))
|
|
3497
3508
|
* ```
|
|
3498
3509
|
*
|
package/dist/core/actions.js
CHANGED
|
@@ -1090,8 +1090,9 @@ export function connector(a, b, c, d) {
|
|
|
1090
1090
|
export function subscriptionFrom(a, opts) {
|
|
1091
1091
|
if (!isTriggerDescriptor(a))
|
|
1092
1092
|
return a;
|
|
1093
|
-
// No
|
|
1094
|
-
//
|
|
1093
|
+
// No `where` is legal: an operation with no event parameters (HTTP Webhook)
|
|
1094
|
+
// has nothing to put in it, and the descriptor carries the version. The
|
|
1095
|
+
// `connection` / `folder` labels are still required (BINDING_MISSING).
|
|
1095
1096
|
return {
|
|
1096
1097
|
connector: a.connector,
|
|
1097
1098
|
event: a.event,
|
|
@@ -5,7 +5,9 @@ export type BindingRefusalCode = 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED'
|
|
|
5
5
|
/** An emitted definition's `<bindings.NAME>` placeholder has no `bindings[]` entry named NAME (#865). */
|
|
6
6
|
| 'BINDING_PLACEHOLDER_UNRESOLVED'
|
|
7
7
|
/** Two steps on one connection disagree on which connector it belongs to (#866). */
|
|
8
|
-
| 'CONNECTION_CONNECTOR_CONFLICT'
|
|
8
|
+
| 'CONNECTION_CONNECTOR_CONFLICT'
|
|
9
|
+
/** A connector-bound node names no `connection` or no `folder` label (#890). */
|
|
10
|
+
| 'BINDING_MISSING';
|
|
9
11
|
/**
|
|
10
12
|
* What `serialize` throws for a label it refuses. It is an ordinary `Error` whose
|
|
11
13
|
* message is the sentence `check` prints; the `code` lets `compile --no-check`
|
|
@@ -73,3 +75,22 @@ export declare function bindingUndeclaredMessage(where: string, name: string, at
|
|
|
73
75
|
* (flow-builder-sdk#866).
|
|
74
76
|
*/
|
|
75
77
|
export declare function connectionConnectorConflictMessage(connection: string, users: ReadonlyMap<string, readonly string[]>): string;
|
|
78
|
+
/**
|
|
79
|
+
* Which of a connector-bound node's two labels the author left out.
|
|
80
|
+
*
|
|
81
|
+
* A connector step, an event trigger, an `.entryPoint` onEvent and a
|
|
82
|
+
* `waitForEvent` all call Integration Service on a tenant connection, so the
|
|
83
|
+
* node needs BOTH halves: `connection` (→ `detail.connectionId`) and `folder`
|
|
84
|
+
* (→ `detail.folderKey`). Omitting a label used to compile the node with
|
|
85
|
+
* `connectionId: null` and no binding at all — `check` and `compile` were
|
|
86
|
+
* silent, and only `uip maestro flow validate` refused it ("A connection is
|
|
87
|
+
* required to run this activity." / "FolderKey is required for the connection
|
|
88
|
+
* binding.", flow-builder-sdk#890). Managed HTTP is NOT one of these sites: its
|
|
89
|
+
* pair is optional by design (absent = an unmanaged request).
|
|
90
|
+
*/
|
|
91
|
+
export declare function missingBindingLabels(labels: {
|
|
92
|
+
connection?: unknown;
|
|
93
|
+
folder?: unknown;
|
|
94
|
+
} | undefined): ('connection' | 'folder')[];
|
|
95
|
+
/** Why compile refuses a connector-bound node with no `connection` / `folder` label, and how to bind it. */
|
|
96
|
+
export declare function bindingMissingMessage(where: string, missing: readonly ('connection' | 'folder')[], target?: PrepareTarget): string;
|
|
@@ -110,3 +110,30 @@ export function connectionConnectorConflictMessage(connection, users) {
|
|
|
110
110
|
+ `\`http({ managed: true, … })\` step on another connector's connection must name that connector in `
|
|
111
111
|
+ `\`targetConnector\`.`;
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Which of a connector-bound node's two labels the author left out.
|
|
115
|
+
*
|
|
116
|
+
* A connector step, an event trigger, an `.entryPoint` onEvent and a
|
|
117
|
+
* `waitForEvent` all call Integration Service on a tenant connection, so the
|
|
118
|
+
* node needs BOTH halves: `connection` (→ `detail.connectionId`) and `folder`
|
|
119
|
+
* (→ `detail.folderKey`). Omitting a label used to compile the node with
|
|
120
|
+
* `connectionId: null` and no binding at all — `check` and `compile` were
|
|
121
|
+
* silent, and only `uip maestro flow validate` refused it ("A connection is
|
|
122
|
+
* required to run this activity." / "FolderKey is required for the connection
|
|
123
|
+
* binding.", flow-builder-sdk#890). Managed HTTP is NOT one of these sites: its
|
|
124
|
+
* pair is optional by design (absent = an unmanaged request).
|
|
125
|
+
*/
|
|
126
|
+
export function missingBindingLabels(labels) {
|
|
127
|
+
const blank = (v) => typeof v !== 'string' || v.trim() === '';
|
|
128
|
+
return ['connection', 'folder'].filter((k) => blank(labels?.[k]));
|
|
129
|
+
}
|
|
130
|
+
/** Why compile refuses a connector-bound node with no `connection` / `folder` label, and how to bind it. */
|
|
131
|
+
export function bindingMissingMessage(where, missing, target) {
|
|
132
|
+
return `${where} has no ${missing.map((m) => `\`${m}\``).join(' or ')}. It calls Integration Service on a tenant `
|
|
133
|
+
+ `connection, so the node needs both labels — \`connection\` (the connection id) and \`folder\` (its folder key), `
|
|
134
|
+
+ `each naming a bindings.json entry. Without them the .flow carries \`connectionId: null\` and `
|
|
135
|
+
+ `\`uip maestro flow validate\` refuses it ("A connection is required to run this activity." / "FolderKey is `
|
|
136
|
+
+ `required for the connection binding."). \`${prepareFor(target)}\` finds the connection and writes both entries; `
|
|
137
|
+
+ `with nothing real to bind yet, declare both labels in bindings.json with a placeholder resourceKey (a `
|
|
138
|
+
+ `CONNECTION_STUB warning, never an invented GUID).`;
|
|
139
|
+
}
|
|
@@ -185,6 +185,17 @@ use?: {
|
|
|
185
185
|
id: string;
|
|
186
186
|
connector: string | undefined;
|
|
187
187
|
}): void;
|
|
188
|
+
/**
|
|
189
|
+
* BINDING_MISSING: a connector-bound node with no `connection` or no `folder`
|
|
190
|
+
* label — the sentence compile throws (see `requireBindingPair` in serialize).
|
|
191
|
+
* Unlike {@link checkBindingLabels} this needs no bindings.json: an ABSENT label
|
|
192
|
+
* is wrong whatever the file says, because the node then compiles with
|
|
193
|
+
* `connectionId: null` and `flow validate` refuses it (flow-builder-sdk#890).
|
|
194
|
+
*/
|
|
195
|
+
export declare function checkBindingPresence(where: string, labels: {
|
|
196
|
+
connection?: unknown;
|
|
197
|
+
folder?: unknown;
|
|
198
|
+
} | undefined, report: (code: 'BINDING_MISSING', message: string) => void, target?: PrepareTarget): void;
|
|
188
199
|
/**
|
|
189
200
|
* The two phrases every lookup diagnostic needs: what the field sends, and how
|
|
190
201
|
* to resolve it.
|
|
@@ -26,7 +26,7 @@ import { isLookupToken, resolvedValue, } from './lookups.js';
|
|
|
26
26
|
import { Expr } from './expr.js';
|
|
27
27
|
import { extractRefs } from './expr-check.js';
|
|
28
28
|
import { prepareCommand, resolveFlagArg } from './cli-spelling.js';
|
|
29
|
-
import { bindingSelfNameMessage, bindingUndeclaredMessage, connectionConnectorConflictMessage, undeclaredBinding, } from './binding-messages.js';
|
|
29
|
+
import { bindingMissingMessage, bindingSelfNameMessage, bindingUndeclaredMessage, connectionConnectorConflictMessage, missingBindingLabels, undeclaredBinding, } from './binding-messages.js';
|
|
30
30
|
/**
|
|
31
31
|
* Flow: an input is an {@link Expr} object. A non-literal one is runtime; a
|
|
32
32
|
* literal one (`lit(…)`) carries the constant it was built from.
|
|
@@ -372,6 +372,18 @@ use) {
|
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* BINDING_MISSING: a connector-bound node with no `connection` or no `folder`
|
|
377
|
+
* label — the sentence compile throws (see `requireBindingPair` in serialize).
|
|
378
|
+
* Unlike {@link checkBindingLabels} this needs no bindings.json: an ABSENT label
|
|
379
|
+
* is wrong whatever the file says, because the node then compiles with
|
|
380
|
+
* `connectionId: null` and `flow validate` refuses it (flow-builder-sdk#890).
|
|
381
|
+
*/
|
|
382
|
+
export function checkBindingPresence(where, labels, report, target) {
|
|
383
|
+
const missing = missingBindingLabels(labels);
|
|
384
|
+
if (missing.length > 0)
|
|
385
|
+
report('BINDING_MISSING', bindingMissingMessage(where, missing, target));
|
|
386
|
+
}
|
|
375
387
|
/**
|
|
376
388
|
* The two phrases every lookup diagnostic needs: what the field sends, and how
|
|
377
389
|
* to resolve it.
|
package/dist/core/expr.d.ts
CHANGED
|
@@ -72,10 +72,9 @@ export declare function entryInput(entryPointId: string, name: string): Expr;
|
|
|
72
72
|
* Write the reference this way, or dotted by hand — never through a bracket
|
|
73
73
|
* (`$vars.fetchRate["output"]`, `$vars["fetchRate"].output`). Both of those
|
|
74
74
|
* resolve at run time, so the mistake is invisible, but only the DOTTED form is
|
|
75
|
-
* rewritten on the way out: a subflow copy renames node ids,
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* one applies. `path` may still bracket INTO the value — `out('rows', 'items')`
|
|
75
|
+
* rewritten on the way out: a subflow copy renames node ids, and a failure
|
|
76
|
+
* envelope resolves per node family. A bracketed read opts out of both and goes
|
|
77
|
+
* stale the moment one applies. `path` may still bracket INTO the value — `out('rows', 'items')`
|
|
79
78
|
* then `["odd key"]` — because nothing rewrites past the runtime variable.
|
|
80
79
|
*
|
|
81
80
|
* @param step - The step whose result to read, named as it was in `.step(...)`.
|
package/dist/core/expr.js
CHANGED
|
@@ -92,10 +92,9 @@ export function entryInput(entryPointId, name) {
|
|
|
92
92
|
* Write the reference this way, or dotted by hand — never through a bracket
|
|
93
93
|
* (`$vars.fetchRate["output"]`, `$vars["fetchRate"].output`). Both of those
|
|
94
94
|
* resolve at run time, so the mistake is invisible, but only the DOTTED form is
|
|
95
|
-
* rewritten on the way out: a subflow copy renames node ids,
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* one applies. `path` may still bracket INTO the value — `out('rows', 'items')`
|
|
95
|
+
* rewritten on the way out: a subflow copy renames node ids, and a failure
|
|
96
|
+
* envelope resolves per node family. A bracketed read opts out of both and goes
|
|
97
|
+
* stale the moment one applies. `path` may still bracket INTO the value — `out('rows', 'items')`
|
|
99
98
|
* then `["odd key"]` — because nothing rewrites past the runtime variable.
|
|
100
99
|
*
|
|
101
100
|
* @param step - The step whose result to read, named as it was in `.step(...)`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One namespace for a flow's declared variables.
|
|
3
|
+
*
|
|
4
|
+
* `.input()`, `.output()` and `.var()` each look like their own namespace in the
|
|
5
|
+
* builder, but the `.flow` stores all three as rows of ONE list,
|
|
6
|
+
* `variables.globals[]`, keyed by `id`:
|
|
7
|
+
*
|
|
8
|
+
* .input({ x }) → { id: 'x', direction: 'in' }
|
|
9
|
+
* .output({ x }) → { id: 'x', direction: 'out' }
|
|
10
|
+
* .var('x') → { id: 'x', direction: 'inout' }
|
|
11
|
+
*
|
|
12
|
+
* The product refuses two rows with one id (`uip maestro flow validate`:
|
|
13
|
+
* `Duplicate variable ID: "x" (appears 2 times)`). Before flow-builder-sdk#890,
|
|
14
|
+
* `check` and `compile` were silent and wrote both rows.
|
|
15
|
+
*
|
|
16
|
+
* The common shape is `.output({ summary }) + .var('summary')` — "the output
|
|
17
|
+
* the arms assign". The remedy keeps the output and renames the var:
|
|
18
|
+
* `.var('draftSummary')` + `.return({ summary: v('draftSummary') })`. The output
|
|
19
|
+
* then stays a declared `out` global — the direction the Flow docs and the eval
|
|
20
|
+
* checkers (`direction == "out"`) treat as the flow's output contract. Dropping the name from `.output()` also
|
|
21
|
+
* validates — a `.var()` is an `inout` global, which the product counts as an
|
|
22
|
+
* output too — but the flow then has no declared `out` for it.
|
|
23
|
+
*/
|
|
24
|
+
export type GlobalKind = 'input' | 'output' | 'var';
|
|
25
|
+
export interface GlobalNameCollision {
|
|
26
|
+
name: string;
|
|
27
|
+
/** Every declaration of the name, in declaration order (`input`, then `output`, then `var`). */
|
|
28
|
+
kinds: GlobalKind[];
|
|
29
|
+
}
|
|
30
|
+
/** The names declared more than once across `.input()`, `.output()` and `.var()`. */
|
|
31
|
+
export declare function globalNameCollisions(inputs: readonly {
|
|
32
|
+
name: string;
|
|
33
|
+
}[], outputs: readonly {
|
|
34
|
+
name: string;
|
|
35
|
+
}[], vars: readonly {
|
|
36
|
+
name: string;
|
|
37
|
+
}[]): GlobalNameCollision[];
|
|
38
|
+
/** The sentence `check` (VARIABLE_NAME_COLLISION) prints and `serialize` throws for one collision. */
|
|
39
|
+
export declare function globalNameCollisionMessage({ name, kinds }: GlobalNameCollision): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One namespace for a flow's declared variables.
|
|
3
|
+
*
|
|
4
|
+
* `.input()`, `.output()` and `.var()` each look like their own namespace in the
|
|
5
|
+
* builder, but the `.flow` stores all three as rows of ONE list,
|
|
6
|
+
* `variables.globals[]`, keyed by `id`:
|
|
7
|
+
*
|
|
8
|
+
* .input({ x }) → { id: 'x', direction: 'in' }
|
|
9
|
+
* .output({ x }) → { id: 'x', direction: 'out' }
|
|
10
|
+
* .var('x') → { id: 'x', direction: 'inout' }
|
|
11
|
+
*
|
|
12
|
+
* The product refuses two rows with one id (`uip maestro flow validate`:
|
|
13
|
+
* `Duplicate variable ID: "x" (appears 2 times)`). Before flow-builder-sdk#890,
|
|
14
|
+
* `check` and `compile` were silent and wrote both rows.
|
|
15
|
+
*
|
|
16
|
+
* The common shape is `.output({ summary }) + .var('summary')` — "the output
|
|
17
|
+
* the arms assign". The remedy keeps the output and renames the var:
|
|
18
|
+
* `.var('draftSummary')` + `.return({ summary: v('draftSummary') })`. The output
|
|
19
|
+
* then stays a declared `out` global — the direction the Flow docs and the eval
|
|
20
|
+
* checkers (`direction == "out"`) treat as the flow's output contract. Dropping the name from `.output()` also
|
|
21
|
+
* validates — a `.var()` is an `inout` global, which the product counts as an
|
|
22
|
+
* output too — but the flow then has no declared `out` for it.
|
|
23
|
+
*/
|
|
24
|
+
/** The names declared more than once across `.input()`, `.output()` and `.var()`. */
|
|
25
|
+
export function globalNameCollisions(inputs, outputs, vars) {
|
|
26
|
+
const seen = new Map();
|
|
27
|
+
const add = (kind, list) => {
|
|
28
|
+
for (const { name } of list) {
|
|
29
|
+
const kinds = seen.get(name);
|
|
30
|
+
if (kinds)
|
|
31
|
+
kinds.push(kind);
|
|
32
|
+
else
|
|
33
|
+
seen.set(name, [kind]);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
add('input', inputs);
|
|
37
|
+
add('output', outputs);
|
|
38
|
+
add('var', vars);
|
|
39
|
+
return [...seen].filter(([, kinds]) => kinds.length > 1).map(([name, kinds]) => ({ name, kinds }));
|
|
40
|
+
}
|
|
41
|
+
const SPELLING = {
|
|
42
|
+
input: (name) => `.input({ ${name} })`,
|
|
43
|
+
output: (name) => `.output({ ${name} })`,
|
|
44
|
+
var: (name) => `.var('${name}')`,
|
|
45
|
+
};
|
|
46
|
+
/** The sentence `check` (VARIABLE_NAME_COLLISION) prints and `serialize` throws for one collision. */
|
|
47
|
+
export function globalNameCollisionMessage({ name, kinds }) {
|
|
48
|
+
const declared = kinds.map((k) => SPELLING[k](name)).join(' and ');
|
|
49
|
+
const head = `"${name}" is declared ${kinds.length} times (${declared}). The .flow keeps inputs, outputs and `
|
|
50
|
+
+ `variables in one list, variables.globals[], so this compiles to ${kinds.length} globals with the id "${name}" `
|
|
51
|
+
+ `and \`uip maestro flow validate\` refuses it: Duplicate variable ID: "${name}".`;
|
|
52
|
+
const distinct = new Set(kinds);
|
|
53
|
+
if (distinct.size === 2 && distinct.has('output') && distinct.has('var')) {
|
|
54
|
+
return `${head} Keep the output and give the variable its own name: `
|
|
55
|
+
+ `.var('${name}Value', …) with { updates: { ${name}Value: … } }, then .return({ ${name}: v('${name}Value') }). `
|
|
56
|
+
+ `(Dropping "${name}" from .output() also validates — a .var() compiles to an \`inout\` global, which the `
|
|
57
|
+
+ `product counts as an output — but then the flow declares no \`out\` variable for it.)`;
|
|
58
|
+
}
|
|
59
|
+
return `${head} Rename one of them.`;
|
|
60
|
+
}
|
package/dist/decompile.js
CHANGED
|
@@ -37,6 +37,8 @@ import { inferScriptShape } from './core/script-return.js';
|
|
|
37
37
|
import { DEFAULT_TRIGGER_ID } from './core/expr.js';
|
|
38
38
|
import { parseIxpProjectType } from './core/actions.js';
|
|
39
39
|
import { readFlowGlobals } from './flow-variables.js';
|
|
40
|
+
import { missingBindingLabels } from './core/binding-messages.js';
|
|
41
|
+
import { prepareCommand } from './core/cli-spelling.js';
|
|
40
42
|
import { OUTCOME_HANDLE_NODE_TYPES } from './core/hitl-routing.js';
|
|
41
43
|
import { eventFilterFromTreeLeaf } from './core/event-filters.js';
|
|
42
44
|
/**
|
|
@@ -1517,6 +1519,25 @@ function emitHitl(node, imp, inputNames, graph) {
|
|
|
1517
1519
|
* `filterExpression` string, but reading the structured copies avoids parsing a
|
|
1518
1520
|
* string this file generated.
|
|
1519
1521
|
*/
|
|
1522
|
+
/**
|
|
1523
|
+
* A connector-bound node the file carries UNBOUND: Studio Web saves a draft whose
|
|
1524
|
+
* connection was never picked as `connectionId: null`. The source cannot invent
|
|
1525
|
+
* labels for it, and `compile` refuses a node without both (BINDING_MISSING, the
|
|
1526
|
+
* rule `flow validate` applies too: flow-builder-sdk#890). So the source says so
|
|
1527
|
+
* at the node and the run warns once, instead of handing back a file whose first
|
|
1528
|
+
* compile fails with no context. Returns the inline marker, or '' when bound.
|
|
1529
|
+
*/
|
|
1530
|
+
function unboundMarker(node, labels, key, action) {
|
|
1531
|
+
const missing = missingBindingLabels(labels);
|
|
1532
|
+
if (missing.length === 0)
|
|
1533
|
+
return '';
|
|
1534
|
+
const names = missing.join(' and ');
|
|
1535
|
+
warnOnce(`node ${str(node.id)} (${node.type}) has no ${missing.join(' or ')} in the .flow (an unbound draft). `
|
|
1536
|
+
+ `The source leaves ${missing.length > 1 ? 'them' : 'it'} out, and compile refuses the node (BINDING_MISSING) `
|
|
1537
|
+
+ `until it names both labels: run \`${prepareCommand(key, action ?? '<action>')}\`, then pass `
|
|
1538
|
+
+ `\`connection\` and \`folder\`.`);
|
|
1539
|
+
return ` /* TODO: bind ${names}; compile refuses this node until it has both labels (BINDING_MISSING) */`;
|
|
1540
|
+
}
|
|
1520
1541
|
function emitEventSubscription(node, imp, inputNames) {
|
|
1521
1542
|
const prefix = node.type.startsWith('uipath.connector.event.') ? 'uipath.connector.event.'
|
|
1522
1543
|
: node.type.startsWith('uipath.connector.trigger.') ? 'uipath.connector.trigger.'
|
|
@@ -1574,7 +1595,7 @@ function emitEventSubscription(node, imp, inputNames) {
|
|
|
1574
1595
|
['filters', filterRows.length ? `[${filterRows.join(', ')}]` : undefined],
|
|
1575
1596
|
['connection', conn !== undefined ? str(conn) : undefined],
|
|
1576
1597
|
['folder', folder !== undefined ? str(folder) : undefined],
|
|
1577
|
-
]);
|
|
1598
|
+
]) + unboundMarker(node, { connection: conn, folder }, connector, event);
|
|
1578
1599
|
}
|
|
1579
1600
|
function emitPublishedResource(node, imp, inputNames) {
|
|
1580
1601
|
const FAMILIES = [
|
|
@@ -1658,7 +1679,8 @@ function emitConnector(node, imp, inputNames, o) {
|
|
|
1658
1679
|
]);
|
|
1659
1680
|
const inputsSrc = objLit(flat) || '{}';
|
|
1660
1681
|
const args = [str(split.key), str(split.action), inputsSrc, ...(optsSrc ? [optsSrc] : [])];
|
|
1661
|
-
return `${imp.use('connector')}(${args.join(', ')})
|
|
1682
|
+
return `${imp.use('connector')}(${args.join(', ')})`
|
|
1683
|
+
+ unboundMarker(node, { connection, folder }, split.key, split.action);
|
|
1662
1684
|
}
|
|
1663
1685
|
/** Best-effort placeholder for a node type the reverse-map does not yet cover. */
|
|
1664
1686
|
function unsupportedSpec(node, imp, o) {
|
package/dist/flow-sdk.d.ts
CHANGED
|
@@ -148,6 +148,7 @@ export declare function manual(): TriggerSpec;
|
|
|
148
148
|
* connector: 'uipath-microsoft-outlook365',
|
|
149
149
|
* event: 'email-received',
|
|
150
150
|
* where: { parentFolderId: inboxId },
|
|
151
|
+
* connection: 'outlook', folder: 'shared', // bindings.json labels, both required
|
|
151
152
|
* }))
|
|
152
153
|
* .step('extract', script({ code: 'return { subject: $vars.start.output.subject };' }))
|
|
153
154
|
* .build();
|
|
@@ -740,12 +741,13 @@ declare class StepList {
|
|
|
740
741
|
* ```ts
|
|
741
742
|
* export default flow('draft-review')
|
|
742
743
|
* .output({ summary: types.string })
|
|
743
|
-
*
|
|
744
|
+
* // Not `.var('summary')`: an output and a var share one namespace (VARIABLE_NAME_COLLISION).
|
|
745
|
+
* .var('result', types.string)
|
|
744
746
|
* .stepSwitch('review', hitl({ fields: [], outcomes: ['Publish', 'Revise'] }), [
|
|
745
|
-
* { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: {
|
|
746
|
-
* { value: 'Revise', body: (b) => b.step('sendBack', script({ code: 'return "revise";', returns: 'string' }), { updates: {
|
|
747
|
+
* { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: { result: out('publish') } }) },
|
|
748
|
+
* { value: 'Revise', body: (b) => b.step('sendBack', script({ code: 'return "revise";', returns: 'string' }), { updates: { result: out('sendBack') } }) },
|
|
747
749
|
* ])
|
|
748
|
-
* .return({ summary: v('
|
|
750
|
+
* .return({ summary: v('result') })
|
|
749
751
|
* .build();
|
|
750
752
|
* ```
|
|
751
753
|
*/
|
package/dist/flow-sdk.js
CHANGED
|
@@ -332,12 +332,13 @@ class StepList {
|
|
|
332
332
|
* ```ts
|
|
333
333
|
* export default flow('draft-review')
|
|
334
334
|
* .output({ summary: types.string })
|
|
335
|
-
*
|
|
335
|
+
* // Not `.var('summary')`: an output and a var share one namespace (VARIABLE_NAME_COLLISION).
|
|
336
|
+
* .var('result', types.string)
|
|
336
337
|
* .stepSwitch('review', hitl({ fields: [], outcomes: ['Publish', 'Revise'] }), [
|
|
337
|
-
* { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: {
|
|
338
|
-
* { value: 'Revise', body: (b) => b.step('sendBack', script({ code: 'return "revise";', returns: 'string' }), { updates: {
|
|
338
|
+
* { value: 'Publish', body: (b) => b.step('publish', script({ code: 'return "published";', returns: 'string' }), { updates: { result: out('publish') } }) },
|
|
339
|
+
* { value: 'Revise', body: (b) => b.step('sendBack', script({ code: 'return "revise";', returns: 'string' }), { updates: { result: out('sendBack') } }) },
|
|
339
340
|
* ])
|
|
340
|
-
* .return({ summary: v('
|
|
341
|
+
* .return({ summary: v('result') })
|
|
341
342
|
* .build();
|
|
342
343
|
* ```
|
|
343
344
|
*/
|