@uipath/maestro-builder-sdk 6.21.2 → 6.22.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/dist/api-index.md +138 -138
- package/dist/api-members.md +407 -407
- package/dist/check.js +43 -331
- package/dist/core/actions.d.ts +0 -147
- package/dist/core/actions.js +0 -15
- package/dist/core/expr.d.ts +1 -1
- package/dist/core/expr.js +2 -2
- package/dist/core/nodes/node-type.d.ts +15 -0
- package/dist/core/step-ports.js +7 -23
- package/dist/core/typed-raw-node.js +0 -2
- package/dist/decompile.js +44 -88
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +59 -0
- package/dist/flow-sdk.d.ts +1 -0
- package/dist/flow-sdk.js +1 -0
- package/dist/flow-step-exits.d.ts +12 -0
- package/dist/flow-step-exits.js +30 -0
- package/dist/nodes/http/flow/check.d.ts +58 -0
- package/dist/nodes/http/flow/check.js +248 -0
- package/dist/nodes/http/flow/factories.d.ts +14 -0
- package/dist/nodes/http/flow/factories.js +20 -0
- package/dist/nodes/http/flow/family.d.ts +8 -0
- package/dist/nodes/http/flow/family.js +13 -0
- package/dist/nodes/http/flow/index.d.ts +3 -0
- package/dist/nodes/http/flow/index.js +3 -0
- package/dist/nodes/http/flow/node-type.d.ts +7 -0
- package/dist/nodes/http/flow/node-type.js +411 -0
- package/dist/nodes/http/flow/spec.d.ts +151 -0
- package/dist/nodes/http/flow/spec.js +1 -0
- package/dist/serialize.d.ts +2 -2
- package/dist/serialize.js +20 -276
- package/package.json +2 -2
package/dist/check.js
CHANGED
|
@@ -20,7 +20,7 @@ import { commentText } from './core/comment-text.js';
|
|
|
20
20
|
import { nearestName } from './core/edit-distance.js';
|
|
21
21
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
22
22
|
import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome, outcomeIdOf } from './core/hitl-routing.js';
|
|
23
|
-
import {
|
|
23
|
+
import { hasExits } from './core/step-ports.js';
|
|
24
24
|
import { stopsAtEnd } from './flow-step-tree.js';
|
|
25
25
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
26
26
|
import { compareSchemaVersions, schemaVersionRefusal } from './schema-version.js';
|
|
@@ -28,6 +28,8 @@ import { planFlowGlobals, planSharedInputs, sharedInputRoots } from './flow-vari
|
|
|
28
28
|
import { planOutputBacking } from './flow-output-backing.js';
|
|
29
29
|
import { ownEntry } from './core/own-entry.js';
|
|
30
30
|
import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
31
|
+
import { stepExits } from './flow-step-exits.js';
|
|
32
|
+
import { plainHttpOnErrorMessage } from './nodes/http/flow/family.js';
|
|
31
33
|
import { resolveVersion } from './core/nodes/versioned.js';
|
|
32
34
|
import { declaredErrorPortRefusal } from './core/error-port.js';
|
|
33
35
|
export function check(built, opts = {}) {
|
|
@@ -125,6 +127,14 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
125
127
|
const registeredSteps = new Map();
|
|
126
128
|
/** Steps of a registered kind whose version did not resolve (NODE_VERSION_UNREGISTERED), so a family rule can stay silent about them. */
|
|
127
129
|
const unresolvedSteps = new Set();
|
|
130
|
+
/**
|
|
131
|
+
* Every step of a registered kind, placed or not, with the implementation its
|
|
132
|
+
* node-local rules ran with: an unregistered pin falls back to the version it
|
|
133
|
+
* would get unpinned. The step-level rules that follow a step (its output
|
|
134
|
+
* reads, its `.onError`) ask this one, so a wrong pin is not followed by a
|
|
135
|
+
* second round of mistakes. Family rules use `registeredSteps` only.
|
|
136
|
+
*/
|
|
137
|
+
const checkedSteps = new Map();
|
|
128
138
|
/**
|
|
129
139
|
* Every expression the flow carries, with the step that failed when the
|
|
130
140
|
* expression sits inside that step's error handler. The failed step's own
|
|
@@ -270,13 +280,14 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
270
280
|
declared.add(s.name); // a step's output is referenceable by name
|
|
271
281
|
}
|
|
272
282
|
if ((s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port === 'error')
|
|
273
|
-
checkOnError(s, prevPortSource, diags, prevPortSource &&
|
|
283
|
+
checkOnError(s, prevPortSource, diags, prevPortSource && checkedSteps.get(prevPortSource.name));
|
|
274
284
|
if ((s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port.startsWith('branch-')) {
|
|
275
285
|
const want = s.port.slice('branch-'.length);
|
|
276
286
|
const prevSpec = prevAction?.spec;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
287
|
+
// Whichever type the preceding step is, its declared exits say whether it
|
|
288
|
+
// has this port: only an HTTP step names `branch-<name>` exits.
|
|
289
|
+
const exits = prevSpec && 'kind' in prevSpec ? stepExits(prevSpec, prevAction?.options?.version) : undefined;
|
|
290
|
+
const declared = exits !== undefined && hasExits(exits) && exits.some((e) => e.port === s.port);
|
|
280
291
|
if (!declared) {
|
|
281
292
|
diags.push({
|
|
282
293
|
level: 'error', code: 'HTTP_BRANCH_UNKNOWN', step: prevAction?.name,
|
|
@@ -375,9 +386,10 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
375
386
|
prevPortSource = s;
|
|
376
387
|
const registered = registeredKind(s.spec);
|
|
377
388
|
if (registered) {
|
|
378
|
-
const placed = checkRegisteredStep(registered, s, exprs, errorSource, opts, diags, typeOf, triggerId);
|
|
389
|
+
const { placed, checked } = checkRegisteredStep(registered, s, exprs, errorSource, opts, diags, typeOf, triggerId);
|
|
390
|
+
checkedSteps.set(s.name, checked);
|
|
379
391
|
if (placed)
|
|
380
|
-
registeredSteps.set(s.name,
|
|
392
|
+
registeredSteps.set(s.name, checked);
|
|
381
393
|
else
|
|
382
394
|
unresolvedSteps.add(s.name);
|
|
383
395
|
}
|
|
@@ -400,43 +412,6 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
400
412
|
}
|
|
401
413
|
checkContribution(maybeCustom, s.options?.version, s.name, own.some((d) => d.severity === 'error'), diags);
|
|
402
414
|
}
|
|
403
|
-
if (s.spec.kind === 'http') {
|
|
404
|
-
checkHttp(s.name, s.spec.inputs, diags);
|
|
405
|
-
// Only a complete pair: an empty half is HTTP_MANAGED_BINDING_INCOMPLETE, which
|
|
406
|
-
// serialize refuses before it resolves either name.
|
|
407
|
-
const pair = s.spec.inputs;
|
|
408
|
-
const filled = (v) => typeof v === 'string' && v.trim() !== '';
|
|
409
|
-
if (pair?.managed && filled(pair.connection) && filled(pair.folder)) {
|
|
410
|
-
const httpStep = s.name;
|
|
411
|
-
const target = pair.targetConnector;
|
|
412
|
-
checkBindingLabels(`managed http "${httpStep}"`, pair, opts, (code, message) => diags.push({ level: 'error', code, step: httpStep, message }), undefined,
|
|
413
|
-
// The connector its CONNECTION belongs to: the target, not the wrapper.
|
|
414
|
-
{ id: httpStep, connector: typeof target === 'string' && target !== '' ? target : 'uipath-uipath-http' });
|
|
415
|
-
}
|
|
416
|
-
const seen = new Set();
|
|
417
|
-
for (const b of s.spec.inputs.branches ?? []) {
|
|
418
|
-
addExpr(b.condition.js, s.name);
|
|
419
|
-
if (!/^[A-Za-z0-9_-]+$/.test(b.name)) {
|
|
420
|
-
diags.push({
|
|
421
|
-
level: 'error', code: 'HTTP_BRANCH_NAME', step: s.name,
|
|
422
|
-
message: `HTTP branch ${JSON.stringify(b.name)} on "${s.name}" is not port-safe — use letters, digits, _ or - (it becomes the branch-<name> port).`,
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
if (seen.has(b.name)) {
|
|
426
|
-
diags.push({
|
|
427
|
-
level: 'error', code: 'HTTP_BRANCH_DUP', step: s.name,
|
|
428
|
-
message: `HTTP branch ${JSON.stringify(b.name)} on "${s.name}" is declared twice.`,
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
seen.add(b.name);
|
|
432
|
-
if (b.condition.literal) {
|
|
433
|
-
diags.push({
|
|
434
|
-
level: 'error', code: 'HTTP_BRANCH_CONST', step: s.name,
|
|
435
|
-
message: `HTTP branch ${JSON.stringify(b.name)} on "${s.name}" tests a constant — it would fire always or never. Reference the response, e.g. $vars.${s.name}.output.statusCode.`,
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
415
|
// `.stepSwitch` routes its exits as ARMS, so the `.stepToList` bookkeeping
|
|
441
416
|
// would report every one of them unrouted. STEP_SWITCH_EXIT_UNROUTED is
|
|
442
417
|
// that step's version of the same question.
|
|
@@ -706,7 +681,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
706
681
|
// on the first cut. The action rules have run by now; what is left is the
|
|
707
682
|
// arms and the three things only this step can get wrong.
|
|
708
683
|
if (s0.kind === 'stepSwitch') {
|
|
709
|
-
const exits =
|
|
684
|
+
const exits = stepExits(s0.spec, s0.options?.version);
|
|
710
685
|
if (!hasExits(exits)) {
|
|
711
686
|
diags.push({
|
|
712
687
|
level: 'error', code: 'STEP_SWITCH_NO_EXITS', step: s0.name,
|
|
@@ -1044,7 +1019,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
1044
1019
|
// per-family rules below, because the envelope carries the same fields
|
|
1045
1020
|
// whatever the node publishes on its success path; which VARIABLE holds it
|
|
1046
1021
|
// is a per-family runtime boundary `serialize` rewrites, not something the
|
|
1047
|
-
// author writes (`
|
|
1022
|
+
// author writes (a node type's `errorEnvelope`).
|
|
1048
1023
|
if (field === 'error') {
|
|
1049
1024
|
const tail = rest ? rest.slice(1).split('.')[0] : undefined;
|
|
1050
1025
|
if (tail !== undefined)
|
|
@@ -1095,13 +1070,17 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
1095
1070
|
}
|
|
1096
1071
|
// A registered node type declares what its output is; `none` reports here,
|
|
1097
1072
|
// at the same point as the per-family no-output rules below.
|
|
1098
|
-
const placed =
|
|
1073
|
+
const placed = checkedSteps.get(name);
|
|
1099
1074
|
if (placed) {
|
|
1100
1075
|
const contract = placed.impl.output(placed.spec);
|
|
1101
1076
|
if (contract.kind === 'none') {
|
|
1102
1077
|
diags.push(catalogued(placed.entry, step, contract.code, contract.message({ node: name, ref: `$vars.${name}${field ? '.' + field : ''}`, reader: step })));
|
|
1103
1078
|
continue;
|
|
1104
1079
|
}
|
|
1080
|
+
if (contract.kind === 'reader') {
|
|
1081
|
+
contract.read({ node: name, ref: `$vars.${name}${field ? '.' + field : ''}${rest}`, reader: step, field, rest }, (code, message, suggestion) => diags.push(catalogued(placed.entry, step, code, message, suggestion)));
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1105
1084
|
}
|
|
1106
1085
|
// A switch only routes; it has no output of its own, so any read of it is
|
|
1107
1086
|
// undefined at run time. Point the author at the arm's step instead.
|
|
@@ -1176,7 +1155,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
1176
1155
|
}
|
|
1177
1156
|
// A registered node type that states its output's fields: the same read
|
|
1178
1157
|
// rule, worded by its contract and reported through its family's catalog.
|
|
1179
|
-
const fieldsPlaced =
|
|
1158
|
+
const fieldsPlaced = checkedSteps.get(name);
|
|
1180
1159
|
const fieldsContract = fieldsPlaced?.impl.output(fieldsPlaced.spec);
|
|
1181
1160
|
if (fieldsPlaced && fieldsContract?.kind === 'fields') {
|
|
1182
1161
|
const found = [];
|
|
@@ -2434,79 +2413,6 @@ function checkPublishedFunctionRead(name, field, rest, spec, step, diags) {
|
|
|
2434
2413
|
suggestion: `returns: { ${wanted}: 'string' }`,
|
|
2435
2414
|
});
|
|
2436
2415
|
}
|
|
2437
|
-
/**
|
|
2438
|
-
* The fields every HTTP response carries at the top of `output`, whatever the
|
|
2439
|
-
* API answered. `core.action.http`'s definition declares all three; the author's
|
|
2440
|
-
* `returns` describes what is INSIDE `body`, never beside it.
|
|
2441
|
-
*/
|
|
2442
|
-
const HTTP_ENVELOPE_FIELDS = ['body', 'statusCode', 'headers'];
|
|
2443
|
-
/**
|
|
2444
|
-
* A read of an `http()` step's result.
|
|
2445
|
-
*
|
|
2446
|
-
* The response is an envelope — `{ body, statusCode, headers }` — and `returns`
|
|
2447
|
-
* declares the parsed JSON body's top-level fields, so a declared field is read
|
|
2448
|
-
* at `output.body.<field>` (`out('<step>', 'body.<field>')`). That nesting is the
|
|
2449
|
-
* one thing this family does not share with the process families, and it is
|
|
2450
|
-
* exactly where flow-builder-sdk#850's run went wrong: a branch kept reading
|
|
2451
|
-
* `output.currentDayForecast…` (the Azure Maps connector's path) after the step
|
|
2452
|
-
* became an Open-Meteo `http()`, passed `check` and `validate`, and faulted at
|
|
2453
|
-
* `flow debug` as `[400302]`.
|
|
2454
|
-
*/
|
|
2455
|
-
function checkHttpRead(name, field, rest, spec, step, diags) {
|
|
2456
|
-
if (field === undefined)
|
|
2457
|
-
return; // `out('fetch')` — the whole response.
|
|
2458
|
-
if (field === 'error')
|
|
2459
|
-
return; // the failure envelope (managed http's error port).
|
|
2460
|
-
if (field !== 'output') {
|
|
2461
|
-
diags.push({
|
|
2462
|
-
level: 'error', code: 'HTTP_READ_NOT_OUTPUT', step,
|
|
2463
|
-
message: `HTTP step "${name}" publishes its response under "output", but "$vars.${name}.${field}" `
|
|
2464
|
-
+ `(read in "${step}") asks for "${field}", which is undefined at run time.`,
|
|
2465
|
-
suggestion: `out('${name}', 'body.<field>')`,
|
|
2466
|
-
});
|
|
2467
|
-
return;
|
|
2468
|
-
}
|
|
2469
|
-
const [top, inner] = (rest ?? '').replace(/^\./, '').split('.');
|
|
2470
|
-
if (!top)
|
|
2471
|
-
return; // `$vars.<name>.output` — the whole envelope.
|
|
2472
|
-
const declared = Object.keys(spec?.returns ?? {});
|
|
2473
|
-
if (!HTTP_ENVELOPE_FIELDS.includes(top)) {
|
|
2474
|
-
// The commonest form: a declared body field read one level too high.
|
|
2475
|
-
const hoisted = declared.includes(top) ? top : undefined;
|
|
2476
|
-
const nearest = hoisted ?? nearestName(top, declared);
|
|
2477
|
-
diags.push({
|
|
2478
|
-
level: 'error', code: 'HTTP_READ_UNKNOWN_FIELD', step,
|
|
2479
|
-
message: `"$vars.${name}.output.${top}" (read in "${step}") is not a field of HTTP step "${name}"'s `
|
|
2480
|
-
+ `response. An HTTP response publishes ${HTTP_ENVELOPE_FIELDS.map((f) => `"${f}"`).join(', ')}; `
|
|
2481
|
-
+ `the fields \`returns\` declares live under "body"`
|
|
2482
|
-
+ (hoisted ? `, so "${top}" is read as "$vars.${name}.output.body.${top}".` : '.')
|
|
2483
|
-
+ ` A read outside that shape resolves to undefined and faults a gateway at run time.`,
|
|
2484
|
-
...(nearest !== undefined ? { suggestion: `out('${name}', 'body.${nearest}')` } : {}),
|
|
2485
|
-
});
|
|
2486
|
-
return;
|
|
2487
|
-
}
|
|
2488
|
-
if (top !== 'body' || !inner)
|
|
2489
|
-
return; // `statusCode`, `headers`, or the whole body.
|
|
2490
|
-
if (declared.length === 0) {
|
|
2491
|
-
diags.push({
|
|
2492
|
-
level: 'error', code: 'HTTP_READ_WITHOUT_RETURNS', step,
|
|
2493
|
-
message: `"$vars.${name}.output.body.${inner}" (read in "${step}") reads a field of HTTP step "${name}"'s `
|
|
2494
|
-
+ `response body, but the step declares no \`returns\` — so the node carries no body schema and nothing `
|
|
2495
|
-
+ `can tell a real field from a typo. Only the API being called knows its body; declare the fields you read.`,
|
|
2496
|
-
suggestion: `returns: { ${inner}: 'string' }`,
|
|
2497
|
-
});
|
|
2498
|
-
return;
|
|
2499
|
-
}
|
|
2500
|
-
if (declared.includes(inner))
|
|
2501
|
-
return;
|
|
2502
|
-
const nearest = nearestName(inner, declared);
|
|
2503
|
-
diags.push({
|
|
2504
|
-
level: 'error', code: 'HTTP_READ_UNKNOWN_FIELD', step,
|
|
2505
|
-
message: `HTTP step "${name}" declares no body field "${inner}" (read as "$vars.${name}.output.body.${inner}" `
|
|
2506
|
-
+ `in "${step}"). Its \`returns\` names: ${declared.join(', ')}. A read outside that set resolves to undefined.`,
|
|
2507
|
-
...(nearest !== undefined ? { suggestion: `out('${name}', 'body.${nearest}')` } : {}),
|
|
2508
|
-
});
|
|
2509
|
-
}
|
|
2510
2416
|
/**
|
|
2511
2417
|
* The read rule the process-shaped families share: the result is published
|
|
2512
2418
|
* under `output`, keyed by a known field list — the author's `returns`, or a
|
|
@@ -2572,7 +2478,7 @@ function checkRpaWorkflowRead(name, field, rest, spec, step, diags) {
|
|
|
2572
2478
|
* build until it lands here.
|
|
2573
2479
|
*/
|
|
2574
2480
|
const OUTPUT_READ_RULES = {
|
|
2575
|
-
http:
|
|
2481
|
+
http: { exempt: 'Registered: the http node types read their own output (nodes/http/flow, a `reader` contract).' },
|
|
2576
2482
|
rpaWorkflow: checkRpaWorkflowRead,
|
|
2577
2483
|
apiWorkflow: checkApiWorkflowRead,
|
|
2578
2484
|
publishedFunction: checkPublishedFunctionRead,
|
|
@@ -3912,7 +3818,7 @@ function hitlEndHazards(steps) {
|
|
|
3912
3818
|
// is where this arm's token stops on that outcome.
|
|
3913
3819
|
const spec = st.spec;
|
|
3914
3820
|
const exits = spec.kind === 'hitl' && hitlDerivesOutcomeHandles(spec.inputs?.variant)
|
|
3915
|
-
?
|
|
3821
|
+
? stepExits(st.spec, st.options?.version) : undefined;
|
|
3916
3822
|
if (exits && hasExits(exits)) {
|
|
3917
3823
|
const stopped = exits.filter((e) => {
|
|
3918
3824
|
const c = st.cases.find((x) => x.value === e.value);
|
|
@@ -3966,170 +3872,6 @@ function endDiag(join, h) {
|
|
|
3966
3872
|
* reference to hide — which is what the checks below enforce for the cases
|
|
3967
3873
|
* where types have been stripped at run time.
|
|
3968
3874
|
*/
|
|
3969
|
-
/**
|
|
3970
|
-
* An ISO-8601 DURATION, which is what both http definitions declare `timeout` to
|
|
3971
|
-
* be (`PT15M` by default) and what all 808 deployed http nodes carry.
|
|
3972
|
-
*
|
|
3973
|
-
* Deliberately the whole grammar rather than the `PT<n>[HMS]` subset the corpus
|
|
3974
|
-
* happens to use: a too-narrow fence would refuse `P1D` or `PT1H30M`, which the
|
|
3975
|
-
* platform parses perfectly well. What it does refuse is the family of things
|
|
3976
|
-
* that are NOT durations at all and would reach the platform as an unparseable
|
|
3977
|
-
* string — `'30s'`, `'30'`, `'PT'`, `'15 minutes'`.
|
|
3978
|
-
*/
|
|
3979
|
-
const ISO8601_DURATION = /^P(?!$)(\d+(?:\.\d+)?Y)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?W)?(\d+(?:\.\d+)?D)?(T(?!$)(\d+(?:\.\d+)?H)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?S)?)?$/;
|
|
3980
|
-
/**
|
|
3981
|
-
* Where `check` stops treating a retry count as plainly reasonable. The
|
|
3982
|
-
* platform's own ceiling is NOT measured, and the deployed corpus's largest
|
|
3983
|
-
* author-set value is 3 — so above this we WARN. A too-narrow fence is fatal
|
|
3984
|
-
* (it refuses something that works); a too-wide one costs nothing.
|
|
3985
|
-
*/
|
|
3986
|
-
const RETRY_COUNT_SOFT_MAX = 5;
|
|
3987
|
-
/**
|
|
3988
|
-
* The two http settings every deployed request node carries.
|
|
3989
|
-
*
|
|
3990
|
-
* Both definitions declare the defaults (`timeout: 'PT15M'`, `retryCount: 0`), so
|
|
3991
|
-
* omitting them is safe and is what ~99% of deployed nodes do. What is NOT safe
|
|
3992
|
-
* is writing them in a form the platform cannot read: an unparseable duration is
|
|
3993
|
-
* not a shorter timeout, it is a value the request node fails to configure with,
|
|
3994
|
-
* and nothing downstream of `check` would say so in the author's own vocabulary.
|
|
3995
|
-
*/
|
|
3996
|
-
function checkHttp(step, inputs, diags) {
|
|
3997
|
-
// `core.action.http` is retired on the platform side: after `registry pull
|
|
3998
|
-
// --force` the tenant serves only `core.action.http.v2` (2.3), and a
|
|
3999
|
-
// `registry get core.action.http` — bare or at the `@1.3` that `flow migrate`
|
|
4000
|
-
// upgrades it to — answers "Node not found". It still compiles, validates and
|
|
4001
|
-
// runs, which is why this is a warning and not an error: existing flows are
|
|
4002
|
-
// not broken and `decompile` must keep round-tripping them.
|
|
4003
|
-
//
|
|
4004
|
-
// Moving to the managed node is not a rename. A plain step keeps a non-2xx on
|
|
4005
|
-
// the SUCCESS path (`out(step,'statusCode')`); the managed one fails on it, so
|
|
4006
|
-
// a status branch becomes an error handler reading the envelope's `status`.
|
|
4007
|
-
//
|
|
4008
|
-
// It is not a free upgrade either, and for a connection-less URL it is usually
|
|
4009
|
-
// the wrong move (flow-builder-sdk#898). With no `connection` the managed node
|
|
4010
|
-
// still goes through Integration Service: serialize emits the `uipath-uipath-http`
|
|
4011
|
-
// connector on `ImplicitConnection`, so the request leaves from IS's SHARED
|
|
4012
|
-
// egress. Measured on slack-weather-pipeline (3/3 runs): agents took the old
|
|
4013
|
-
// `managed: true` hint, and Open-Meteo answered that egress 429 "Daily API
|
|
4014
|
-
// request limit exceeded" while direct calls from the same box got 200. The
|
|
4015
|
-
// plain node goes direct. So the hint only steers toward `managed: true` when
|
|
4016
|
-
// the step was given connection settings; the two other real reasons to move
|
|
4017
|
-
// (a binding, an `.onError()`) already fire as errors with that suggestion
|
|
4018
|
-
// (HTTP_BINDING_REQUIRES_MANAGED, HTTP_ONERROR_V1).
|
|
4019
|
-
if (inputs && !inputs.managed) {
|
|
4020
|
-
const asked = inputs;
|
|
4021
|
-
const wantsConnection = asked.connection !== undefined || asked.folder !== undefined
|
|
4022
|
-
|| asked.targetConnector !== undefined;
|
|
4023
|
-
diags.push(wantsConnection
|
|
4024
|
-
? {
|
|
4025
|
-
level: 'warning',
|
|
4026
|
-
code: 'HTTP_V1_RETIRED',
|
|
4027
|
-
message: `Step "${step}" emits core.action.http, which the tenant registry no longer serves (only `
|
|
4028
|
-
+ `core.action.http.v2). It sets Integration Service connection settings, which only the managed node `
|
|
4029
|
-
+ `can use. Moving to managed: true changes behaviour rather than just the node type: a non-2xx stops `
|
|
4030
|
-
+ `arriving on the success path with statusCode and fails the step instead, so a status branch becomes `
|
|
4031
|
-
+ `an .onError() handler reading err('${step}', 'status').`,
|
|
4032
|
-
step,
|
|
4033
|
-
suggestion: `http({ url: …, managed: true, connection, folder })`,
|
|
4034
|
-
}
|
|
4035
|
-
: {
|
|
4036
|
-
level: 'warning',
|
|
4037
|
-
code: 'HTTP_V1_RETIRED',
|
|
4038
|
-
message: `Step "${step}" emits core.action.http, which the tenant registry no longer serves (only `
|
|
4039
|
-
+ `core.action.http.v2). It still compiles, validates and runs, and it calls the URL directly. For a URL `
|
|
4040
|
-
+ `that needs no Integration Service connection, keep managed: false. managed: true is not a drop-in `
|
|
4041
|
-
+ `replacement: even without a connection it sends the request through Integration Service's shared `
|
|
4042
|
-
+ `egress (rate limits there are shared, and public APIs can answer 429 where a direct call succeeds), `
|
|
4043
|
-
+ `and a non-2xx fails the step instead of arriving with statusCode. Switch only if the call needs an `
|
|
4044
|
-
+ `Integration Service connection or an .onError() handler.`,
|
|
4045
|
-
step,
|
|
4046
|
-
});
|
|
4047
|
-
}
|
|
4048
|
-
const connection = inputs?.connection;
|
|
4049
|
-
const folder = inputs?.folder;
|
|
4050
|
-
const targetConnector = inputs?.targetConnector;
|
|
4051
|
-
const bindingConfigured = connection !== undefined || folder !== undefined;
|
|
4052
|
-
const targetConfigured = targetConnector !== undefined;
|
|
4053
|
-
const nonEmpty = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
4054
|
-
if (!inputs?.managed && (bindingConfigured || targetConfigured)) {
|
|
4055
|
-
diags.push({
|
|
4056
|
-
level: 'error',
|
|
4057
|
-
code: 'HTTP_BINDING_REQUIRES_MANAGED',
|
|
4058
|
-
message: `Step "${step}" supplies Integration Service connection settings to the standalone HTTP node. `
|
|
4059
|
-
+ `Only the managed node can use connection, folder, or targetConnector.`,
|
|
4060
|
-
step,
|
|
4061
|
-
suggestion: `managed: true`,
|
|
4062
|
-
});
|
|
4063
|
-
}
|
|
4064
|
-
else if (inputs?.managed && bindingConfigured && (!nonEmpty(connection) || !nonEmpty(folder))) {
|
|
4065
|
-
diags.push({
|
|
4066
|
-
level: 'error',
|
|
4067
|
-
code: 'HTTP_MANAGED_BINDING_INCOMPLETE',
|
|
4068
|
-
message: `Managed HTTP step "${step}" must name both a non-empty \`connection\` and \`folder\`. The platform `
|
|
4069
|
-
+ `needs the pair to resolve the Integration Service connection at runtime.`,
|
|
4070
|
-
step,
|
|
4071
|
-
suggestion: `connection: 'http', folder: 'shared'`,
|
|
4072
|
-
});
|
|
4073
|
-
}
|
|
4074
|
-
if (inputs?.managed && targetConfigured && !nonEmpty(targetConnector)) {
|
|
4075
|
-
diags.push({
|
|
4076
|
-
level: 'error',
|
|
4077
|
-
code: 'HTTP_TARGET_CONNECTOR_EMPTY',
|
|
4078
|
-
message: `Managed HTTP step "${step}" sets an empty \`targetConnector\`; name its Integration Service connector key.`,
|
|
4079
|
-
step,
|
|
4080
|
-
suggestion: `targetConnector: 'uipath-salesforce-slack'`,
|
|
4081
|
-
});
|
|
4082
|
-
}
|
|
4083
|
-
else if (inputs?.managed && targetConfigured && !bindingConfigured) {
|
|
4084
|
-
diags.push({
|
|
4085
|
-
level: 'error',
|
|
4086
|
-
code: 'HTTP_TARGET_CONNECTOR_REQUIRES_BINDING',
|
|
4087
|
-
message: `Managed HTTP step "${step}" sets \`targetConnector\` without a connection and folder. `
|
|
4088
|
-
+ `A connector-specific fallback needs the binding pair whose authentication it reuses.`,
|
|
4089
|
-
step,
|
|
4090
|
-
suggestion: `connection: 'slack', folder: 'shared'`,
|
|
4091
|
-
});
|
|
4092
|
-
}
|
|
4093
|
-
const timeout = inputs?.timeout;
|
|
4094
|
-
if (timeout !== undefined) {
|
|
4095
|
-
if (typeof timeout !== 'string' || !ISO8601_DURATION.test(timeout.trim())) {
|
|
4096
|
-
diags.push({
|
|
4097
|
-
level: 'error',
|
|
4098
|
-
code: 'HTTP_TIMEOUT_NOT_DURATION',
|
|
4099
|
-
message: `Step "${step}" sets timeout to ${JSON.stringify(timeout)}, which is not an ISO-8601 duration. The `
|
|
4100
|
-
+ `platform reads this field as a duration string (both http definitions default it to "PT15M"), so a `
|
|
4101
|
-
+ `value it cannot parse configures no timeout at all.`,
|
|
4102
|
-
step,
|
|
4103
|
-
suggestion: `timeout: 'PT30S' // 30 seconds — also 'PT1M', 'PT15M', 'PT1H'`,
|
|
4104
|
-
});
|
|
4105
|
-
}
|
|
4106
|
-
}
|
|
4107
|
-
const retryCount = inputs?.retryCount;
|
|
4108
|
-
if (retryCount !== undefined) {
|
|
4109
|
-
if (typeof retryCount !== 'number' || !Number.isInteger(retryCount) || retryCount < 0) {
|
|
4110
|
-
diags.push({
|
|
4111
|
-
level: 'error',
|
|
4112
|
-
code: 'HTTP_RETRY_COUNT_INVALID',
|
|
4113
|
-
message: `Step "${step}" sets retryCount to ${JSON.stringify(retryCount)}. It is a count of RETRIES after the `
|
|
4114
|
-
+ `first attempt, so it must be a non-negative whole number — 0 (the definitions' default) means "try `
|
|
4115
|
-
+ `once", 2 means up to three attempts.`,
|
|
4116
|
-
step,
|
|
4117
|
-
suggestion: `retryCount: 2`,
|
|
4118
|
-
});
|
|
4119
|
-
}
|
|
4120
|
-
else if (retryCount > RETRY_COUNT_SOFT_MAX) {
|
|
4121
|
-
diags.push({
|
|
4122
|
-
level: 'warning',
|
|
4123
|
-
code: 'HTTP_RETRY_COUNT_HIGH',
|
|
4124
|
-
message: `Step "${step}" asks for ${retryCount} retries. The local runtime clamps at ${RETRY_COUNT_SOFT_MAX} and `
|
|
4125
|
-
+ `the largest value in the deployed corpus is 3, so a local replay will not exercise what you wrote. `
|
|
4126
|
-
+ `The platform's own ceiling is not measured — this is a warning, not a refusal.`,
|
|
4127
|
-
step,
|
|
4128
|
-
suggestion: `retryCount: ${RETRY_COUNT_SOFT_MAX}`,
|
|
4129
|
-
});
|
|
4130
|
-
}
|
|
4131
|
-
}
|
|
4132
|
-
}
|
|
4133
3875
|
/**
|
|
4134
3876
|
* Why a form-field type is refused: a product spelling of a type the SDK
|
|
4135
3877
|
* models (`"string"` is the platform's own name for text), or a type
|
|
@@ -4699,6 +4441,7 @@ function checkRegisteredStep(entry, s, exprs, errorSource, opts, diags, typeOf,
|
|
|
4699
4441
|
return name ? { name, type: typeOf.get(name) } : undefined;
|
|
4700
4442
|
},
|
|
4701
4443
|
options: opts,
|
|
4444
|
+
bindingLabels: (where, labels, use) => checkBindingLabels(where, labels, opts, (code, message) => diags.push({ level: 'error', code, step: s.name, message }), undefined, use),
|
|
4702
4445
|
};
|
|
4703
4446
|
impl.check?.(spec, ctx);
|
|
4704
4447
|
for (const site of impl.expressions(spec)) {
|
|
@@ -4726,11 +4469,15 @@ function checkRegisteredStep(entry, s, exprs, errorSource, opts, diags, typeOf,
|
|
|
4726
4469
|
// would get unpinned (select(), else the default) — as decompile lifts an
|
|
4727
4470
|
// unregistered version — so fixing the pin is not followed by a second round.
|
|
4728
4471
|
// It stays unplaced: the pin is wrong, so no family rule may rely on it.
|
|
4729
|
-
|
|
4730
|
-
|
|
4472
|
+
const fallback = resolveVersion(entry.type, spec, undefined, `step "${s.name}"`);
|
|
4473
|
+
const fallbackCtx = runRules(fallback);
|
|
4474
|
+
return {
|
|
4475
|
+
placed: false,
|
|
4476
|
+
checked: { entry, impl: fallback, spec, placement: { step: s.name, kind: fallback.kind, spec, version: fallback.version }, ctx: fallbackCtx },
|
|
4477
|
+
};
|
|
4731
4478
|
}
|
|
4732
4479
|
const ctx = runRules(impl);
|
|
4733
|
-
return { entry, impl, spec, placement: { step: s.name, kind: impl.kind, spec, version: impl.version }, ctx };
|
|
4480
|
+
return { placed: true, checked: { entry, impl, spec, placement: { step: s.name, kind: impl.kind, spec, version: impl.version }, ctx } };
|
|
4734
4481
|
}
|
|
4735
4482
|
/** Every non-literal Expr reachable in `value`, as expressions to check. */
|
|
4736
4483
|
function pushDeepExprs(value, step, errorSource, out) {
|
|
@@ -4795,16 +4542,6 @@ function collectActionExprs(s, out, errorSource) {
|
|
|
4795
4542
|
if (val && typeof val === 'object')
|
|
4796
4543
|
return Object.values(val).forEach(pushDeep);
|
|
4797
4544
|
};
|
|
4798
|
-
if (s.spec.kind === 'http') {
|
|
4799
|
-
const i = s.spec.inputs;
|
|
4800
|
-
push(i.url);
|
|
4801
|
-
push(i.body);
|
|
4802
|
-
for (const val of Object.values(i.headers ?? {}))
|
|
4803
|
-
push(val);
|
|
4804
|
-
for (const val of Object.values(i.query ?? {}))
|
|
4805
|
-
push(val);
|
|
4806
|
-
return;
|
|
4807
|
-
}
|
|
4808
4545
|
if (s.spec.kind === 'hitl') {
|
|
4809
4546
|
// A human task's only expressions are the values its shown fields display.
|
|
4810
4547
|
// Without this a typo'd reference inside a form field would reach the
|
|
@@ -4933,8 +4670,8 @@ placed) {
|
|
|
4933
4670
|
// A registered node type: a bundled one asks its definition at the version it
|
|
4934
4671
|
// will be emitted at; a per-tenant one answers for itself (its errorPort).
|
|
4935
4672
|
if (registeredKind(spec)) {
|
|
4936
|
-
//
|
|
4937
|
-
//
|
|
4673
|
+
// An unregistered pin still has a record, with the version it would get
|
|
4674
|
+
// unpinned; NODE_VERSION_UNREGISTERED reports the pin itself.
|
|
4938
4675
|
if (!placed)
|
|
4939
4676
|
return;
|
|
4940
4677
|
const { impl } = placed;
|
|
@@ -5002,36 +4739,11 @@ placed) {
|
|
|
5002
4739
|
});
|
|
5003
4740
|
return;
|
|
5004
4741
|
}
|
|
5005
|
-
// Two separate facts about a PLAIN http step, both measured, both fatal to a
|
|
5006
|
-
// handler there:
|
|
5007
|
-
//
|
|
5008
|
-
// 1. a 4xx/5xx does not FAIL the node — it completes carrying `statusCode`,
|
|
5009
|
-
// so the handler only ever runs on a transport failure (DNS, refused,
|
|
5010
|
-
// timeout);
|
|
5011
|
-
// 2. when it does run there is nothing to read. `core.action.http@1.0.0` and
|
|
5012
|
-
// the `@1.3` that `uip maestro flow migrate` upgrades it to both publish
|
|
5013
|
-
// `<step>.error = true` and leave `<step>.output` null — no envelope
|
|
5014
|
-
// anywhere, unlike every other family. `validate` still says Valid.
|
|
5015
|
-
//
|
|
5016
|
-
// So this stays an error, and says the second part too: an author who reads
|
|
5017
|
-
// the old message ("use the managed node if statuses must route") could
|
|
5018
|
-
// reasonably conclude a transport-failure handler is fine as it is.
|
|
5019
|
-
if (spec.kind === 'http' && !spec.inputs?.managed)
|
|
5020
|
-
diags.push(plainHttpOnErrorRefusal(step.name));
|
|
5021
4742
|
}
|
|
5022
|
-
/** `.onError` after a plain `core.action.http`
|
|
4743
|
+
/** `.onError` after a custom node built on the plain `core.action.http` definition (the http family's refusal). */
|
|
5023
4744
|
function plainHttpOnErrorRefusal(stepName) {
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
code: 'HTTP_ONERROR_V1',
|
|
5027
|
-
message: `Step "${stepName}" handles errors on a plain http() request. A 4xx/5xx does NOT fail that node — it `
|
|
5028
|
-
+ `completes with out('${stepName}', 'statusCode') set — so the handler runs only on a transport failure, `
|
|
5029
|
-
+ `and when it does the node publishes no failure envelope at all (measured: "<step>.error" is the boolean `
|
|
5030
|
-
+ `true and "<step>.output" is null, in 1.0.0 and in the 1.3 flow migrate produces), so err() has nothing `
|
|
5031
|
-
+ `to read. The managed node is the one whose failures route AND carry the envelope.`,
|
|
5032
|
-
step: stepName,
|
|
5033
|
-
suggestion: `http({ url: …, managed: true })`,
|
|
5034
|
-
};
|
|
4745
|
+
const { message, suggestion } = plainHttpOnErrorMessage(stepName);
|
|
4746
|
+
return { level: 'error', code: 'HTTP_ONERROR_V1', message, step: stepName, suggestion };
|
|
5035
4747
|
}
|
|
5036
4748
|
/**
|
|
5037
4749
|
* Reading a value that only ONE arm produces (flow-builder-sdk#200).
|
|
@@ -5356,7 +5068,7 @@ function loopExitInArm() {
|
|
|
5356
5068
|
* checks and what it does not. It checks the FIELD NAME against the envelope's
|
|
5357
5069
|
* own fields, which are the same on every family. It does NOT police the prefix:
|
|
5358
5070
|
* `.error` vs `.output` is a per-family runtime boundary, measured per family and
|
|
5359
|
-
* rewritten by `serialize` (`
|
|
5071
|
+
* rewritten by `serialize` (a node type's `errorEnvelope`), so an author writing `.error`
|
|
5360
5072
|
* is right by construction and an author hand-writing `.output` is caught by
|
|
5361
5073
|
* ERROR_ENVELOPE_VIA_OUTPUT instead.
|
|
5362
5074
|
*
|