@uipath/maestro-builder-sdk 6.15.2 → 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 +156 -156
- package/dist/api-members.md +409 -409
- package/dist/check.js +18 -4
- 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/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 +33 -5
- 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)
|
|
@@ -1257,9 +1264,15 @@ function checkConnectorSchema(step, spec, opts, diags) {
|
|
|
1257
1264
|
const { where, report } = connectorSite(step, diags);
|
|
1258
1265
|
coreCheckConnectorSchema(where, spec, opts, FLOW_EXPR_DIALECT, report);
|
|
1259
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
|
+
*/
|
|
1260
1272
|
function checkConnectorContract(step, spec, opts, diags) {
|
|
1261
1273
|
const { where, report } = connectorSite(step, diags);
|
|
1262
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 });
|
|
1263
1276
|
}
|
|
1264
1277
|
/** Reject a wrapper hop when the runtime shape of a list has been measured. */
|
|
1265
1278
|
function checkLoopListEnvelope(loop, connectors, opts, diags) {
|
|
@@ -1647,7 +1660,7 @@ function checkNodeUpdates(built, diags) {
|
|
|
1647
1660
|
};
|
|
1648
1661
|
visit(built.steps);
|
|
1649
1662
|
}
|
|
1650
|
-
function checkTrigger(spec, diags, opts = {}) {
|
|
1663
|
+
function checkTrigger(spec, diags, opts = {}, where) {
|
|
1651
1664
|
const push = (level) => (code, message, suggestion) => {
|
|
1652
1665
|
diags.push({ level, code, message, step: 'trigger', ...(suggestion ? { suggestion } : {}) });
|
|
1653
1666
|
};
|
|
@@ -1659,6 +1672,7 @@ function checkTrigger(spec, diags, opts = {}) {
|
|
|
1659
1672
|
checkEventSubscription(sub, 'trigger', at, push('warning'), eventParamsFor(sub, opts));
|
|
1660
1673
|
checkEventResolution(sub, 'trigger', 'trigger', opts, at, push('warning'), eventParamsFor(sub, opts));
|
|
1661
1674
|
checkBindingLabels('event trigger', sub, opts, at, eventTarget(sub), { id: 'trigger', connector: eventTarget(sub)?.key });
|
|
1675
|
+
checkBindingPresence(where ?? 'event trigger', sub, at, eventTarget(sub));
|
|
1662
1676
|
return;
|
|
1663
1677
|
}
|
|
1664
1678
|
// manual() is the explicit spelling of the default — nothing to validate.
|
|
@@ -1675,7 +1689,7 @@ function checkTrigger(spec, diags, opts = {}) {
|
|
|
1675
1689
|
if (spec?.kind !== 'scheduled') {
|
|
1676
1690
|
at('TRIGGER_UNKNOWN_KIND', `.trigger() was given ${JSON.stringify(spec?.kind)}, which is not a trigger. `
|
|
1677
1691
|
+ `Wrap it in a factory: scheduled({ every: 'R/PT1H' }) for a schedule, `
|
|
1678
|
-
+ `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 `
|
|
1679
1693
|
+ `submitted form, or conversationTrigger() to start on a new conversation. Omit .trigger() entirely `
|
|
1680
1694
|
+ `for the manual trigger (the default).`, `scheduled({ every: 'R/PT1H' })`);
|
|
1681
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.
|
|
@@ -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
|
*/
|
package/dist/serialize.js
CHANGED
|
@@ -16,7 +16,8 @@ import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
|
16
16
|
import { dataFabricUnknownInputs } from './core/typed-raw-node.js';
|
|
17
17
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
18
18
|
import { connectionConnectors } from './bindings-v2.js';
|
|
19
|
-
import {
|
|
19
|
+
import { globalNameCollisionMessage, globalNameCollisions } from './core/global-names.js';
|
|
20
|
+
import { BindingRefusal, GUID_RE, bindingMissingMessage, bindingSelfNameMessage, bindingUndeclaredMessage, missingBindingLabels, undeclaredBinding } from './core/binding-messages.js';
|
|
20
21
|
import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
|
|
21
22
|
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
|
|
22
23
|
import { declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
|
|
@@ -3484,6 +3485,27 @@ function refuseUnverifiedByoa(opts, where, sub) {
|
|
|
3484
3485
|
if (message)
|
|
3485
3486
|
throw new ByoaRefusal(message);
|
|
3486
3487
|
}
|
|
3488
|
+
/** Refuse a name declared twice across `.input()` / `.output()` / `.var()` — one `variables.globals[]` id each. */
|
|
3489
|
+
function refuseGlobalNameCollisions(inputs, outputs, vars) {
|
|
3490
|
+
const [first] = globalNameCollisions(inputs, outputs, vars);
|
|
3491
|
+
if (first)
|
|
3492
|
+
throw new Error(globalNameCollisionMessage(first));
|
|
3493
|
+
}
|
|
3494
|
+
/**
|
|
3495
|
+
* Refuse a connector-bound node (connector step, event trigger, `.entryPoint`
|
|
3496
|
+
* onEvent, `waitForEvent`) that names no `connection` or no `folder` label.
|
|
3497
|
+
* (An inline agent's connector tool is #875's: `check` refuses it as
|
|
3498
|
+
* INLINE_AGENT_TOOL_CONNECTOR_NO_CONNECTION, and serialize leaves it unbound.)
|
|
3499
|
+
* `resolveBinding` returns `undefined` for an absent label, which used to reach
|
|
3500
|
+
* the node as `detail.connectionId: null` with no binding — valid to compile,
|
|
3501
|
+
* refused by `flow validate` (flow-builder-sdk#890). `check` reports the same
|
|
3502
|
+
* sentence first (BINDING_MISSING).
|
|
3503
|
+
*/
|
|
3504
|
+
function requireBindingPair(where, labels, target) {
|
|
3505
|
+
const missing = missingBindingLabels(labels);
|
|
3506
|
+
if (missing.length > 0)
|
|
3507
|
+
throw new BindingRefusal('BINDING_MISSING', bindingMissingMessage(where, missing, target));
|
|
3508
|
+
}
|
|
3487
3509
|
/**
|
|
3488
3510
|
* Resolve a symbolic connection/folder name to the resource key that is inlined
|
|
3489
3511
|
* as `connectionId` / `connectionFolderKey`.
|
|
@@ -3709,7 +3731,8 @@ function flattenWhere(where, declared) {
|
|
|
3709
3731
|
walk(where, '');
|
|
3710
3732
|
return out;
|
|
3711
3733
|
}
|
|
3712
|
-
function eventDetail(rc, sub, nodeId, opts) {
|
|
3734
|
+
function eventDetail(rc, sub, nodeId, opts, site) {
|
|
3735
|
+
requireBindingPair(site, sub, eventTarget(sub));
|
|
3713
3736
|
const conn = resolveBinding(opts, sub.connection, 'connection', `event node "${nodeId}"`, eventTarget(sub));
|
|
3714
3737
|
const folder = resolveBinding(opts, sub.folder, 'folder', `event node "${nodeId}"`, eventTarget(sub));
|
|
3715
3738
|
const op = (rc.entry?.operation ?? {});
|
|
@@ -4420,6 +4443,7 @@ export function serialize(built, opts = {}) {
|
|
|
4420
4443
|
for (const t of tails)
|
|
4421
4444
|
addEdge(scope, t, endId);
|
|
4422
4445
|
}
|
|
4446
|
+
refuseGlobalNameCollisions(child.inputs, child.outputs, child.vars);
|
|
4423
4447
|
const globals = [];
|
|
4424
4448
|
for (const inp of child.inputs)
|
|
4425
4449
|
globals.push({ id: inp.name, name: inp.name, direction: 'in', type: inp.type, triggerNodeId: startId, ...varMeta(inp) });
|
|
@@ -4508,7 +4532,7 @@ export function serialize(built, opts = {}) {
|
|
|
4508
4532
|
// `detail` (see eventDetail).
|
|
4509
4533
|
entryPointId: stableId(`${built.id}:start:entryPoint`),
|
|
4510
4534
|
isDefaultEntryPoint: true,
|
|
4511
|
-
detail: eventDetail(eventTriggerRc, built.trigger.subscription, triggerId, opts),
|
|
4535
|
+
detail: eventDetail(eventTriggerRc, built.trigger.subscription, triggerId, opts, 'event trigger'),
|
|
4512
4536
|
}
|
|
4513
4537
|
: built.trigger?.kind === 'scheduled'
|
|
4514
4538
|
? scheduleInputs(built.trigger.inputs, triggerId)
|
|
@@ -4658,6 +4682,7 @@ export function serialize(built, opts = {}) {
|
|
|
4658
4682
|
const id = stepUid(scope, step.name);
|
|
4659
4683
|
const node = makeNode(rc.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def: rc.v1def });
|
|
4660
4684
|
declareListOutputType(node, rc);
|
|
4685
|
+
requireBindingPair(`connector "${step.name}"`, spec, { key: spec.key, action: spec.action });
|
|
4661
4686
|
const conn = resolveBinding(opts, spec.connection, 'connection', `connector "${step.name}"`, { key: spec.key, action: spec.action });
|
|
4662
4687
|
const folder = resolveBinding(opts, spec.folder, 'folder', `connector "${step.name}"`, { key: spec.key, action: spec.action });
|
|
4663
4688
|
// The platform reaches the connection through the flow's `bindings[]`,
|
|
@@ -5614,7 +5639,7 @@ export function serialize(built, opts = {}) {
|
|
|
5614
5639
|
refuseUnverifiedByoa(opts, `waitForEvent "${step.name}"`, sub);
|
|
5615
5640
|
const id = stepUid(scope, step.name);
|
|
5616
5641
|
const node = makeNode(rc.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def: rc.v1def });
|
|
5617
|
-
node.inputs = { detail: eventDetail(rc, sub, id, opts) };
|
|
5642
|
+
node.inputs = { detail: eventDetail(rc, sub, id, opts, `waitForEvent "${step.name}"`) };
|
|
5618
5643
|
// Same connection-binding closure as the connector branch (G-19).
|
|
5619
5644
|
const { entries: waitConnBindings } = connectionResourceBindings({
|
|
5620
5645
|
def: rc.v1def,
|
|
@@ -6471,7 +6496,7 @@ export function serialize(built, opts = {}) {
|
|
|
6471
6496
|
node = makeNode(rc.nodeType, ep.id, 'Event trigger', { def: rc.v1def, requestedVersion });
|
|
6472
6497
|
node.inputs = {
|
|
6473
6498
|
entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`),
|
|
6474
|
-
detail: eventDetail(rc, sub, ep.id, opts),
|
|
6499
|
+
detail: eventDetail(rc, sub, ep.id, opts, `onEvent() entry point "${ep.id}"`),
|
|
6475
6500
|
};
|
|
6476
6501
|
const { entries } = connectionResourceBindings({
|
|
6477
6502
|
def: rc.v1def,
|
|
@@ -6506,6 +6531,9 @@ export function serialize(built, opts = {}) {
|
|
|
6506
6531
|
}
|
|
6507
6532
|
}
|
|
6508
6533
|
}
|
|
6534
|
+
// Entry-point inputs are rows of the same globals[] (pushed below), so they
|
|
6535
|
+
// share the namespace; `check` reports these as ENTRY_POINT_INPUT_COLLISION.
|
|
6536
|
+
refuseGlobalNameCollisions([...built.inputs, ...entryPoints.flatMap((ep) => ep.inputs)], built.outputs, built.vars);
|
|
6509
6537
|
const globals = [];
|
|
6510
6538
|
const triggerNodeId = built.triggerId ?? 'start';
|
|
6511
6539
|
for (const inp of built.inputs)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.15.
|
|
3
|
+
"version": "6.15.3",
|
|
4
4
|
"description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.uipath.com/maestro",
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"@types/node": "^22.7.0",
|
|
93
93
|
"esbuild": "^0.28.1"
|
|
94
94
|
},
|
|
95
|
-
"gitref": "
|
|
95
|
+
"gitref": "a85d11af254f270d645ec46d57994ccb800ebc1f"
|
|
96
96
|
}
|