@uipath/maestro-builder-sdk 6.17.0 → 6.17.2
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 +2 -2
- package/dist/api-index.md +12 -12
- package/dist/api-members.md +27 -27
- package/dist/check-cli.js +2 -30
- package/dist/check.js +139 -58
- package/dist/cli-library.d.ts +18 -4
- package/dist/cli-library.js +24 -6
- package/dist/compile-cli.js +4 -33
- package/dist/core/actions.d.ts +1 -1
- package/dist/core/cli-library.d.ts +16 -7
- package/dist/core/cli-library.js +53 -19
- package/dist/core/connector-checks.d.ts +18 -2
- package/dist/core/connector-checks.js +15 -5
- package/dist/core/error-port.d.ts +25 -0
- package/dist/core/error-port.js +36 -0
- package/dist/core/expr.d.ts +11 -5
- package/dist/core/expr.js +9 -3
- package/dist/core/library.d.ts +19 -0
- package/dist/core/library.js +22 -6
- package/dist/flow-sdk.d.ts +8 -3
- package/dist/flow-sdk.js +9 -4
- package/dist/serialize.js +72 -58
- package/package.json +2 -2
package/dist/check.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { Expr, SCHEDULE_PRESETS, DELAY_PRESETS, VARIANT_OPERATION, parseIxpProjectType, flatNameOf, flatNameSegmentsOf, FLAT_SEP } from './flow-sdk.js';
|
|
9
9
|
import { FlowNode } from './core/node-classes.js';
|
|
10
10
|
import { planContributionBindings } from './core/contribution-bindings.js';
|
|
11
|
+
import { definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
|
|
11
12
|
import { resolveContributionDefinition } from './serialize.js';
|
|
12
13
|
import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
|
|
13
14
|
import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
|
|
@@ -257,7 +258,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
257
258
|
stepNames.add(s.name);
|
|
258
259
|
declared.add(s.name); // a step's output is referenceable by name
|
|
259
260
|
}
|
|
260
|
-
if (s.kind === 'stepToList' && s.port === 'error')
|
|
261
|
+
if ((s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port === 'error')
|
|
261
262
|
checkOnError(s, prevPortSource, diags);
|
|
262
263
|
if ((s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port.startsWith('branch-')) {
|
|
263
264
|
const want = s.port.slice('branch-'.length);
|
|
@@ -776,10 +777,10 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
776
777
|
}
|
|
777
778
|
// An exit with no arm compiles to an End node, so the run FINISHES
|
|
778
779
|
// there instead of reaching a port with no edge and stalling. Better
|
|
779
|
-
// than stalling, still probably not what the author meant
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
// already ends the run and needs no arm.
|
|
780
|
+
// than stalling, still probably not what the author meant: the End
|
|
781
|
+
// maps each output from its variable, so the run answers whatever
|
|
782
|
+
// those hold at that point, usually nothing (flow-builder-sdk#742).
|
|
783
|
+
// An `action: 'End'` outcome already ends the run and needs no arm.
|
|
783
784
|
const unrouted = exits.filter((e) => !seenExit.has(e.port) && e.endsProcess !== true);
|
|
784
785
|
if (unrouted.length > 0) {
|
|
785
786
|
const outs = built.outputs.map((o) => o.name);
|
|
@@ -789,8 +790,9 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
789
790
|
+ `${unrouted.map((e) => JSON.stringify(e.value)).join(', ')}. Each compiles to an End node, `
|
|
790
791
|
+ `so the run finishes there`
|
|
791
792
|
+ (outs.length > 0
|
|
792
|
-
?
|
|
793
|
-
+ `(${outs.join(', ')})
|
|
793
|
+
? `, and nothing on that path sets the flow's declared output${outs.length === 1 ? '' : 's'} `
|
|
794
|
+
+ `(${outs.join(', ')}): the End answers whatever ${outs.length === 1 ? 'its variable holds' : 'their variables hold'}, `
|
|
795
|
+
+ `usually nothing.`
|
|
794
796
|
: '.'),
|
|
795
797
|
suggestion: `{ value: '${unrouted[0].value}', body: (b) => … }`,
|
|
796
798
|
});
|
|
@@ -1294,9 +1296,7 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
1294
1296
|
}
|
|
1295
1297
|
}
|
|
1296
1298
|
// The End compile appends when the root path runs out of steps (#941).
|
|
1297
|
-
|
|
1298
|
-
if (fallOff)
|
|
1299
|
-
diags.push(fallOff);
|
|
1299
|
+
diags.push(...checkFallOffEnd(built));
|
|
1300
1300
|
// Check every child flow with the same rules, in its OWN scope — a child's
|
|
1301
1301
|
// steps never see the parent's variables, so its undeclared references,
|
|
1302
1302
|
// duplicate step names and type mismatches are its own. Diagnostics come back
|
|
@@ -1384,32 +1384,29 @@ function checkLoopListEnvelope(loop, connectors, opts, diags) {
|
|
|
1384
1384
|
});
|
|
1385
1385
|
}
|
|
1386
1386
|
/**
|
|
1387
|
-
* NO_RETURN: a path that runs off the end of the flow,
|
|
1388
|
-
* is never written
|
|
1387
|
+
* NO_RETURN: a path that runs off the end of the flow, or off the end of a
|
|
1388
|
+
* `.stepToList` / `.onError` body, where a declared output is never written
|
|
1389
|
+
* (#941, #742).
|
|
1389
1390
|
*
|
|
1390
1391
|
* It asks serialize's question, not "is there a `.return()` anywhere?".
|
|
1391
1392
|
* serialize appends an End exactly when the root walk has tails left
|
|
1392
1393
|
* (`if (finalTails.length)`), and `armTails` mirrors that walk tail for tail.
|
|
1393
1394
|
* So a `.terminate()` path appends no End, and a branch where one arm returns
|
|
1394
|
-
* and the other falls off DOES append one.
|
|
1395
|
+
* and the other falls off DOES append one. A `.stepToList` body is a side exit:
|
|
1396
|
+
* it never rejoins the main path, and when it runs out of steps `emitPortList`
|
|
1397
|
+
* gives it an End of its own. `writtenTails` collects those bodies' tails.
|
|
1395
1398
|
*
|
|
1396
|
-
*
|
|
1399
|
+
* Every such End maps every output from its own variable
|
|
1397
1400
|
* (`result <- $vars.result`), the same as a `.return()` that names no value.
|
|
1398
1401
|
* That loses nothing when the flow declares no outputs, or when each output
|
|
1399
1402
|
* was set on the way: by a `default`, a `{ shared }` input that every root
|
|
1400
|
-
* feeds, or `{ updates }` on a step of that path.
|
|
1401
|
-
* that are not, and the steps the path
|
|
1402
|
-
*
|
|
1403
|
-
* Not covered: the private End a `.stepToList` body gets (#742). `armTails`
|
|
1404
|
-
* skips those bodies, and that End is a different emission site with no
|
|
1405
|
-
* output mappings at all.
|
|
1403
|
+
* feeds, or `{ updates }` on a step of that path. One warning per End: it
|
|
1404
|
+
* names the outputs that are not set, and the steps the path runs out from.
|
|
1406
1405
|
*/
|
|
1407
1406
|
function checkFallOffEnd(built) {
|
|
1408
1407
|
if (built.outputs.length === 0)
|
|
1409
|
-
return
|
|
1408
|
+
return [];
|
|
1410
1409
|
const from = built.triggerId ?? 'start';
|
|
1411
|
-
if (armTails(built.steps, [{ from }]).length === 0)
|
|
1412
|
-
return undefined;
|
|
1413
1410
|
const preset = new Set(built.outputs.filter((o) => o.default !== undefined).map((o) => o.name));
|
|
1414
1411
|
// A shared input sets its variable only on runs that enter through a root
|
|
1415
1412
|
// feeding it, so it counts as preset only when every root does.
|
|
@@ -1418,21 +1415,42 @@ function checkFallOffEnd(built) {
|
|
|
1418
1415
|
if (t.declaredAs === 'output' && roots.every((r) => t.feeds.some((f) => f.root === r.id)))
|
|
1419
1416
|
preset.add(t.name);
|
|
1420
1417
|
}
|
|
1421
|
-
const
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
code: 'NO_RETURN',
|
|
1430
|
-
message: `Path ending at ${steps.map((x) => `"${x}"`).join(', ')} falls into the End compile appends. `
|
|
1431
|
-
+ `${one ? 'Output' : 'Outputs'} ${missing.map((n) => `"${n}"`).join(', ')} may be unset there. `
|
|
1432
|
-
+ `End the path with .return({ … }) or set ${one ? 'it' : 'them'} with { updates } (a loop body may run zero times).`,
|
|
1433
|
-
step: steps[0],
|
|
1434
|
-
suggestion: `.return({ ${missing.map((n) => `${n}: …`).join(', ')} })`,
|
|
1418
|
+
const sideEnds = [];
|
|
1419
|
+
const rootTails = writtenTails(built.steps, [{ from, written: preset }], { breaks: [], sideEnds });
|
|
1420
|
+
const diags = [];
|
|
1421
|
+
const unset = (tails) => {
|
|
1422
|
+
const missing = built.outputs.map((o) => o.name).filter((n) => tails.some((t) => !t.written.has(n)));
|
|
1423
|
+
if (missing.length === 0)
|
|
1424
|
+
return undefined;
|
|
1425
|
+
return { missing, steps: [...new Set(tails.filter((t) => missing.some((n) => !t.written.has(n))).map((t) => t.from))] };
|
|
1435
1426
|
};
|
|
1427
|
+
// `lead(at)` is the sentence saying where the path ends and which End it falls into.
|
|
1428
|
+
const warn = (lead, u, fix) => {
|
|
1429
|
+
const one = u.missing.length === 1;
|
|
1430
|
+
return {
|
|
1431
|
+
level: 'warning',
|
|
1432
|
+
code: 'NO_RETURN',
|
|
1433
|
+
message: `${lead(u.steps.map((x) => `"${x}"`).join(', '))} ${one ? 'Output' : 'Outputs'} `
|
|
1434
|
+
+ `${u.missing.map((n) => `"${n}"`).join(', ')} may be unset there. ${fix(one ? 'it' : 'them')}`,
|
|
1435
|
+
step: u.steps[0],
|
|
1436
|
+
suggestion: `.return({ ${u.missing.map((n) => `${n}: …`).join(', ')} })`,
|
|
1437
|
+
};
|
|
1438
|
+
};
|
|
1439
|
+
// `armTails` and `writtenTails` hand on the same tails; the root End exists
|
|
1440
|
+
// exactly when there are any.
|
|
1441
|
+
const root = rootTails.length ? unset(rootTails) : undefined;
|
|
1442
|
+
if (root) {
|
|
1443
|
+
diags.push(warn((at) => `Path ending at ${at} falls into the End compile appends.`, root, (it) => `End the path with .return({ … }) or set ${it} with { updates } (a loop body may run zero times).`));
|
|
1444
|
+
}
|
|
1445
|
+
for (const side of sideEnds) {
|
|
1446
|
+
const u = unset(side.tails);
|
|
1447
|
+
if (!u)
|
|
1448
|
+
continue;
|
|
1449
|
+
const call = side.port === 'error' ? '.onError(…)' : `.stepToList('${side.port}', …)`;
|
|
1450
|
+
diags.push(warn((at) => `The ${call} path from "${side.source}" ends at ${at} and does not rejoin the main path, `
|
|
1451
|
+
+ `so compile ends the run there on an End of its own.`, u, (it) => `End the path with .return({ … }), set ${it} with { updates }, or hand it back with .stepToRef('<step>').`));
|
|
1452
|
+
}
|
|
1453
|
+
return diags;
|
|
1436
1454
|
}
|
|
1437
1455
|
/**
|
|
1438
1456
|
* `armTails`, step for step, carrying what each path has surely written. A
|
|
@@ -1446,12 +1464,31 @@ function checkFallOffEnd(built) {
|
|
|
1446
1464
|
* runs at least once, and is left by its end or by a `.break()`, so what all of
|
|
1447
1465
|
* those wrote counts. Every `.parallel` arm runs, so what each surely writes
|
|
1448
1466
|
* counts.
|
|
1467
|
+
*
|
|
1468
|
+
* A `.stepToList` body leaves the main path as it was (as in `armTails`), but
|
|
1469
|
+
* it is walked too: it starts with what was written before its port fired, and
|
|
1470
|
+
* if it hands back tails, serialize ends it on an End of its own, so those
|
|
1471
|
+
* tails go to `sideEnds`. On the `error` port the step failed, so its own
|
|
1472
|
+
* `{ updates }` are not counted (they usually read the output it never made).
|
|
1473
|
+
* Bodies nested anywhere, a plain loop's included, are walked with the same
|
|
1474
|
+
* `sideEnds`.
|
|
1449
1475
|
*/
|
|
1450
|
-
function writtenTails(steps, incoming,
|
|
1476
|
+
function writtenTails(steps, incoming, sinks) {
|
|
1477
|
+
const { breaks, sideEnds } = sinks;
|
|
1451
1478
|
let tails = incoming;
|
|
1479
|
+
// What was written before the last step on this path, i.e. without its own `{ updates }`.
|
|
1480
|
+
let beforeLast = surelyWritten(tails);
|
|
1452
1481
|
for (const st of steps) {
|
|
1453
|
-
if (st.kind === 'stepToList')
|
|
1482
|
+
if (st.kind === 'stepToList') {
|
|
1483
|
+
const source = tails[0]?.from;
|
|
1484
|
+
if (source === undefined)
|
|
1485
|
+
continue;
|
|
1486
|
+
const entry = st.port === 'error' ? beforeLast : surelyWritten(tails);
|
|
1487
|
+
const ends = writtenTails(st.body, [{ from: source, written: entry }], sinks);
|
|
1488
|
+
if (ends.length)
|
|
1489
|
+
sideEnds.push({ port: st.port, source, tails: ends });
|
|
1454
1490
|
continue;
|
|
1491
|
+
}
|
|
1455
1492
|
if (st.kind === 'stepToRef') {
|
|
1456
1493
|
if (st.port === 'output')
|
|
1457
1494
|
tails = [];
|
|
@@ -1464,8 +1501,9 @@ function writtenTails(steps, incoming, breaks) {
|
|
|
1464
1501
|
if (st.kind === 'return' || st.kind === 'terminate')
|
|
1465
1502
|
return [];
|
|
1466
1503
|
const before = surelyWritten(tails);
|
|
1504
|
+
beforeLast = before;
|
|
1467
1505
|
const written = new Set([...before, ...Object.keys(st.options?.updates ?? {})]);
|
|
1468
|
-
const arm = (body) => writtenTails(body, [{ from: st.name, written }],
|
|
1506
|
+
const arm = (body) => writtenTails(body, [{ from: st.name, written }], sinks);
|
|
1469
1507
|
if (st.kind === 'action') {
|
|
1470
1508
|
tails = actionTails(st.name, st.spec, st.options?.version)
|
|
1471
1509
|
.map((t) => ({ from: t.from, written }));
|
|
@@ -1481,7 +1519,7 @@ function writtenTails(steps, incoming, breaks) {
|
|
|
1481
1519
|
}
|
|
1482
1520
|
else if (st.kind === 'doWhile') {
|
|
1483
1521
|
const exits = [];
|
|
1484
|
-
exits.push(...writtenTails(st.body, [{ from: st.name, written }], exits));
|
|
1522
|
+
exits.push(...writtenTails(st.body, [{ from: st.name, written }], { breaks: exits, sideEnds }));
|
|
1485
1523
|
surelyWritten(exits).forEach((n) => written.add(n));
|
|
1486
1524
|
tails = [{ from: st.name, written }];
|
|
1487
1525
|
}
|
|
@@ -1491,6 +1529,9 @@ function writtenTails(steps, incoming, breaks) {
|
|
|
1491
1529
|
tails = [{ from: st.name, written }];
|
|
1492
1530
|
}
|
|
1493
1531
|
else {
|
|
1532
|
+
// A plain loop's body writes nothing for sure, but its side exits still end runs.
|
|
1533
|
+
if (st.kind === 'loop')
|
|
1534
|
+
writtenTails(st.body, [{ from: st.name, written }], { breaks: [], sideEnds });
|
|
1494
1535
|
tails = [{ from: st.name, written }];
|
|
1495
1536
|
}
|
|
1496
1537
|
}
|
|
@@ -5341,7 +5382,7 @@ function checkOnError(_handler, step, diags) {
|
|
|
5341
5382
|
// A `.loop()` CONTAINER is fine: `core.logic.loop` declares the error handle at
|
|
5342
5383
|
// both bundled versions, and a measured run routed a body failure to it — the
|
|
5343
5384
|
// container's envelope carried the body's message in `detail` and the failing
|
|
5344
|
-
// body step's
|
|
5385
|
+
// body step's label in `element`.
|
|
5345
5386
|
if (step.kind === 'loop')
|
|
5346
5387
|
return;
|
|
5347
5388
|
// `.doWhile()` is not. `core.logic.dowhile@1.0` declares an error VARIABLE and
|
|
@@ -5350,12 +5391,38 @@ function checkOnError(_handler, step, diags) {
|
|
|
5350
5391
|
// declare that handle". The builder refuses this too; this catches a
|
|
5351
5392
|
// hand-built flow and says the same thing.
|
|
5352
5393
|
if (step.kind === 'doWhile') {
|
|
5394
|
+
diags.push(doWhileOnErrorRefusal(step.name));
|
|
5395
|
+
return;
|
|
5396
|
+
}
|
|
5397
|
+
// A custom FlowAction or rawNode has no `kind`: ask its DEFINITION, with the
|
|
5398
|
+
// product's own rule (definitionHasErrorPort), not the kind list (#943).
|
|
5399
|
+
const maybeCustom = step.spec;
|
|
5400
|
+
if (!('kind' in maybeCustom) && maybeCustom instanceof FlowNode) {
|
|
5401
|
+
let def;
|
|
5402
|
+
try {
|
|
5403
|
+
const requestedVersion = step.options?.version;
|
|
5404
|
+
def = resolveContributionDefinition(maybeCustom.contribute(requestedVersion !== undefined ? { requestedVersion } : {}).definition, requestedVersion, `step "${step.name}"`);
|
|
5405
|
+
}
|
|
5406
|
+
catch {
|
|
5407
|
+
// checkContribution already reports a contribution compile cannot lower.
|
|
5408
|
+
return;
|
|
5409
|
+
}
|
|
5410
|
+
// A custom node backed by a typed family's bundled definition gets that
|
|
5411
|
+
// family's refusals: the rules are about the node type, not the SDK kind.
|
|
5412
|
+
if (def?.nodeType === 'core.logic.dowhile') {
|
|
5413
|
+
diags.push(doWhileOnErrorRefusal(step.name));
|
|
5414
|
+
return;
|
|
5415
|
+
}
|
|
5416
|
+
if (def?.nodeType === 'core.action.http') {
|
|
5417
|
+
diags.push(plainHttpOnErrorRefusal(step.name));
|
|
5418
|
+
return;
|
|
5419
|
+
}
|
|
5420
|
+
if (definitionHasErrorPort(def))
|
|
5421
|
+
return;
|
|
5353
5422
|
diags.push({
|
|
5354
5423
|
level: 'error',
|
|
5355
5424
|
code: 'ONERROR_NO_ERROR_PORT',
|
|
5356
|
-
message:
|
|
5357
|
-
+ `(core.logic.dowhile@1.0: input, success, start, continue, break), so the edge would leave a handle the `
|
|
5358
|
-
+ `node does not have. Handle the failure inside the body, on the step that can fail.`,
|
|
5425
|
+
message: errorPortRefusal(step.name, def),
|
|
5359
5426
|
step: step.name,
|
|
5360
5427
|
});
|
|
5361
5428
|
return;
|
|
@@ -5386,19 +5453,33 @@ function checkOnError(_handler, step, diags) {
|
|
|
5386
5453
|
// So this stays an error, and says the second part too: an author who reads
|
|
5387
5454
|
// the old message ("use the managed node if statuses must route") could
|
|
5388
5455
|
// reasonably conclude a transport-failure handler is fine as it is.
|
|
5389
|
-
if (spec.kind === 'http' && !spec.inputs?.managed)
|
|
5390
|
-
diags.push(
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
}
|
|
5456
|
+
if (spec.kind === 'http' && !spec.inputs?.managed)
|
|
5457
|
+
diags.push(plainHttpOnErrorRefusal(step.name));
|
|
5458
|
+
}
|
|
5459
|
+
/** `.onError` after a `core.logic.dowhile` container: it declares no error handle. */
|
|
5460
|
+
function doWhileOnErrorRefusal(stepName) {
|
|
5461
|
+
return {
|
|
5462
|
+
level: 'error',
|
|
5463
|
+
code: 'ONERROR_NO_ERROR_PORT',
|
|
5464
|
+
message: `Step "${stepName}" is a .doWhile() container. It declares an error variable but no error HANDLE `
|
|
5465
|
+
+ `(core.logic.dowhile@1.0: input, success, start, continue, break), so the edge would leave a handle the `
|
|
5466
|
+
+ `node does not have. Handle the failure inside the body, on the step that can fail.`,
|
|
5467
|
+
step: stepName,
|
|
5468
|
+
};
|
|
5469
|
+
}
|
|
5470
|
+
/** `.onError` after a plain `core.action.http` node (see the two measured facts in {@link checkOnError}). */
|
|
5471
|
+
function plainHttpOnErrorRefusal(stepName) {
|
|
5472
|
+
return {
|
|
5473
|
+
level: 'error',
|
|
5474
|
+
code: 'HTTP_ONERROR_V1',
|
|
5475
|
+
message: `Step "${stepName}" handles errors on a plain http() request. A 4xx/5xx does NOT fail that node — it `
|
|
5476
|
+
+ `completes with out('${stepName}', 'statusCode') set — so the handler runs only on a transport failure, `
|
|
5477
|
+
+ `and when it does the node publishes no failure envelope at all (measured: "<step>.error" is the boolean `
|
|
5478
|
+
+ `true and "<step>.output" is null, in 1.0.0 and in the 1.3 flow migrate produces), so err() has nothing `
|
|
5479
|
+
+ `to read. The managed node is the one whose failures route AND carry the envelope.`,
|
|
5480
|
+
step: stepName,
|
|
5481
|
+
suggestion: `http({ url: …, managed: true })`,
|
|
5482
|
+
};
|
|
5402
5483
|
}
|
|
5403
5484
|
/**
|
|
5404
5485
|
* Reading a value that only ONE arm produces (flow-builder-sdk#200).
|
package/dist/cli-library.d.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* cli-library —
|
|
2
|
+
* cli-library — Flow's check options, plus the historical re-exports.
|
|
3
3
|
*
|
|
4
4
|
* How a CLI opens the connector library and its `connectors-local/` overlay, and
|
|
5
|
-
* the event verdicts `check` shares with
|
|
6
|
-
* `core/cli-library.ts
|
|
7
|
-
*
|
|
5
|
+
* the connector and event verdicts every family's `check` shares with its
|
|
6
|
+
* `compile`, live in `core/cli-library.ts`. This module keeps the historical
|
|
7
|
+
* `./cli-library.js` import path and adds the one thing that is Flow's own:
|
|
8
|
+
* {@link flowCheckOpts}, the options both `flow check` and `flow compile`'s check
|
|
9
|
+
* pass run on.
|
|
8
10
|
*/
|
|
11
|
+
import type { CheckOpts } from './check.js';
|
|
12
|
+
import type { BindingsView } from './core/binding-messages.js';
|
|
13
|
+
import type { Library } from './core/library.js';
|
|
9
14
|
export { openCliLibrary, connectorCheckOpts, eventResolveErrorFor, byoaCheckOpts } from './core/cli-library.js';
|
|
15
|
+
/**
|
|
16
|
+
* Flow's check options from a library the CLI already opened: the shared
|
|
17
|
+
* connector and event hooks, Flow's own field validator (it renders Flow
|
|
18
|
+
* expressions, as `serialize` does), and the measured list envelopes.
|
|
19
|
+
*
|
|
20
|
+
* One builder for `flow check` and `flow compile`'s check pass, so the two
|
|
21
|
+
* cannot disagree.
|
|
22
|
+
*/
|
|
23
|
+
export declare function flowCheckOpts(library: Library | undefined, bindings?: BindingsView): CheckOpts;
|
package/dist/cli-library.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
+
import { connectorCheckOpts } from './core/cli-library.js';
|
|
2
|
+
import { connectorInputErrors } from './serialize.js';
|
|
3
|
+
export { openCliLibrary, connectorCheckOpts, eventResolveErrorFor, byoaCheckOpts } from './core/cli-library.js';
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
5
|
+
* Flow's check options from a library the CLI already opened: the shared
|
|
6
|
+
* connector and event hooks, Flow's own field validator (it renders Flow
|
|
7
|
+
* expressions, as `serialize` does), and the measured list envelopes.
|
|
3
8
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `core/cli-library.ts` when BPMN's `compile` and `check` needed the same things.
|
|
7
|
-
* This shim preserves the historical `./cli-library.js` import path.
|
|
9
|
+
* One builder for `flow check` and `flow compile`'s check pass, so the two
|
|
10
|
+
* cannot disagree.
|
|
8
11
|
*/
|
|
9
|
-
export
|
|
12
|
+
export function flowCheckOpts(library, bindings) {
|
|
13
|
+
const shared = connectorCheckOpts(library, bindings, connectorInputErrors);
|
|
14
|
+
if (!library)
|
|
15
|
+
return shared;
|
|
16
|
+
return {
|
|
17
|
+
...shared,
|
|
18
|
+
listEnvelope: (connector, operation, version, object) => {
|
|
19
|
+
try {
|
|
20
|
+
return library.resolve(connector, operation, version, object).listEnvelope;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined; // an unresolvable operation is connectorResolveError's to report
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
package/dist/compile-cli.js
CHANGED
|
@@ -22,9 +22,9 @@ import { writeJsonFile } from './core/json-file.js';
|
|
|
22
22
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
23
23
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
24
24
|
import { resolveFlowFile, loadBuiltFlow } from './load.js';
|
|
25
|
-
import { serializeWithSidecars,
|
|
25
|
+
import { serializeWithSidecars, compileOwnsResource } from './serialize.js';
|
|
26
26
|
import { check } from './check.js';
|
|
27
|
-
import {
|
|
27
|
+
import { flowCheckOpts, openCliLibrary } from './cli-library.js';
|
|
28
28
|
import { Bindings } from './bindings.js';
|
|
29
29
|
import { flagValue, runWhenInvokedDirectly } from './cli-run.js';
|
|
30
30
|
import { connectionResources, inlineAgentResources, mergeBindingsV2, mergeInlineAgentResources, readBindingsV2, readInlineAgentFiles, } from './bindings-v2.js';
|
|
@@ -70,37 +70,8 @@ export async function run(argv) {
|
|
|
70
70
|
// One list per run; every exit path reports it (reportResult overwrites keys).
|
|
71
71
|
const diagnostics = [];
|
|
72
72
|
if (!noCheck) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// The refusal serialize() would throw for an event it cannot resolve,
|
|
76
|
-
// printed as `✗ [EVENT_UNKNOWN]` before stopping instead of hiding
|
|
77
|
-
// behind "1 error(s); not emitting".
|
|
78
|
-
eventResolveError: eventResolveErrorFor(library),
|
|
79
|
-
...byoaCheckOpts(library),
|
|
80
|
-
eventParams: (connector, event) => {
|
|
81
|
-
try {
|
|
82
|
-
return library.eventParameterFields(connector, event);
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
return undefined; // unresolvable: serialize() reports it properly
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
lookups: (connector, action, version, object) => library.lookupSpecs(connector, action, version, object),
|
|
89
|
-
resolutions: library.lookupResolutions,
|
|
90
|
-
// Same validator serialize() runs below, so the check pass names the
|
|
91
|
-
// refusal (and stops) instead of printing `0 error(s)` first.
|
|
92
|
-
connectorInputs: (spec, inputs) => {
|
|
93
|
-
try {
|
|
94
|
-
const rc = library.resolve(spec.key, spec.action, spec.version, spec.object);
|
|
95
|
-
return connectorInputErrors(rc, inputs, library.lookupResolutions ?? {}, library.lookupSpecs(spec.key, spec.action, spec.version, spec.object) ?? {});
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
bindings,
|
|
102
|
-
}
|
|
103
|
-
: { bindings });
|
|
73
|
+
// Same options as `flow check`, so the two cannot disagree.
|
|
74
|
+
const diags = check(built, flowCheckOpts(library, bindings));
|
|
104
75
|
const errors = diags.filter((d) => d.level === 'error');
|
|
105
76
|
for (const d of diags)
|
|
106
77
|
console.error(` ${d.level === 'error' ? '✗' : '⚠'} [${d.code}] ${d.message}${d.suggestion ? ` (did you mean "${d.suggestion}"?)` : ''}`);
|
package/dist/core/actions.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ interface HttpInputsBase {
|
|
|
58
58
|
* an error port only makes sense here (`check` says so: HTTP_ONERROR_V1).
|
|
59
59
|
* The handler can answer the flow (`.return`), stop the run
|
|
60
60
|
* (`.terminate`), or compensate and continue at a step the success path
|
|
61
|
-
* also reaches (`h.
|
|
61
|
+
* also reaches (`h.stepToRef('<step>')` — see `references/error-handling.md`);
|
|
62
62
|
* - both nodes expose JSON responses as parsed values in the live product;
|
|
63
63
|
* use `returns` to declare fields before reading them. The node choice does
|
|
64
64
|
* not decide whether application/json is parsed.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Library } from './library.js';
|
|
1
|
+
import { Library, type ResolvedConnector } from './library.js';
|
|
2
2
|
import type { BindingsView } from './binding-messages.js';
|
|
3
3
|
import { type ConnectorCheckOpts } from './connector-checks.js';
|
|
4
|
+
import type { LookupResolutions, LookupSpec } from './lookups.js';
|
|
4
5
|
import type { EventCheckOpts } from './event-checks.js';
|
|
5
6
|
/**
|
|
6
7
|
* The library for this invocation, or `undefined` when there is none.
|
|
@@ -15,17 +16,25 @@ export declare function openCliLibrary(argv: readonly string[]): Library | undef
|
|
|
15
16
|
* The connector half of a family's check options, from a library the CLI already
|
|
16
17
|
* opened.
|
|
17
18
|
*
|
|
18
|
-
* Every callback fails OPEN:
|
|
19
|
-
*
|
|
20
|
-
* `check` stay the fast inner-loop rung — a process with no
|
|
21
|
-
* library
|
|
22
|
-
*
|
|
19
|
+
* Every fact callback fails OPEN: no library at all, or an entry the library
|
|
20
|
+
* cannot resolve, means "unknown", and an unknown fact fires no rule. That is
|
|
21
|
+
* what lets `check` stay the fast inner-loop rung — a process with no
|
|
22
|
+
* connectors needs no library. The one exception is the resolution itself:
|
|
23
|
+
* `connectorResolveError` reports the refusal `compile` would throw for an
|
|
24
|
+
* action, the way `eventResolveError` does for an event, so `check` never
|
|
25
|
+
* passes a connector step `compile` refuses (#948).
|
|
26
|
+
*
|
|
27
|
+
* `inputErrors` is the family's field-contract validator. The default speaks
|
|
28
|
+
* the `=`-expression dialect BPMN uses; Flow passes its own, which renders its
|
|
29
|
+
* expressions (see `flowCheckOpts` in `../cli-library.ts`).
|
|
23
30
|
*
|
|
24
31
|
* The `resolutions` are the overlay's: without them every lookup already
|
|
25
32
|
* recorded by `registry prepare --resolve` would be reported as still owed,
|
|
26
33
|
* telling the author to re-run a prepare they already ran.
|
|
27
34
|
*/
|
|
28
|
-
export declare function connectorCheckOpts(library: Library | undefined, bindings?: BindingsView): ConnectorCheckOpts & EventCheckOpts;
|
|
35
|
+
export declare function connectorCheckOpts(library: Library | undefined, bindings?: BindingsView, inputErrors?: ConnectorInputValidator): ConnectorCheckOpts & EventCheckOpts;
|
|
36
|
+
/** A family's field-contract validator: `compile`'s refusal for these inputs, or `undefined`. */
|
|
37
|
+
export type ConnectorInputValidator = (rc: ResolvedConnector, inputs: Record<string, unknown>, resolutions: LookupResolutions, lookups: Readonly<Record<string, LookupSpec>>) => string | undefined;
|
|
29
38
|
/**
|
|
30
39
|
* `check`'s EVENT_UNKNOWN verdict: the message `library.resolveEvent` throws
|
|
31
40
|
* for a subscription, or `undefined` when it resolves.
|
package/dist/core/cli-library.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { existsSync } from 'node:fs';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
|
-
import { Library } from './library.js';
|
|
19
|
+
import { ConnectorResolveError, Library } from './library.js';
|
|
20
20
|
import { resolveLibraryDir } from './library-cache.js';
|
|
21
21
|
import { flagValue } from '../cli-run.js';
|
|
22
22
|
import { defaultOverlayDir } from '../workdir.js';
|
|
@@ -41,19 +41,28 @@ export function openCliLibrary(argv) {
|
|
|
41
41
|
* The connector half of a family's check options, from a library the CLI already
|
|
42
42
|
* opened.
|
|
43
43
|
*
|
|
44
|
-
* Every callback fails OPEN:
|
|
45
|
-
*
|
|
46
|
-
* `check` stay the fast inner-loop rung — a process with no
|
|
47
|
-
* library
|
|
48
|
-
*
|
|
44
|
+
* Every fact callback fails OPEN: no library at all, or an entry the library
|
|
45
|
+
* cannot resolve, means "unknown", and an unknown fact fires no rule. That is
|
|
46
|
+
* what lets `check` stay the fast inner-loop rung — a process with no
|
|
47
|
+
* connectors needs no library. The one exception is the resolution itself:
|
|
48
|
+
* `connectorResolveError` reports the refusal `compile` would throw for an
|
|
49
|
+
* action, the way `eventResolveError` does for an event, so `check` never
|
|
50
|
+
* passes a connector step `compile` refuses (#948).
|
|
51
|
+
*
|
|
52
|
+
* `inputErrors` is the family's field-contract validator. The default speaks
|
|
53
|
+
* the `=`-expression dialect BPMN uses; Flow passes its own, which renders its
|
|
54
|
+
* expressions (see `flowCheckOpts` in `../cli-library.ts`).
|
|
49
55
|
*
|
|
50
56
|
* The `resolutions` are the overlay's: without them every lookup already
|
|
51
57
|
* recorded by `registry prepare --resolve` would be reported as still owed,
|
|
52
58
|
* telling the author to re-run a prepare they already ran.
|
|
53
59
|
*/
|
|
54
|
-
export function connectorCheckOpts(library, bindings) {
|
|
60
|
+
export function connectorCheckOpts(library, bindings, inputErrors = (rc, inputs, resolutions, lookups) => connectorInputErrors(rc, inputs, resolutions, lookups, { dialect: EQUALS_EXPR_DIALECT })) {
|
|
55
61
|
if (!library)
|
|
56
62
|
return bindings ? { bindings } : {};
|
|
63
|
+
// One memoized resolve per connector step, shared by `connectorResolveError`
|
|
64
|
+
// and `connectorInputs`, so the second hook does not resolve again.
|
|
65
|
+
const resolve = connectorResolver(library);
|
|
57
66
|
return {
|
|
58
67
|
eventResolveError: eventResolveErrorFor(library),
|
|
59
68
|
...byoaCheckOpts(library),
|
|
@@ -85,26 +94,51 @@ export function connectorCheckOpts(library, bindings) {
|
|
|
85
94
|
return undefined;
|
|
86
95
|
}
|
|
87
96
|
},
|
|
97
|
+
// The refusal compile throws for an operation it cannot resolve, with its
|
|
98
|
+
// code and text: CONNECTOR_NOT_IN_LIBRARY, CONNECTOR_OBJECT_REQUIRED,
|
|
99
|
+
// CONNECTOR_OBJECT_NOT_GENERIC.
|
|
100
|
+
connectorResolveError: (spec) => {
|
|
101
|
+
const r = resolve(spec);
|
|
102
|
+
return 'error' in r ? r.error : undefined;
|
|
103
|
+
},
|
|
88
104
|
// The field contract compile enforces, from the same validator, so the inner
|
|
89
105
|
// loop and the emit rung cannot disagree about a missing required field, an
|
|
90
|
-
// unknown key or a mistyped value. An operation
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
// The `=`-expression dialect, because this bag serves the families that spell
|
|
94
|
-
// an expression that way. Flow binds its own `connectorInputErrors` in
|
|
95
|
-
// `serialize.ts`, which carries `renderDeep` with it, and overrides this.
|
|
106
|
+
// unknown key or a mistyped value. An unresolvable operation is
|
|
107
|
+
// `connectorResolveError`'s to report; here it just means "unknown".
|
|
96
108
|
connectorInputs: (spec, inputs) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return connectorInputErrors(rc, inputs, library.lookupResolutions ?? {}, library.lookupSpecs(spec.key, spec.action, spec.version, spec.object) ?? {}, { dialect: EQUALS_EXPR_DIALECT });
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
109
|
+
const r = resolve(spec);
|
|
110
|
+
if (!('rc' in r))
|
|
102
111
|
return undefined;
|
|
103
|
-
}
|
|
112
|
+
return inputErrors(r.rc, inputs, library.lookupResolutions ?? {}, library.lookupSpecs(spec.key, spec.action, spec.version, spec.object) ?? {});
|
|
104
113
|
},
|
|
105
114
|
...(bindings ? { bindings } : {}),
|
|
106
115
|
};
|
|
107
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* `library.resolve` for one connector step, memoized per step for the life of
|
|
119
|
+
* one check's options. A {@link ConnectorResolveError} is the refusal compile
|
|
120
|
+
* throws and becomes `error`; anything else thrown is not a verdict about the
|
|
121
|
+
* author's source and stays "unknown", as before.
|
|
122
|
+
*/
|
|
123
|
+
function connectorResolver(library) {
|
|
124
|
+
const memo = new Map();
|
|
125
|
+
return (spec) => {
|
|
126
|
+
const id = JSON.stringify([spec.key, spec.action, spec.version ?? null, spec.object ?? null]);
|
|
127
|
+
let r = memo.get(id);
|
|
128
|
+
if (!r) {
|
|
129
|
+
try {
|
|
130
|
+
r = { rc: library.resolve(spec.key, spec.action, spec.version, spec.object) };
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
r = error instanceof ConnectorResolveError
|
|
134
|
+
? { error: { code: error.code, message: error.message } }
|
|
135
|
+
: { unknown: true };
|
|
136
|
+
}
|
|
137
|
+
memo.set(id, r);
|
|
138
|
+
}
|
|
139
|
+
return r;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
108
142
|
/**
|
|
109
143
|
* `check`'s EVENT_UNKNOWN verdict: the message `library.resolveEvent` throws
|
|
110
144
|
* for a subscription, or `undefined` when it resolves.
|
|
@@ -113,6 +113,21 @@ export interface ConnectorCheckOpts {
|
|
|
113
113
|
resolutions?: LookupResolutions;
|
|
114
114
|
/** Whether a connector step's schema is materialized locally. */
|
|
115
115
|
schemaReadiness?: (connector: string, action: string, version?: string, object?: string) => SchemaReadiness | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Why `compile` could not resolve this operation at all (`library.resolve`
|
|
118
|
+
* throws), or `undefined` when it resolves. Checked before
|
|
119
|
+
* {@link ConnectorCheckOpts.connectorInputs}, which answers `undefined` for an
|
|
120
|
+
* unresolvable operation and so cannot tell "not in the library" from "no facts".
|
|
121
|
+
*/
|
|
122
|
+
connectorResolveError?: (spec: {
|
|
123
|
+
key: string;
|
|
124
|
+
action: string;
|
|
125
|
+
version?: string;
|
|
126
|
+
object?: string;
|
|
127
|
+
}) => {
|
|
128
|
+
code: string;
|
|
129
|
+
message: string;
|
|
130
|
+
} | undefined;
|
|
116
131
|
/** What `compile` would refuse these inputs with, or `undefined` when it would accept them. */
|
|
117
132
|
connectorInputs?: (spec: {
|
|
118
133
|
key: string;
|
|
@@ -157,9 +172,10 @@ export declare function checkConnectorLookups(where: string, spec: ConnectorSpec
|
|
|
157
172
|
*/
|
|
158
173
|
export declare function checkConnectorSchema(where: string, spec: ConnectorSpec, opts: ConnectorCheckOpts, dialect: ExprDialect, report: ReportConnector): void;
|
|
159
174
|
/**
|
|
160
|
-
* The field contract and the binding names `compile` enforces,
|
|
175
|
+
* The operation, its field contract and the binding names `compile` enforces,
|
|
176
|
+
* reported here first.
|
|
161
177
|
*
|
|
162
|
-
*
|
|
178
|
+
* Each rule runs only with the facts in hand (a library for the operation and its fields, a
|
|
163
179
|
* bindings.json for the names); without them `check` stays a pure function of the
|
|
164
180
|
* built model and says nothing, as before.
|
|
165
181
|
*/
|