@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/cli-library.d.ts
CHANGED
|
@@ -15,7 +15,8 @@ export { openCliLibrary, connectorCheckOpts, eventResolveErrorFor, byoaCheckOpts
|
|
|
15
15
|
/**
|
|
16
16
|
* Flow's check options from a library the CLI already opened: the shared
|
|
17
17
|
* connector and event hooks, Flow's own field validator (it renders Flow
|
|
18
|
-
* expressions, as `serialize` does),
|
|
18
|
+
* expressions, as `serialize` does), the measured list envelopes, and the
|
|
19
|
+
* library and bindings its serializer pass compiles with.
|
|
19
20
|
*
|
|
20
21
|
* One builder for `flow check` and `flow compile`'s check pass, so the two
|
|
21
22
|
* cannot disagree.
|
package/dist/cli-library.js
CHANGED
|
@@ -5,14 +5,19 @@ export { openCliLibrary, connectorCheckOpts, eventResolveErrorFor, byoaCheckOpts
|
|
|
5
5
|
/**
|
|
6
6
|
* Flow's check options from a library the CLI already opened: the shared
|
|
7
7
|
* connector and event hooks, Flow's own field validator (it renders Flow
|
|
8
|
-
* expressions, as `serialize` does),
|
|
8
|
+
* expressions, as `serialize` does), the measured list envelopes, and the
|
|
9
|
+
* library and bindings its serializer pass compiles with.
|
|
9
10
|
*
|
|
10
11
|
* One builder for `flow check` and `flow compile`'s check pass, so the two
|
|
11
12
|
* cannot disagree.
|
|
12
13
|
*/
|
|
13
14
|
export function flowCheckOpts(library, bindings) {
|
|
14
15
|
// Flow's own refusal of an operation `prepare` synthesized from Integration Service (BPMN-only).
|
|
15
|
-
|
|
16
|
+
// The serializer pass gets what `compile` serializes with.
|
|
17
|
+
const shared = {
|
|
18
|
+
...connectorCheckOpts(library, bindings, connectorInputErrors, synthesizedDefinitionRefusal),
|
|
19
|
+
...(library ? { library } : {}),
|
|
20
|
+
};
|
|
16
21
|
if (!library)
|
|
17
22
|
return shared;
|
|
18
23
|
return {
|
package/dist/cli-result.d.ts
CHANGED
|
@@ -32,8 +32,22 @@ export declare function reportDiagnostics(diags: ReadonlyArray<{
|
|
|
32
32
|
level: string;
|
|
33
33
|
code: string;
|
|
34
34
|
message: string;
|
|
35
|
+
step?: string;
|
|
35
36
|
suggestion?: string;
|
|
36
37
|
}>): void;
|
|
38
|
+
/**
|
|
39
|
+
* One diagnostic as `check` and `compile` print it: its mark and code, the
|
|
40
|
+
* message, the step it is on (unless the message already names it), and the
|
|
41
|
+
* suggestion. For instance
|
|
42
|
+
* `✗ [STEP_SWITCH_EXIT_UNKNOWN] no exit named "maybe". … (step "route")`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function diagnosticLine(d: {
|
|
45
|
+
level: string;
|
|
46
|
+
code: string;
|
|
47
|
+
message: string;
|
|
48
|
+
step?: string;
|
|
49
|
+
suggestion?: string;
|
|
50
|
+
}): string;
|
|
37
51
|
/**
|
|
38
52
|
* The error diagnostic for a throw that ends a compile: the error's own string
|
|
39
53
|
* `code` when it carries one, else `COMPILE_FAILED`.
|
package/dist/cli-result.js
CHANGED
|
@@ -44,9 +44,26 @@ export function reportDiagnostics(diags) {
|
|
|
44
44
|
reportResult({
|
|
45
45
|
errors: diags.filter((d) => d.level === 'error').length,
|
|
46
46
|
warnings: diags.filter((d) => d.level !== 'error').length,
|
|
47
|
-
diagnostics: diags.map((d) => ({
|
|
47
|
+
diagnostics: diags.map((d) => ({
|
|
48
|
+
level: d.level, code: d.code, message: d.message,
|
|
49
|
+
...(d.step !== undefined ? { step: d.step } : {}),
|
|
50
|
+
...(d.suggestion ? { suggestion: d.suggestion } : {}),
|
|
51
|
+
})),
|
|
48
52
|
});
|
|
49
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* One diagnostic as `check` and `compile` print it: its mark and code, the
|
|
56
|
+
* message, the step it is on (unless the message already names it), and the
|
|
57
|
+
* suggestion. For instance
|
|
58
|
+
* `✗ [STEP_SWITCH_EXIT_UNKNOWN] no exit named "maybe". … (step "route")`.
|
|
59
|
+
*/
|
|
60
|
+
export function diagnosticLine(d) {
|
|
61
|
+
// A step inside a subflow is `<caller> › <step>`; the message names the innermost one.
|
|
62
|
+
const named = d.step !== undefined && d.message.includes(`"${d.step.split(' › ').pop()}"`);
|
|
63
|
+
return `${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}`
|
|
64
|
+
+ (d.step !== undefined && !named ? ` (step "${d.step}")` : '')
|
|
65
|
+
+ (d.suggestion ? ` (did you mean "${d.suggestion}"?)` : '');
|
|
66
|
+
}
|
|
50
67
|
/**
|
|
51
68
|
* The error diagnostic for a throw that ends a compile: the error's own string
|
|
52
69
|
* `code` when it carries one, else `COMPILE_FAILED`.
|
package/dist/compile-cli.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* product's own gates are supposed to answer. `.build()` still throws on
|
|
18
18
|
* structural problems; only the diagnostic pass is skipped.
|
|
19
19
|
*/
|
|
20
|
-
import { reportDiagnostics, reportResult, thrownDiagnostic } from './cli-result.js';
|
|
20
|
+
import { diagnosticLine, reportDiagnostics, reportResult, thrownDiagnostic } from './cli-result.js';
|
|
21
21
|
import { writeJsonFile } from './core/json-file.js';
|
|
22
22
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
23
23
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
@@ -80,7 +80,7 @@ export async function run(argv) {
|
|
|
80
80
|
const diags = check(built, flowCheckOpts(library, bindings));
|
|
81
81
|
const errors = diags.filter((d) => d.level === 'error');
|
|
82
82
|
for (const d of diags)
|
|
83
|
-
console.error(` ${
|
|
83
|
+
console.error(` ${diagnosticLine(d)}`);
|
|
84
84
|
diagnostics.push(...diags);
|
|
85
85
|
if (errors.length) {
|
|
86
86
|
reportDiagnostics(diagnostics);
|
|
@@ -17,5 +17,11 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export declare class CompileRefusal extends Error {
|
|
19
19
|
readonly code: string;
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* What to write instead, when the refusal knows (`check` reports it as the
|
|
22
|
+
* diagnostic's `suggestion`). For instance `value: 'approve'` for a
|
|
23
|
+
* `.stepSwitch` arm that names an exit the step does not declare.
|
|
24
|
+
*/
|
|
25
|
+
readonly suggestion?: string;
|
|
26
|
+
constructor(code: string, message: string, suggestion?: string);
|
|
21
27
|
}
|
|
@@ -17,10 +17,18 @@
|
|
|
17
17
|
*/
|
|
18
18
|
export class CompileRefusal extends Error {
|
|
19
19
|
code;
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* What to write instead, when the refusal knows (`check` reports it as the
|
|
22
|
+
* diagnostic's `suggestion`). For instance `value: 'approve'` for a
|
|
23
|
+
* `.stepSwitch` arm that names an exit the step does not declare.
|
|
24
|
+
*/
|
|
25
|
+
suggestion;
|
|
26
|
+
constructor(code, message, suggestion) {
|
|
21
27
|
super(message);
|
|
22
28
|
// `name` stays `Error`, so a converted `throw new Error(…)` prints exactly as
|
|
23
29
|
// it did (`Error: …` in `String(e)` and stacks). A subclass names itself.
|
|
24
30
|
this.code = code;
|
|
31
|
+
if (suggestion !== undefined)
|
|
32
|
+
this.suggestion = suggestion;
|
|
25
33
|
}
|
|
26
34
|
}
|
|
@@ -154,7 +154,12 @@ export interface ConnectorCheckOpts {
|
|
|
154
154
|
* author about everything ELSE that is wrong, which is the whole point of
|
|
155
155
|
* running the cheap pass first.
|
|
156
156
|
*/
|
|
157
|
-
export declare function checkConnectorLookups(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, dialect: ExprDialect, report: ReportConnector
|
|
157
|
+
export declare function checkConnectorLookups(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, dialect: ExprDialect, report: ReportConnector,
|
|
158
|
+
/**
|
|
159
|
+
* Whether to warn LOOKUP_UNRESOLVED. Flow's `check` does not: it runs the
|
|
160
|
+
* serializer, which refuses the node under that code.
|
|
161
|
+
*/
|
|
162
|
+
unresolvedWarning?: boolean): void;
|
|
158
163
|
/**
|
|
159
164
|
* The schema rules: an unprepared object, and unprepared custom fields.
|
|
160
165
|
*
|
|
@@ -179,9 +184,7 @@ export declare function checkConnectorSchema(where: string, spec: ConnectorSpec,
|
|
|
179
184
|
* bindings.json for the names); without them `check` stays a pure function of the
|
|
180
185
|
* built model and says nothing, as before.
|
|
181
186
|
*/
|
|
182
|
-
export declare function checkConnectorContract(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, report: ReportConnector
|
|
183
|
-
/** The activity's own id, for {@link connectionConnectorConflicts}; omit to record nothing. */
|
|
184
|
-
id?: string): void;
|
|
187
|
+
export declare function checkConnectorContract(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, report: ReportConnector): void;
|
|
185
188
|
/**
|
|
186
189
|
* The binding-label rules `serialize`'s `resolveBinding` enforces, for one
|
|
187
190
|
* `{ connection, folder }` pair — reported at every site that resolves one
|
|
@@ -195,12 +198,7 @@ id?: string): void;
|
|
|
195
198
|
export declare function checkBindingLabels(where: string, labels: {
|
|
196
199
|
connection?: unknown;
|
|
197
200
|
folder?: unknown;
|
|
198
|
-
} | undefined, opts: ConnectorCheckOpts, report: (code: 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED', message: string) => void, target?: PrepareTarget
|
|
199
|
-
/** The activity, and the connector its connection must belong to, for {@link connectionConnectorConflicts}. */
|
|
200
|
-
use?: {
|
|
201
|
-
id: string;
|
|
202
|
-
connector: string | undefined;
|
|
203
|
-
}): void;
|
|
201
|
+
} | undefined, opts: ConnectorCheckOpts, report: (code: 'BINDING_SELF_NAME' | 'BINDING_UNDECLARED', message: string) => void, target?: PrepareTarget): void;
|
|
204
202
|
/**
|
|
205
203
|
* BINDING_MISSING: a connector-bound node with no `connection` or no `folder`
|
|
206
204
|
* label — the sentence compile throws (see `requireBindingPair` in serialize).
|
|
@@ -231,23 +229,3 @@ export declare function lookupPhrases(field: string, lookup: LookupSpec, spec: C
|
|
|
231
229
|
target: string;
|
|
232
230
|
call: string;
|
|
233
231
|
};
|
|
234
|
-
/** One activity's use of a connection label, as the connector the connection must belong to. */
|
|
235
|
-
export interface ConnectionUse {
|
|
236
|
-
/** The activity's own id — Flow's step name, BPMN's element id. */
|
|
237
|
-
id: string;
|
|
238
|
-
label: string;
|
|
239
|
-
connector: string;
|
|
240
|
-
}
|
|
241
|
-
/** Run `check`, and hand back the connection uses its binding-label rules saw. */
|
|
242
|
-
export declare function collectConnectionUses(check: () => void): ConnectionUse[];
|
|
243
|
-
/**
|
|
244
|
-
* CONNECTION_CONNECTOR_CONFLICT — the rule `serialize` enforces on the emitted
|
|
245
|
-
* document (`connectionConnectors`, bindings-v2.ts), reported here with the same
|
|
246
|
-
* sentence so `check` does not print `✓ no issues` for a source `compile` then
|
|
247
|
-
* refuses. Labels are grouped by what they resolve to, so two labels bound to
|
|
248
|
-
* one connection id are one connection, as they are in the artifact.
|
|
249
|
-
*/
|
|
250
|
-
export declare function connectionConnectorConflicts(uses: readonly ConnectionUse[], opts: ConnectorCheckOpts): {
|
|
251
|
-
id: string;
|
|
252
|
-
message: string;
|
|
253
|
-
}[];
|
|
@@ -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 { bindingLabelRefusal, bindingMissingMessage,
|
|
29
|
+
import { bindingLabelRefusal, bindingMissingMessage, missingBindingLabels, } 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.
|
|
@@ -80,7 +80,12 @@ export const EQUALS_EXPR_DIALECT = {
|
|
|
80
80
|
* author about everything ELSE that is wrong, which is the whole point of
|
|
81
81
|
* running the cheap pass first.
|
|
82
82
|
*/
|
|
83
|
-
export function checkConnectorLookups(where, spec, opts, dialect, report
|
|
83
|
+
export function checkConnectorLookups(where, spec, opts, dialect, report,
|
|
84
|
+
/**
|
|
85
|
+
* Whether to warn LOOKUP_UNRESOLVED. Flow's `check` does not: it runs the
|
|
86
|
+
* serializer, which refuses the node under that code.
|
|
87
|
+
*/
|
|
88
|
+
unresolvedWarning = true) {
|
|
84
89
|
const specs = opts.lookups?.(spec.key, spec.action, spec.version, spec.object);
|
|
85
90
|
const resolutions = opts.resolutions ?? {};
|
|
86
91
|
// One LOOKUP_LITERAL_ID per field: a multiselect of three pasted ids is one
|
|
@@ -170,7 +175,7 @@ export function checkConnectorLookups(where, spec, opts, dialect, report) {
|
|
|
170
175
|
};
|
|
171
176
|
for (const [name, value] of Object.entries(spec.inputs ?? {}))
|
|
172
177
|
visit(value, [name]);
|
|
173
|
-
if (unresolved.length)
|
|
178
|
+
if (unresolved.length && unresolvedWarning)
|
|
174
179
|
report(unresolvedDiagnostic(where, spec, unresolved));
|
|
175
180
|
if (literals.length)
|
|
176
181
|
report(literalIdDiagnostic(where, spec, literals, dialect.descriptors));
|
|
@@ -328,9 +333,7 @@ export function checkConnectorSchema(where, spec, opts, dialect, report) {
|
|
|
328
333
|
* bindings.json for the names); without them `check` stays a pure function of the
|
|
329
334
|
* built model and says nothing, as before.
|
|
330
335
|
*/
|
|
331
|
-
export function checkConnectorContract(where, spec, opts, report
|
|
332
|
-
/** The activity's own id, for {@link connectionConnectorConflicts}; omit to record nothing. */
|
|
333
|
-
id) {
|
|
336
|
+
export function checkConnectorContract(where, spec, opts, report) {
|
|
334
337
|
const target = { key: spec.key, action: spec.action, version: spec.version, object: spec.object };
|
|
335
338
|
// The operation itself first: an operation compile cannot resolve has no field
|
|
336
339
|
// contract to check, and `connectorInputs` answers "unknown" for it.
|
|
@@ -344,7 +347,7 @@ id) {
|
|
|
344
347
|
report({ level: 'error', code: 'CONNECTOR_INPUT', message: `${where}: ${inputErr}` });
|
|
345
348
|
}
|
|
346
349
|
}
|
|
347
|
-
checkBindingLabels(where, spec, opts, (code, message) => report({ level: 'error', code, message }), { key: spec.key, action: spec.action }
|
|
350
|
+
checkBindingLabels(where, spec, opts, (code, message) => report({ level: 'error', code, message }), { key: spec.key, action: spec.action });
|
|
348
351
|
}
|
|
349
352
|
/**
|
|
350
353
|
* The binding-label rules `serialize`'s `resolveBinding` enforces, for one
|
|
@@ -356,13 +359,7 @@ id) {
|
|
|
356
359
|
* Silent without a bindings.json, and for a label that resolves to anything
|
|
357
360
|
* other than its own name (stub values are compile's CONNECTION_STUB warning).
|
|
358
361
|
*/
|
|
359
|
-
export function checkBindingLabels(where, labels, opts, report, target
|
|
360
|
-
/** The activity, and the connector its connection must belong to, for {@link connectionConnectorConflicts}. */
|
|
361
|
-
use) {
|
|
362
|
-
const label = labels?.connection;
|
|
363
|
-
if (use?.connector && typeof label === 'string' && label !== '') {
|
|
364
|
-
connectionUsage?.push({ id: use.id, label, connector: use.connector });
|
|
365
|
-
}
|
|
362
|
+
export function checkBindingLabels(where, labels, opts, report, target) {
|
|
366
363
|
const bindings = opts.bindings;
|
|
367
364
|
if (!bindings || !labels)
|
|
368
365
|
return;
|
|
@@ -485,49 +482,3 @@ function runtimeLookupDiagnostic(where, spec, field, lookup, js, dialect) {
|
|
|
485
482
|
: '');
|
|
486
483
|
return { level: isEmail ? 'error' : 'warning', code: 'LOOKUP_RUNTIME_VALUE', message };
|
|
487
484
|
}
|
|
488
|
-
/**
|
|
489
|
-
* The connection uses {@link checkBindingLabels} saw during the current check —
|
|
490
|
-
* the model and every child model, which share one `bindings[]`. Module state
|
|
491
|
-
* rather than a `ConnectorCheckOpts` field because a check is synchronous and
|
|
492
|
-
* this is not an input; {@link collectConnectionUses} saves and restores it, so
|
|
493
|
-
* a nested check cannot mix two models.
|
|
494
|
-
*/
|
|
495
|
-
let connectionUsage;
|
|
496
|
-
/** Run `check`, and hand back the connection uses its binding-label rules saw. */
|
|
497
|
-
export function collectConnectionUses(check) {
|
|
498
|
-
const outer = connectionUsage;
|
|
499
|
-
connectionUsage = [];
|
|
500
|
-
try {
|
|
501
|
-
check();
|
|
502
|
-
return connectionUsage;
|
|
503
|
-
}
|
|
504
|
-
finally {
|
|
505
|
-
connectionUsage = outer;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* CONNECTION_CONNECTOR_CONFLICT — the rule `serialize` enforces on the emitted
|
|
510
|
-
* document (`connectionConnectors`, bindings-v2.ts), reported here with the same
|
|
511
|
-
* sentence so `check` does not print `✓ no issues` for a source `compile` then
|
|
512
|
-
* refuses. Labels are grouped by what they resolve to, so two labels bound to
|
|
513
|
-
* one connection id are one connection, as they are in the artifact.
|
|
514
|
-
*/
|
|
515
|
-
export function connectionConnectorConflicts(uses, opts) {
|
|
516
|
-
const byConnection = new Map();
|
|
517
|
-
for (const use of uses) {
|
|
518
|
-
const key = opts.bindings?.resolve(use.label) ?? use.label;
|
|
519
|
-
const byConnector = byConnection.get(key) ?? new Map();
|
|
520
|
-
byConnection.set(key, byConnector);
|
|
521
|
-
byConnector.set(use.connector, [...(byConnector.get(use.connector) ?? []), use.id]);
|
|
522
|
-
}
|
|
523
|
-
const out = [];
|
|
524
|
-
for (const [key, byConnector] of byConnection) {
|
|
525
|
-
if (byConnector.size < 2)
|
|
526
|
-
continue;
|
|
527
|
-
out.push({
|
|
528
|
-
id: [...byConnector.values()][1][0],
|
|
529
|
-
message: connectionConnectorConflictMessage(key, byConnector),
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
return out;
|
|
533
|
-
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* The ROUTING is the library's business either way: a field's location is a
|
|
23
23
|
* property of the operation, not of the family emitting it.
|
|
24
24
|
*/
|
|
25
|
-
import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './lookups.js';
|
|
25
|
+
import { isLookupToken, resolvedValue, unresolvedLookupMessage, unresolvedLookupsMessage, } from './lookups.js';
|
|
26
26
|
import { prepareCommand } from './cli-spelling.js';
|
|
27
27
|
import { editDistance } from './edit-distance.js';
|
|
28
28
|
/** Flow's dialect, re-exported so `serialize.ts` need not reach past this module. */
|
|
@@ -101,6 +101,8 @@ export function buildConnectorInputs(rc, inputs, opts) {
|
|
|
101
101
|
const fields = (rc.fields ?? []);
|
|
102
102
|
const byName = new Map(fields.map((f) => [f.name, f]));
|
|
103
103
|
const errs = validateNestedConnectorKeys(fields, inputs, byName, dialect);
|
|
104
|
+
/** The unresolved lookups among `errs`: when they are all of them, the refusal is LOOKUP_UNRESOLVED. */
|
|
105
|
+
const owed = [];
|
|
104
106
|
const routed = {
|
|
105
107
|
bodyParameters: {}, pathParameters: {}, queryParameters: {},
|
|
106
108
|
};
|
|
@@ -149,6 +151,7 @@ export function buildConnectorInputs(rc, inputs, opts) {
|
|
|
149
151
|
if (policy.unresolvedLookups !== 'ignore') {
|
|
150
152
|
for (const token of unresolved)
|
|
151
153
|
errs.push(unresolvedLookupMessage(token));
|
|
154
|
+
owed.push(...unresolved);
|
|
152
155
|
continue;
|
|
153
156
|
}
|
|
154
157
|
if (isLookupToken(nested.value))
|
|
@@ -171,6 +174,11 @@ export function buildConnectorInputs(rc, inputs, opts) {
|
|
|
171
174
|
const slot = ownEntry(SLOT_FOR_LOCATION, location) ?? 'bodyParameters';
|
|
172
175
|
routed[slot][f.name] = render(nested.value);
|
|
173
176
|
}
|
|
177
|
+
// Inputs that are fine but for lookups `prepare` has not recorded are that one
|
|
178
|
+
// condition, LOOKUP_UNRESOLVED, with the one command that records them all.
|
|
179
|
+
if (errs.length && owed.length === errs.length) {
|
|
180
|
+
throw new CompileRefusal('LOOKUP_UNRESOLVED', `connector ${rc.nodeType}: ${unresolvedLookupsMessage(owed)}`);
|
|
181
|
+
}
|
|
174
182
|
if (errs.length) {
|
|
175
183
|
// Name the location of each required field: a "missing required input" on a
|
|
176
184
|
// path parameter reads very differently from one on a body field, and the
|
|
@@ -135,7 +135,14 @@ export declare function eventParamsFor(subscription: unknown, opts: EventCheckOp
|
|
|
135
135
|
* (compile repeats it as the hard failure), with the recorded answer looked up
|
|
136
136
|
* under the event-form node type — the form `prepare` stores.
|
|
137
137
|
*/
|
|
138
|
-
export declare function checkEventResolution(subscription: unknown, where: string, kind: 'trigger' | 'event', opts: EventCheckOpts, error: (code: string, message: string, suggestion?: string) => void, warn: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields
|
|
138
|
+
export declare function checkEventResolution(subscription: unknown, where: string, kind: 'trigger' | 'event', opts: EventCheckOpts, error: (code: string, message: string, suggestion?: string) => void, warn: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields,
|
|
139
|
+
/**
|
|
140
|
+
* Whether to report what `compile` refuses — an event the library cannot
|
|
141
|
+
* resolve (EVENT_UNKNOWN), an unverified BYOA connection, an unresolved
|
|
142
|
+
* `where` lookup. Flow's `check` passes `false`: it runs its serializer,
|
|
143
|
+
* which refuses each under the same code.
|
|
144
|
+
*/
|
|
145
|
+
compileRefusals?: boolean): void;
|
|
139
146
|
/**
|
|
140
147
|
* The compile-time refusal of a GENERIC event subscription that names no object,
|
|
141
148
|
* or `undefined` when the resolved event is fine.
|
|
@@ -167,4 +174,10 @@ export declare function genericEventObjectRefusal(rc: {
|
|
|
167
174
|
}, sub: {
|
|
168
175
|
where?: unknown;
|
|
169
176
|
}, objectOption: (object: string) => string): string | undefined;
|
|
170
|
-
export declare function checkEventSubscription(subscription: unknown, where: string, at: (code: string, message: string, suggestion?: string) => void, warn?: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields
|
|
177
|
+
export declare function checkEventSubscription(subscription: unknown, where: string, at: (code: string, message: string, suggestion?: string) => void, warn?: (code: string, message: string, suggestion?: string) => void, eventParams?: EventParamFields,
|
|
178
|
+
/**
|
|
179
|
+
* Whether to report what `compile` refuses — a generic event with no object,
|
|
180
|
+
* a filter leaf it cannot lower, an ambiguous backslash. Flow's `check`
|
|
181
|
+
* passes `false`: it runs its serializer, which refuses each under the same code.
|
|
182
|
+
*/
|
|
183
|
+
compileRefusals?: boolean): void;
|
|
@@ -77,7 +77,14 @@ export function eventParamsFor(subscription, opts) {
|
|
|
77
77
|
* (compile repeats it as the hard failure), with the recorded answer looked up
|
|
78
78
|
* under the event-form node type — the form `prepare` stores.
|
|
79
79
|
*/
|
|
80
|
-
export function checkEventResolution(subscription, where, kind, opts, error, warn, eventParams
|
|
80
|
+
export function checkEventResolution(subscription, where, kind, opts, error, warn, eventParams,
|
|
81
|
+
/**
|
|
82
|
+
* Whether to report what `compile` refuses — an event the library cannot
|
|
83
|
+
* resolve (EVENT_UNKNOWN), an unverified BYOA connection, an unresolved
|
|
84
|
+
* `where` lookup. Flow's `check` passes `false`: it runs its serializer,
|
|
85
|
+
* which refuses each under the same code.
|
|
86
|
+
*/
|
|
87
|
+
compileRefusals = true) {
|
|
81
88
|
const sub = subscription;
|
|
82
89
|
if (!sub || typeof sub.connector !== 'string' || typeof sub.event !== 'string'
|
|
83
90
|
|| sub.connector === '' || sub.event === '')
|
|
@@ -97,11 +104,12 @@ export function checkEventResolution(subscription, where, kind, opts, error, war
|
|
|
97
104
|
...(typeof sub.object === 'string' ? { object: sub.object } : {}),
|
|
98
105
|
});
|
|
99
106
|
if (unresolved !== undefined) {
|
|
100
|
-
|
|
107
|
+
if (compileRefusals)
|
|
108
|
+
error('EVENT_UNKNOWN', `${where}: ${unresolved}`);
|
|
101
109
|
return;
|
|
102
110
|
}
|
|
103
111
|
const object = typeof sub.object === 'string' && sub.object !== '' ? sub.object : undefined;
|
|
104
|
-
const byoa = opts.eventFacts && byoaUnverified(where, { connector: connectorKey, event: eventName, ...(object ? { object } : {}), connection: sub.connection }, opts.eventFacts(connectorKey, eventName, object), opts.byoa, opts.bindings);
|
|
112
|
+
const byoa = compileRefusals && opts.eventFacts && byoaUnverified(where, { connector: connectorKey, event: eventName, ...(object ? { object } : {}), connection: sub.connection }, opts.eventFacts(connectorKey, eventName, object), opts.byoa, opts.bindings);
|
|
105
113
|
if (byoa)
|
|
106
114
|
error(EVENT_BYOA_UNVERIFIED, byoa);
|
|
107
115
|
if (eventParams?.objectUnprepared && object) {
|
|
@@ -126,7 +134,7 @@ export function checkEventResolution(subscription, where, kind, opts, error, war
|
|
|
126
134
|
const resolutions = opts.resolutions ?? {};
|
|
127
135
|
const scope = sub.where && typeof sub.where === 'object'
|
|
128
136
|
? Object.entries(sub.where) : [];
|
|
129
|
-
for (const [key, value] of scope) {
|
|
137
|
+
for (const [key, value] of compileRefusals ? scope : []) {
|
|
130
138
|
if (!isLookupToken(value))
|
|
131
139
|
continue;
|
|
132
140
|
if (resolvedEventValue(value, resolutions) !== undefined)
|
|
@@ -242,7 +250,14 @@ export function genericEventObjectRefusal(rc, sub, objectOption) {
|
|
|
242
250
|
: '')
|
|
243
251
|
+ `\`uip is triggers objects ${rc.connectorKey} ${String(op.eventOperation ?? '<EVENT>')} --connection-id <id>\` lists the objects.`;
|
|
244
252
|
}
|
|
245
|
-
export function checkEventSubscription(subscription, where, at, warn, eventParams
|
|
253
|
+
export function checkEventSubscription(subscription, where, at, warn, eventParams,
|
|
254
|
+
/**
|
|
255
|
+
* Whether to report what `compile` refuses — a generic event with no object,
|
|
256
|
+
* a filter leaf it cannot lower, an ambiguous backslash. Flow's `check`
|
|
257
|
+
* passes `false`: it runs its serializer, which refuses each under the same code.
|
|
258
|
+
*/
|
|
259
|
+
compileRefusals = true) {
|
|
260
|
+
const refuse = compileRefusals ? at : () => { };
|
|
246
261
|
const sub = subscription;
|
|
247
262
|
if (!sub || typeof sub !== 'object') {
|
|
248
263
|
at('EVENT_NO_SUBSCRIPTION', `${where} has no event subscription. Pass one: `
|
|
@@ -276,7 +291,7 @@ export function checkEventSubscription(subscription, where, at, warn, eventParam
|
|
|
276
291
|
for (const [path, value] of scopeEntries.flatMap(([k, v]) => scopeStrings(v, k))) {
|
|
277
292
|
const ambiguous = jmesStringProblem(value);
|
|
278
293
|
if (ambiguous)
|
|
279
|
-
|
|
294
|
+
refuse('EVENT_FILTER_BACKSLASH', `${where}'s \`where.${path}\`: ${ambiguous}`);
|
|
280
295
|
}
|
|
281
296
|
// The OBJECT half of the contract. A GENERIC event (`record-created` on Data
|
|
282
297
|
// Fabric, Salesforce, ServiceNow, Jira, …) fires for ONE object of the connection
|
|
@@ -286,7 +301,7 @@ export function checkEventSubscription(subscription, where, at, warn, eventParam
|
|
|
286
301
|
// built in, so an `object` there names a choice that does not exist.
|
|
287
302
|
if (eventParams?.generic) {
|
|
288
303
|
if (object === undefined) {
|
|
289
|
-
|
|
304
|
+
refuse('EVENT_GENERIC_NO_OBJECT', `${where} subscribes to a GENERIC event: \`${String(sub.connector)}\` \`${String(sub.event)}\` fires for ONE `
|
|
290
305
|
+ `object of the connection (an entity, a table, a custom object) and the library carries no default. `
|
|
291
306
|
+ `Name it with \`object\`.`
|
|
292
307
|
+ (scopeObject !== undefined
|
|
@@ -347,28 +362,28 @@ export function checkEventSubscription(subscription, where, at, warn, eventParam
|
|
|
347
362
|
filters.forEach((f, i) => {
|
|
348
363
|
const leaf = f;
|
|
349
364
|
if (!leaf || typeof leaf.field !== 'string' || leaf.field === '') {
|
|
350
|
-
|
|
365
|
+
refuse('EVENT_FILTER_NO_FIELD', `${where}'s filters[${i}] has no \`field\` — name a field on the event payload, e.g. 'subject'.`);
|
|
351
366
|
}
|
|
352
367
|
const { keys, read } = readEventFilter(leaf);
|
|
353
368
|
if (keys.length === 0) {
|
|
354
|
-
|
|
369
|
+
refuse('EVENT_FILTER_NO_VALUE', `${where}'s filters[${i}] has no operator. Write exactly one of ${operators} with its value — `
|
|
355
370
|
+ `\`contains\` is a case-sensitive substring test, the same one Integration Service applies `
|
|
356
371
|
+ `server-side; \`lessThan\`/\`greaterThan\` order a number or an ISO date.`, `{ field: '${typeof leaf?.field === 'string' ? leaf.field : 'subject'}', contains: '<text>' }`);
|
|
357
372
|
}
|
|
358
373
|
else if (keys.length > 1) {
|
|
359
|
-
|
|
374
|
+
refuse('EVENT_FILTER_MANY_OPERATORS', `${where}'s filters[${i}] carries ${keys.length} operators (${keys.join(', ')}); a condition has exactly `
|
|
360
375
|
+ `one. Write two conditions instead — they are ANDed.`);
|
|
361
376
|
}
|
|
362
377
|
else if (!read) {
|
|
363
|
-
|
|
378
|
+
refuse('EVENT_FILTER_NO_VALUE', `${where}'s filters[${i}] gives \`${keys[0]}\` a value that is not a string, number or boolean.`);
|
|
364
379
|
}
|
|
365
380
|
else {
|
|
366
381
|
const ambiguous = typeof read.value === 'string' ? jmesStringProblem(read.value) : undefined;
|
|
367
382
|
const problem = eventFilterProblem(read);
|
|
368
383
|
if (ambiguous)
|
|
369
|
-
|
|
384
|
+
refuse('EVENT_FILTER_BACKSLASH', `${where}'s filters[${i}] on \`${read.field}\`: ${ambiguous}`);
|
|
370
385
|
else if (problem)
|
|
371
|
-
|
|
386
|
+
refuse('EVENT_FILTER_BAD_VALUE', `${where}'s filters[${i}] on \`${read.field}\`: ${problem}`);
|
|
372
387
|
}
|
|
373
388
|
});
|
|
374
389
|
}
|
|
@@ -87,9 +87,18 @@ type EdgeLike = {
|
|
|
87
87
|
* bare `completed` never is, unless the task asked for it with
|
|
88
88
|
* `legacyCompleted: true` (`legacyCompleted` here).
|
|
89
89
|
*/
|
|
90
|
-
export declare function hitlExitViolation(node: NodeLike, port: string, legacyCompleted?: boolean
|
|
90
|
+
export declare function hitlExitViolation(node: NodeLike, port: string, legacyCompleted?: boolean,
|
|
91
|
+
/**
|
|
92
|
+
* How the task's main path leaves it, when the caller knows: on its primary
|
|
93
|
+
* outcome (`{ primary }`, a task routed per outcome), or on every outcome
|
|
94
|
+
* (`'every-outcome'`). It picks the remedy a `completed` refusal names.
|
|
95
|
+
*/
|
|
96
|
+
routing?: {
|
|
97
|
+
primary: string;
|
|
98
|
+
} | 'every-outcome'): {
|
|
91
99
|
code: 'HITL_COMPLETED_PORT_GONE' | 'HITL_OUTCOME_PORT_UNKNOWN';
|
|
92
100
|
message: string;
|
|
101
|
+
suggestion?: string;
|
|
93
102
|
} | undefined;
|
|
94
103
|
/**
|
|
95
104
|
* The post-emit half of the guard: every edge of an emitted scope, whatever
|
|
@@ -183,7 +183,13 @@ function outcomeIdsOf(node) {
|
|
|
183
183
|
* bare `completed` never is, unless the task asked for it with
|
|
184
184
|
* `legacyCompleted: true` (`legacyCompleted` here).
|
|
185
185
|
*/
|
|
186
|
-
export function hitlExitViolation(node, port, legacyCompleted = false
|
|
186
|
+
export function hitlExitViolation(node, port, legacyCompleted = false,
|
|
187
|
+
/**
|
|
188
|
+
* How the task's main path leaves it, when the caller knows: on its primary
|
|
189
|
+
* outcome (`{ primary }`, a task routed per outcome), or on every outcome
|
|
190
|
+
* (`'every-outcome'`). It picks the remedy a `completed` refusal names.
|
|
191
|
+
*/
|
|
192
|
+
routing) {
|
|
187
193
|
if (!OUTCOME_HANDLE_NODE_TYPES.has(String(node.type)))
|
|
188
194
|
return undefined;
|
|
189
195
|
const ids = outcomeIdsOf(node);
|
|
@@ -193,17 +199,26 @@ export function hitlExitViolation(node, port, legacyCompleted = false) {
|
|
|
193
199
|
if (port === 'completed') {
|
|
194
200
|
if (legacyCompleted)
|
|
195
201
|
return undefined;
|
|
202
|
+
const ports = ids.map((i) => `"outcome-${i}"`).join(', ');
|
|
196
203
|
return {
|
|
197
204
|
code: 'HITL_COMPLETED_PORT_GONE',
|
|
198
|
-
message: `human task "${String(node.id)}" has outcomes
|
|
199
|
-
+ `
|
|
200
|
-
+ `outcome
|
|
205
|
+
message: `Port "completed" does not exist on human task "${String(node.id)}". It has outcomes, so the `
|
|
206
|
+
+ `product draws one handle per outcome (${ports}) and no 'completed' — an edge there is remapped to `
|
|
207
|
+
+ `the primary outcome and the others lose their path.`
|
|
208
|
+
+ (routing === 'every-outcome' ? ' .step() after this task already continues every outcome to the next step.'
|
|
209
|
+
: routing !== undefined ? ` The FIRST outcome ("${routing.primary}") is what continues the main path.` : ''),
|
|
210
|
+
...(routing === 'every-outcome'
|
|
211
|
+
? { suggestion: "drop the 'completed' route — .step() continues every outcome — or route each outcome with .stepSwitch" }
|
|
212
|
+
: routing !== undefined
|
|
213
|
+
? { suggestion: `route the outcomes (.stepToList('outcome-${ids[1] ?? ids[0]}', …)), or continue every outcome to the next step with outcomePorts: false` }
|
|
214
|
+
: {}),
|
|
201
215
|
};
|
|
202
216
|
}
|
|
203
217
|
if (port.startsWith('outcome-') && !ids.includes(port.slice('outcome-'.length))) {
|
|
204
218
|
return {
|
|
205
219
|
code: 'HITL_OUTCOME_PORT_UNKNOWN',
|
|
206
|
-
message: `human task "${String(node.id)}" has no outcome for port "${port}" — it declares ${declared}
|
|
220
|
+
message: `human task "${String(node.id)}" has no outcome for port "${port}" — it declares ${declared}. `
|
|
221
|
+
+ `Ids are the outcome names slugified (lowercase, non-alphanumerics to '-').`,
|
|
207
222
|
};
|
|
208
223
|
}
|
|
209
224
|
return undefined;
|
package/dist/core/lookups.d.ts
CHANGED
|
@@ -209,6 +209,19 @@ export declare function resolvedEventValue(token: LookupToken, resolutions: Look
|
|
|
209
209
|
* @returns A diagnostic whose last line can be pasted into a shell.
|
|
210
210
|
*/
|
|
211
211
|
export declare function unresolvedLookupMessage(token: LookupToken): string;
|
|
212
|
+
/**
|
|
213
|
+
* The message for every lookup one node still owes, with the ONE `prepare` that
|
|
214
|
+
* records them all (it takes every `--resolve` in one invocation). For instance:
|
|
215
|
+
*
|
|
216
|
+
* inputs `channel` (profile.email="ada@x.com"), `send_as` (name="bot") are unresolved lookups. …
|
|
217
|
+
* npx flow-sdk registry prepare uipath-salesforce-slack --action send-message-to-user \
|
|
218
|
+
* --resolve channel:profile.email=ada@x.com \
|
|
219
|
+
* --resolve send_as:name=bot
|
|
220
|
+
*
|
|
221
|
+
* @param tokens - The node's unresolved lookups, at least one, all of one node type.
|
|
222
|
+
* @returns A message whose last lines can be pasted into a shell.
|
|
223
|
+
*/
|
|
224
|
+
export declare function unresolvedLookupsMessage(tokens: readonly LookupToken[]): string;
|
|
212
225
|
type RawReference = {
|
|
213
226
|
objectName?: string;
|
|
214
227
|
path?: string;
|
package/dist/core/lookups.js
CHANGED
|
@@ -230,6 +230,32 @@ export function unresolvedLookupMessage(token) {
|
|
|
230
230
|
// is a glob and a real name has spaces, and the last line is meant to paste.
|
|
231
231
|
` --resolve ${resolveFlagArg(field, by, String(value))}`);
|
|
232
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* The message for every lookup one node still owes, with the ONE `prepare` that
|
|
235
|
+
* records them all (it takes every `--resolve` in one invocation). For instance:
|
|
236
|
+
*
|
|
237
|
+
* inputs `channel` (profile.email="ada@x.com"), `send_as` (name="bot") are unresolved lookups. …
|
|
238
|
+
* npx flow-sdk registry prepare uipath-salesforce-slack --action send-message-to-user \
|
|
239
|
+
* --resolve channel:profile.email=ada@x.com \
|
|
240
|
+
* --resolve send_as:name=bot
|
|
241
|
+
*
|
|
242
|
+
* @param tokens - The node's unresolved lookups, at least one, all of one node type.
|
|
243
|
+
* @returns A message whose last lines can be pasted into a shell.
|
|
244
|
+
*/
|
|
245
|
+
export function unresolvedLookupsMessage(tokens) {
|
|
246
|
+
if (tokens.length === 1)
|
|
247
|
+
return unresolvedLookupMessage(tokens[0]);
|
|
248
|
+
const { nodeType } = tokens[0].__lookup;
|
|
249
|
+
const rest = nodeType.replace(/^uipath\.connector\.(?:trigger\.|event\.)?/, '');
|
|
250
|
+
const dot = rest.lastIndexOf('.');
|
|
251
|
+
const key = dot > 0 ? rest.slice(0, dot) : rest;
|
|
252
|
+
const action = dot > 0 ? rest.slice(dot + 1) : rest;
|
|
253
|
+
const named = tokens.map((t) => `\`${t.__lookup.field}\` (${t.__lookup.by}=${JSON.stringify(t.__lookup.value)})`);
|
|
254
|
+
const args = tokens.map((t) => ` --resolve ${resolveFlagArg(t.__lookup.field, t.__lookup.by, String(t.__lookup.value))}`);
|
|
255
|
+
return `inputs ${named.join(', ')} are unresolved lookups. `
|
|
256
|
+
+ `Compile does not make network calls, so the values have to be resolved first:\n`
|
|
257
|
+
+ ` ${prepareCommand(key, action)} \\\n${args.join(' \\\n')}`;
|
|
258
|
+
}
|
|
233
259
|
// ─── Classification, mirroring `_lookups.py` ─────────────────────────────────
|
|
234
260
|
//
|
|
235
261
|
// The generator decides which fields get a helper; `check` has to agree, or it
|
package/dist/core/refusals.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export declare const REFUSALS: {
|
|
|
55
55
|
readonly CARRIED_DEFINITION_CONFLICT: "lowering";
|
|
56
56
|
readonly CARRIED_DEFINITION_INVALID: "lowering";
|
|
57
57
|
readonly CEQL_UNPARSEABLE: "lowering";
|
|
58
|
+
readonly COMPILE_FAILED: "lowering";
|
|
58
59
|
readonly CONNECTION_CONNECTOR_CONFLICT: "draft";
|
|
59
60
|
readonly CONNECTION_STUB: "lint";
|
|
60
61
|
readonly CONNECTOR_IMPORT_NOT_A_FILE: "lowering";
|
|
@@ -209,6 +210,7 @@ export declare const REFUSALS: {
|
|
|
209
210
|
readonly HITL_OUTCOME_ACTION: "lint";
|
|
210
211
|
readonly HITL_OUTCOME_BAD_ID: "lint";
|
|
211
212
|
readonly HITL_OUTCOME_NO_NAME: "lint";
|
|
213
|
+
readonly HITL_OUTCOME_PORTS_NONE: "lowering";
|
|
212
214
|
readonly HITL_OUTCOME_PORTS_OFF: "lowering";
|
|
213
215
|
readonly HITL_OUTCOME_PORTS_VARIANT: "lowering";
|
|
214
216
|
readonly HITL_OUTCOME_PORT_MISPLACED: "lint";
|