@uipath/maestro-builder-sdk 6.25.1 → 6.27.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 +22 -21
- package/dist/api-members.md +87 -84
- package/dist/check.js +83 -18
- package/dist/core/carried-definitions.d.ts +33 -0
- package/dist/core/carried-definitions.js +63 -0
- package/dist/core/nodes/versioned.d.ts +35 -0
- package/dist/core/nodes/versioned.js +35 -0
- package/dist/decompile.js +152 -57
- package/dist/flow-expr-check.js +6 -1
- package/dist/flow-node-type.d.ts +8 -3
- package/dist/flow-sdk.d.ts +91 -12
- package/dist/flow-sdk.js +74 -6
- package/dist/merge.js +2 -1
- package/dist/serialize.js +76 -28
- package/package.json +2 -2
package/dist/decompile.js
CHANGED
|
@@ -83,6 +83,13 @@ let runDefs = new Map();
|
|
|
83
83
|
* agent that has to edit the file next.
|
|
84
84
|
*/
|
|
85
85
|
let runRawManifests = [];
|
|
86
|
+
/**
|
|
87
|
+
* The file's definitions for pinned pairs this SDK cannot resolve, by
|
|
88
|
+
* `<type>@<version>` → hoisted const name (#1128). Emitted once, in
|
|
89
|
+
* `.definitions([...])`, so compile resolves the pin against the file's own
|
|
90
|
+
* definition and the node keeps its version even when the author edits it.
|
|
91
|
+
*/
|
|
92
|
+
let runCarried = new Map();
|
|
86
93
|
let runStrict = false;
|
|
87
94
|
/** `DecompileOptions.warn` for the current run, and the messages already sent to it (one line per finding). */
|
|
88
95
|
let runWarn;
|
|
@@ -131,22 +138,21 @@ function verClause(node) {
|
|
|
131
138
|
+ `the file's definitions[] disagrees with the instance.`);
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
|
-
// A pin the SDK cannot resolve
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// (`nodeUnchanged` ignores typeVersion). Interim: a node the author EDITS is
|
|
142
|
-
// re-emitted at the default — the root fix is for compile to accept the
|
|
143
|
-
// definition the file carries.
|
|
141
|
+
// A pin the SDK cannot resolve (design §2.6: exact, against the registered
|
|
142
|
+
// or bundled versions) is kept when the file carries that pair's definition:
|
|
143
|
+
// `.definitions([...])` hands it to compile (#1128). With no definition to
|
|
144
|
+
// carry, keeping the pin would emit source `compile` refuses, so the source
|
|
145
|
+
// omits it: the node compiles at the SDK's default, and `merge --baseline`
|
|
146
|
+
// keeps the UNTOUCHED original node verbatim (`nodeUnchanged` ignores
|
|
147
|
+
// typeVersion).
|
|
144
148
|
//
|
|
145
149
|
// A type with no SDK-known version at all (a connector, a published-resource
|
|
146
150
|
// `uipath.core.*` node, a rawNode) resolves its version elsewhere, so its pin
|
|
147
151
|
// is kept.
|
|
152
|
+
if (runCarried.has(`${node.type}@${tv}`))
|
|
153
|
+
return `version: ${tsLiteral(tv)}`;
|
|
148
154
|
const registered = FLOW_NODE_TYPES.byTypeName(node.type);
|
|
149
|
-
const shipped =
|
|
155
|
+
const shipped = shippedVersions(node.type);
|
|
150
156
|
if (shipped.length > 0 && !shipped.includes(tv)) {
|
|
151
157
|
const known = `${node.type}@${shipped.join(', ')}`;
|
|
152
158
|
if (runStrict) {
|
|
@@ -161,6 +167,15 @@ function verClause(node) {
|
|
|
161
167
|
}
|
|
162
168
|
return `version: ${tsLiteral(tv)}`;
|
|
163
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* The versions this SDK resolves a `{ version }` of `type` against: a
|
|
172
|
+
* registered type's registered versions, any other type's bundled ones; `[]`
|
|
173
|
+
* for a type it ships at no version (a connector, a published resource).
|
|
174
|
+
*/
|
|
175
|
+
function shippedVersions(type) {
|
|
176
|
+
const registered = FLOW_NODE_TYPES.byTypeName(type);
|
|
177
|
+
return registered ? Object.keys(registered.type.versions) : bundledVersions(type);
|
|
178
|
+
}
|
|
164
179
|
function verArg(node) {
|
|
165
180
|
const clause = verClause(node);
|
|
166
181
|
return clause ? `, { ${clause} }` : '';
|
|
@@ -1494,6 +1509,92 @@ function unsupportedSpec(node, imp, o) {
|
|
|
1494
1509
|
// is a safe structural stand-in, and the marker records what it replaced.
|
|
1495
1510
|
return `${imp.use('mock')}() /* TODO: unsupported node type ${commentText(node.type)} */`;
|
|
1496
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Every pinned `<type>@<version>` in the file (root and subflows) that this SDK
|
|
1514
|
+
* cannot resolve, for a type it ships at some version, and that the file's
|
|
1515
|
+
* `definitions[]` holds — mapped to the const name decompile hoists the
|
|
1516
|
+
* definition under. For instance a quick-form node at 1.6 maps
|
|
1517
|
+
* `uipath.human-in-the-loop.quick-form@1.6` → `quickFormV1_6Definition`.
|
|
1518
|
+
*/
|
|
1519
|
+
function carriedPairs(flow) {
|
|
1520
|
+
const out = new Map();
|
|
1521
|
+
const taken = new Set();
|
|
1522
|
+
const scopes = [flow.nodes ?? [], ...Object.values(flow.subflows ?? {}).map((sf) => sf?.nodes ?? [])];
|
|
1523
|
+
for (const n of scopes.flat()) {
|
|
1524
|
+
const key = `${n.type}@${n.typeVersion}`;
|
|
1525
|
+
if (!n.typeVersion || out.has(key) || !runDefs.has(key))
|
|
1526
|
+
continue;
|
|
1527
|
+
const shipped = shippedVersions(n.type);
|
|
1528
|
+
if (shipped.length === 0 || shipped.includes(n.typeVersion))
|
|
1529
|
+
continue;
|
|
1530
|
+
const base = `${camel(String(n.type).split('.').pop() ?? 'node')}V${String(n.typeVersion).replace(/[^A-Za-z0-9]+/g, '_')}Definition`;
|
|
1531
|
+
let name = base;
|
|
1532
|
+
for (let i = 2; taken.has(name); i++)
|
|
1533
|
+
name = `${base}${i}`;
|
|
1534
|
+
taken.add(name);
|
|
1535
|
+
out.set(key, name);
|
|
1536
|
+
warnCarriedInputDrift(n, runDefs.get(key));
|
|
1537
|
+
}
|
|
1538
|
+
return out;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* The input names a definition declares: its `inputDefaults` keys and the
|
|
1542
|
+
* top-level `inputs.<name>` its form fields edit. For instance quick-form@1.1
|
|
1543
|
+
* gives `recipient, schema`.
|
|
1544
|
+
*/
|
|
1545
|
+
function definitionInputNames(def) {
|
|
1546
|
+
const names = new Set(Object.keys(def?.inputDefaults ?? {}));
|
|
1547
|
+
const visit = (v) => {
|
|
1548
|
+
if (Array.isArray(v)) {
|
|
1549
|
+
v.forEach(visit);
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
if (!v || typeof v !== 'object')
|
|
1553
|
+
return;
|
|
1554
|
+
const field = v.name;
|
|
1555
|
+
if (typeof field === 'string' && field.startsWith('inputs.'))
|
|
1556
|
+
names.add(field.slice('inputs.'.length).split(/[.[]/)[0]);
|
|
1557
|
+
for (const child of Object.values(v))
|
|
1558
|
+
visit(child);
|
|
1559
|
+
};
|
|
1560
|
+
visit(def?.form);
|
|
1561
|
+
return [...names].sort();
|
|
1562
|
+
}
|
|
1563
|
+
/** Compare two dotted versions numerically: `1.10` sorts after `1.9`. */
|
|
1564
|
+
function compareVersions(a, b) {
|
|
1565
|
+
const pa = a.split('.').map(Number);
|
|
1566
|
+
const pb = b.split('.').map(Number);
|
|
1567
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1568
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1569
|
+
if (d !== 0)
|
|
1570
|
+
return d;
|
|
1571
|
+
}
|
|
1572
|
+
return 0;
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Warn when a carried definition's inputs differ from the nearest version the
|
|
1576
|
+
* SDK ships (the highest one below the carried version, else the lowest). An
|
|
1577
|
+
* edited node keeps the file's pin, but its inputs are rebuilt in the SDK's
|
|
1578
|
+
* shape, so a renamed input would be written under the wrong name. For
|
|
1579
|
+
* instance `node "review1" keeps quick-form@1.6 ...; its inputs differ from
|
|
1580
|
+
* 1.1 (assignee)`.
|
|
1581
|
+
*/
|
|
1582
|
+
function warnCarriedInputDrift(node, carried) {
|
|
1583
|
+
const versions = shippedVersions(node.type).filter((v) => BUNDLED_DEFINITIONS[`${node.type}@${v}`]).sort(compareVersions);
|
|
1584
|
+
const below = versions.filter((v) => compareVersions(v, String(node.typeVersion)) < 0);
|
|
1585
|
+
const nearest = below.length > 0 ? below[below.length - 1] : versions[0];
|
|
1586
|
+
if (nearest === undefined)
|
|
1587
|
+
return;
|
|
1588
|
+
const shipped = BUNDLED_DEFINITIONS[`${node.type}@${nearest}`];
|
|
1589
|
+
const factoryVersion = nearest;
|
|
1590
|
+
const a = definitionInputNames(carried);
|
|
1591
|
+
const b = definitionInputNames(shipped);
|
|
1592
|
+
const differ = [...a.filter((x) => !b.includes(x)), ...b.filter((x) => !a.includes(x))].sort();
|
|
1593
|
+
if (differ.length === 0)
|
|
1594
|
+
return;
|
|
1595
|
+
warnOnce(`node ${tsLiteral(node.id)} keeps ${node.type}@${node.typeVersion} from the file's definitions[]; its inputs differ from `
|
|
1596
|
+
+ `${factoryVersion} (${differ.join(', ')}), so an edit to it may need rawNode.`);
|
|
1597
|
+
}
|
|
1497
1598
|
/** `some-node.id` → `someNodeId`, for a hoisted const name. */
|
|
1498
1599
|
function camel(id) {
|
|
1499
1600
|
const parts = String(id).split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
@@ -2528,6 +2629,11 @@ function parseSwitchCase(expr) {
|
|
|
2528
2629
|
* expression is `<discriminant> === <JSON-literal>`; the discriminant (shared by
|
|
2529
2630
|
* all cases) becomes `on`, and each RHS literal a case `value`. The case port is
|
|
2530
2631
|
* `case-<caseId>`; the optional default arm is the `default` port.
|
|
2632
|
+
*
|
|
2633
|
+
* A switch whose cases do NOT share that shape — the product's per-case form,
|
|
2634
|
+
* `amount > 100` / `amount <= 100`, first truthy case wins — decompiles to
|
|
2635
|
+
* `when:` cases with `on` left `undefined`, so each condition round-trips
|
|
2636
|
+
* as written (#1131).
|
|
2531
2637
|
*/
|
|
2532
2638
|
function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
|
|
2533
2639
|
const r = ipdom.get(node.id) ?? stop;
|
|
@@ -2535,70 +2641,55 @@ function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
|
|
|
2535
2641
|
let caseIndex = 0;
|
|
2536
2642
|
let disc;
|
|
2537
2643
|
let nonUniform = false; // a case whose expr isn't `<disc> === <literal>`, or a differing discriminant
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
if (parsed) {
|
|
2644
|
+
// First pass: is every case one shared `<disc> === <literal>`? If not, every
|
|
2645
|
+
// case is emitted as `when:` with its expression as written.
|
|
2646
|
+
const parsed = cases.map((c) => {
|
|
2647
|
+
const p = parseSwitchCase(String(c.expression ?? ''));
|
|
2648
|
+
if (p) {
|
|
2544
2649
|
if (disc === undefined)
|
|
2545
|
-
disc =
|
|
2546
|
-
else if (disc !==
|
|
2650
|
+
disc = p.lhs;
|
|
2651
|
+
else if (disc !== p.lhs)
|
|
2547
2652
|
nonUniform = true;
|
|
2548
|
-
valueLiteral = parsed.literal;
|
|
2549
|
-
value = parsed.value;
|
|
2550
2653
|
}
|
|
2551
2654
|
else {
|
|
2552
2655
|
nonUniform = true;
|
|
2553
|
-
// Keep the best-effort output the TODO comment points at.
|
|
2554
|
-
const m = String(c.expression ?? '').match(/^(.*?)\s*===?\s*(.*)$/);
|
|
2555
|
-
if (m && m[1].trim() && disc === undefined)
|
|
2556
|
-
disc = m[1].trim();
|
|
2557
|
-
if (m) {
|
|
2558
|
-
valueLiteral = m[2].trim();
|
|
2559
|
-
try {
|
|
2560
|
-
value = tsLiteral(JSON.parse(valueLiteral));
|
|
2561
|
-
}
|
|
2562
|
-
catch {
|
|
2563
|
-
const q = valueLiteral.match(/^'(.*)'$/);
|
|
2564
|
-
value = q ? tsLiteral(q[1]) : tsLiteral(valueLiteral);
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
2656
|
}
|
|
2657
|
+
return { c, p };
|
|
2658
|
+
});
|
|
2659
|
+
const caseSrcs = parsed.map(({ c, p }) => {
|
|
2568
2660
|
const armSegs = forkRegion(portEdges(ctx.graph, node.id, `case-${c.id}`), r, scope, ipdom, ctx, refInto);
|
|
2569
|
-
|
|
2661
|
+
caseIndex += 1;
|
|
2662
|
+
const defaultLabel = nonUniform || !p ? `Case ${caseIndex}` : `Case ${p.literal}`;
|
|
2570
2663
|
const labelPart = c.label && c.label !== defaultLabel ? `, label: ${tsLiteral(c.label)}` : '';
|
|
2571
2664
|
// The file's case id comes back when it is not the positional one serialize
|
|
2572
2665
|
// derives (a designer-drawn switch uses `default-1`, …), so the recompiled
|
|
2573
2666
|
// edges leave from the ORIGINAL node's handles, which merge keeps (#1130).
|
|
2574
|
-
caseIndex += 1;
|
|
2575
2667
|
const idPart = typeof c.id === 'string' && c.id !== '' && c.id !== `case_${caseIndex}` ? `, id: ${tsLiteral(c.id)}` : '';
|
|
2576
|
-
|
|
2668
|
+
const head = nonUniform || !p
|
|
2669
|
+
? `when: ${emitBareExpr(String(c.expression ?? ''), ctx.imp, ctx.inputNames)}`
|
|
2670
|
+
: `value: ${p.value}`;
|
|
2671
|
+
return `{ ${head}${labelPart}${idPart}, body: ${armCb('b', armSegs)} }`;
|
|
2672
|
+
});
|
|
2673
|
+
// A case with no condition has no `when:` that compiles: emitting `js\`\``
|
|
2674
|
+
// would fail at compile ([EXPR_SYNTAX]) with no warning here.
|
|
2675
|
+
const empty = cases.filter((c) => String(c.expression ?? '').trim() === '');
|
|
2676
|
+
if (empty.length > 0) {
|
|
2677
|
+
const listing = empty.map((c) => tsLiteral(c.label ?? c.id)).join(', ');
|
|
2678
|
+
const message = `switch ${tsLiteral(node.id)} has a case with no condition (${listing}). `
|
|
2679
|
+
+ '`.switch()` needs a condition on every case, so the decompiled `when:` is empty and recompiling fails. '
|
|
2680
|
+
+ 'Give the case a condition in the .flow, or edit this switch in the .flow JSON directly.';
|
|
2681
|
+
if (runStrict)
|
|
2682
|
+
throw new Error(`decompile: ${message}`);
|
|
2683
|
+
warnOnce(message);
|
|
2577
2684
|
}
|
|
2578
|
-
const on = emitBareExpr(disc ?? '', ctx.imp, ctx.inputNames);
|
|
2685
|
+
const on = nonUniform ? 'undefined' : emitBareExpr(disc ?? '', ctx.imp, ctx.inputNames);
|
|
2579
2686
|
const hasDefault = (node.inputs ?? {}).hasDefault === true || portEdge(ctx.graph, node.id, 'default') !== undefined;
|
|
2580
2687
|
const defArm = hasDefault
|
|
2581
2688
|
? `, ${armCb('d', forkRegion(portEdges(ctx.graph, node.id, 'default'), r, scope, ipdom, ctx, refInto))}`
|
|
2582
2689
|
: '';
|
|
2583
2690
|
const caseList = `[\n${caseSrcs.map((c) => reindent(c, ' ')).join(',\n')},\n]`;
|
|
2584
|
-
// The builder's `.switch(on, [{value}])` models `on === value` only. A flow whose
|
|
2585
|
-
// cases carry arbitrary per-case boolean expressions (or a non-uniform
|
|
2586
|
-
// discriminant) cannot round-trip through it — flag it rather than emit silently
|
|
2587
|
-
// wrong values.
|
|
2588
|
-
if (nonUniform) {
|
|
2589
|
-
const listing = cases.map((c) => `${tsLiteral(c.label ?? c.id)}: ${tsLiteral(String(c.expression ?? ''))}`).join(', ');
|
|
2590
|
-
const message = `switch ${tsLiteral(node.id)} has per-case conditions that are not one shared \`<discriminant> === <value>\` `
|
|
2591
|
-
+ `(${listing}). \`.switch()\` models only that form, so the decompiled \`on\`/\`value\` are placeholders: `
|
|
2592
|
-
+ 'recompiling either fails or changes which case runs. Edit this switch in the .flow JSON directly.';
|
|
2593
|
-
if (runStrict)
|
|
2594
|
-
throw new Error(`decompile: ${message}`);
|
|
2595
|
-
warnOnce(message);
|
|
2596
|
-
}
|
|
2597
|
-
const warn = nonUniform
|
|
2598
|
-
? `\n/* TODO: this switch's case expressions are not a uniform \`<discriminant> === <value>\`; the recovered on/value are best-effort and need review. */`
|
|
2599
|
-
: '';
|
|
2600
2691
|
const ver = verArg(node);
|
|
2601
|
-
return `.switch(${tsLiteral(node.id)}, ${on}, ${caseList}${defArm}${ver && !defArm ? ', undefined' : ''}${ver})
|
|
2692
|
+
return `.switch(${tsLiteral(node.id)}, ${on}, ${caseList}${defArm}${ver && !defArm ? ', undefined' : ''}${ver})`;
|
|
2602
2693
|
}
|
|
2603
2694
|
/**
|
|
2604
2695
|
* Recover `.stickyNotes([...])` from the scope's `stickyNote` nodes.
|
|
@@ -2923,6 +3014,7 @@ export function decompile(flow, options = {}) {
|
|
|
2923
3014
|
if (!runDefs.has(d.nodeType))
|
|
2924
3015
|
runDefs.set(d.nodeType, d);
|
|
2925
3016
|
}
|
|
3017
|
+
runCarried = carriedPairs(flow);
|
|
2926
3018
|
const imp = new Imports();
|
|
2927
3019
|
const graph = new Graph(flow.nodes ?? [], flow.edges ?? []);
|
|
2928
3020
|
const globals = readFlowGlobals(flow.variables, runStrict);
|
|
@@ -2956,6 +3048,8 @@ export function decompile(flow, options = {}) {
|
|
|
2956
3048
|
if (typeof flow.description === 'string') {
|
|
2957
3049
|
e.line(d, `.description(${tsLiteral(flow.description)})`);
|
|
2958
3050
|
}
|
|
3051
|
+
if (runCarried.size > 0)
|
|
3052
|
+
e.line(d, `.definitions([${[...runCarried.values()].join(', ')}])`);
|
|
2959
3053
|
// Trigger nodes. One root is the default: the isDefaultEntryPoint carrier
|
|
2960
3054
|
// when the file marks one (a multi-root flow always does), else `start`.
|
|
2961
3055
|
// A node's TYPE is not enough: `uipath.connector.event.<key>.<event>` is a
|
|
@@ -3042,7 +3136,8 @@ export function decompile(flow, options = {}) {
|
|
|
3042
3136
|
const lay = layoutSeg(flow, (flow.nodes ?? []).filter((n) => !detachedIds.has(n.id)), (flow.edges ?? []).filter((edge) => !detachedIds.has(edge.sourceNodeId) && !detachedIds.has(edge.targetNodeId)));
|
|
3043
3137
|
const layout = lay ? reindent(lay, ' ') : '';
|
|
3044
3138
|
const chain = [header, eps, body, sticky, layout, ' .build();'].filter((s) => s.length > 0).join('\n');
|
|
3045
|
-
const
|
|
3139
|
+
const carriedConsts = [...runCarried].map(([key, name]) => [name, JSON.stringify(runDefs.get(key), null, 2)]);
|
|
3140
|
+
const hoisted = [...carriedConsts, ...runRawManifests]
|
|
3046
3141
|
.map(([name, json]) => `const ${name} = ${json};`)
|
|
3047
3142
|
.join('\n\n');
|
|
3048
3143
|
return [imp.line(importSpecifier), hoisted, chain].filter((p) => p.length > 0).join('\n\n') + '\n';
|
package/dist/flow-expr-check.js
CHANGED
|
@@ -147,7 +147,12 @@ function walk(steps, roots, out) {
|
|
|
147
147
|
walk(s.otherwise, roots, out);
|
|
148
148
|
break;
|
|
149
149
|
case 'switch': {
|
|
150
|
-
|
|
150
|
+
if (s.on)
|
|
151
|
+
pushExpr(s.on, s.name, roots, out);
|
|
152
|
+
// A `when` case's condition is read like a branch condition (#1131).
|
|
153
|
+
for (const c of s.cases)
|
|
154
|
+
if (c.when)
|
|
155
|
+
pushExpr(c.when, s.name, roots, out);
|
|
151
156
|
// Inside a case (or default) body the discriminant is bound to
|
|
152
157
|
// `$vars.value`, so it is a valid root only there.
|
|
153
158
|
const caseRoots = new Set(roots).add('value');
|
package/dist/flow-node-type.d.ts
CHANGED
|
@@ -97,7 +97,12 @@ export interface FlowSerializeContext {
|
|
|
97
97
|
bind(entry: {
|
|
98
98
|
readonly id: string;
|
|
99
99
|
} & Readonly<Record<string, unknown>>): void;
|
|
100
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* The bundled definition `<typeName>@<version>`, else one the flow carries;
|
|
102
|
+
* undefined when neither exists. When the node is emitted at a carried
|
|
103
|
+
* version, asking for its own type answers with that carried definition at
|
|
104
|
+
* any `version`, so a type's defaults follow the file's definition.
|
|
105
|
+
*/
|
|
101
106
|
definition(typeName: string, version: string): unknown;
|
|
102
107
|
/**
|
|
103
108
|
* The flow's bindings so far, by id — read-only. For a type that SHARES a
|
|
@@ -186,8 +191,8 @@ export interface FlowCheckContext {
|
|
|
186
191
|
/** The version the node will be emitted at. */
|
|
187
192
|
readonly version: string;
|
|
188
193
|
/**
|
|
189
|
-
* The
|
|
190
|
-
*
|
|
194
|
+
* The definition serialize emits this step from: the one the flow carries at
|
|
195
|
+
* a carried version, else the SDK's bundled one; undefined when neither exists.
|
|
191
196
|
*/
|
|
192
197
|
definition(): unknown;
|
|
193
198
|
/** Report a diagnostic on this step. Its severity is the family catalog's for `code`. */
|
package/dist/flow-sdk.d.ts
CHANGED
|
@@ -338,9 +338,33 @@ export declare function conversationTrigger(): TriggerSpec;
|
|
|
338
338
|
export declare function voiceTrigger(): TriggerSpec;
|
|
339
339
|
/** A value a `.switch` case compares the discriminant against. */
|
|
340
340
|
export type CaseValue = string | number | boolean;
|
|
341
|
+
/**
|
|
342
|
+
* One case of `.switch(name, on, cases)`: either `{ value }`, taken when
|
|
343
|
+
* `on === value`, or `{ when }`, taken when its own condition is true (the
|
|
344
|
+
* product switch's per-case form, #1131). Cases are tried top to bottom and the
|
|
345
|
+
* first match wins.
|
|
346
|
+
*/
|
|
347
|
+
export type SwitchCase = ({
|
|
348
|
+
value: CaseValue;
|
|
349
|
+
when?: undefined;
|
|
350
|
+
} | {
|
|
351
|
+
when: Expr;
|
|
352
|
+
value?: undefined;
|
|
353
|
+
}) & {
|
|
354
|
+
body: (b: ArmBuilder) => void;
|
|
355
|
+
label?: string;
|
|
356
|
+
id?: string;
|
|
357
|
+
};
|
|
341
358
|
/** One arm of a built `.switch` (the serializer's view: body already collected). */
|
|
342
359
|
export interface SwitchArm {
|
|
343
|
-
value
|
|
360
|
+
/** The value `on` is compared against (`on === value`). Absent on a `when` case. */
|
|
361
|
+
value?: CaseValue;
|
|
362
|
+
/**
|
|
363
|
+
* The case's own boolean condition, instead of `on === value` — the product
|
|
364
|
+
* switch stores one condition per case and takes the first that is true
|
|
365
|
+
* (#1131). Exactly one of `value` / `when`.
|
|
366
|
+
*/
|
|
367
|
+
when?: Expr;
|
|
344
368
|
label?: string;
|
|
345
369
|
/**
|
|
346
370
|
* The case's wire id — `inputs.cases[].id`, and the `case-<id>` port its arm
|
|
@@ -500,7 +524,7 @@ export type Step = {
|
|
|
500
524
|
kind: 'switch';
|
|
501
525
|
name: string;
|
|
502
526
|
label?: string;
|
|
503
|
-
on
|
|
527
|
+
on?: Expr;
|
|
504
528
|
cases: SwitchArm[];
|
|
505
529
|
default?: Step[];
|
|
506
530
|
options?: NodeOptions;
|
|
@@ -731,6 +755,12 @@ export interface BuiltFlow {
|
|
|
731
755
|
layout?: FlowLayout;
|
|
732
756
|
/** Canvas sticky notes from `.stickyNotes()`. Inert annotations. */
|
|
733
757
|
stickyNotes?: StickyNote[];
|
|
758
|
+
/**
|
|
759
|
+
* Node definitions the flow carries, from `.definitions()`: exact manifests a
|
|
760
|
+
* pinned `{ version }` resolves against when the SDK does not bundle that
|
|
761
|
+
* version.
|
|
762
|
+
*/
|
|
763
|
+
definitions?: Record<string, unknown>[];
|
|
734
764
|
/** Additional roots from `.entryPoint()`. The default root stays `.trigger()`/`.input()`. */
|
|
735
765
|
entryPoints?: BuiltEntryPoint[];
|
|
736
766
|
/**
|
|
@@ -978,23 +1008,30 @@ declare class StepList {
|
|
|
978
1008
|
* ], (other) => other.step('unknown', script({ code: 'return "Unknown";' })))
|
|
979
1009
|
* ```
|
|
980
1010
|
*
|
|
1011
|
+
* @example
|
|
1012
|
+
* **Cases with their own conditions** (`on` is `undefined`)
|
|
1013
|
+
* ```ts
|
|
1014
|
+
* .switch('route', undefined, [
|
|
1015
|
+
* { when: js`$vars.start.output.amount > 100`, label: 'High', body: (b) => b.step('review', script({ code: 'return 1;' })) },
|
|
1016
|
+
* { when: js`$vars.start.output.amount <= 100`, label: 'Low', body: (b) => b.step('auto', script({ code: 'return 2;' })) },
|
|
1017
|
+
* ])
|
|
1018
|
+
* ```
|
|
1019
|
+
*
|
|
981
1020
|
* @param name - The switch node's id.
|
|
982
|
-
* @param on - The value to compare each case against
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
*
|
|
1021
|
+
* @param on - The value to compare each `value` case against; `undefined`
|
|
1022
|
+
* when every case carries its own `when` condition.
|
|
1023
|
+
* @param cases - One entry per arm, evaluated top to bottom; the first match
|
|
1024
|
+
* wins. A case is `{ value }` (strict `on === value`) or `{ when }` (its own
|
|
1025
|
+
* boolean expression). `label` names the arm's outgoing edge. `id` keeps a
|
|
1026
|
+
* case's wire id (`inputs.cases[].id`, port `case-<id>`); omitted, it is
|
|
1027
|
+
* `case_<n>`.
|
|
986
1028
|
* @param defaultFn - Receives a sub-builder for the arm taken when nothing
|
|
987
1029
|
* matches. Omit it and an unmatched value simply continues.
|
|
988
1030
|
* @param options - Node options; `version` selects the exact definition
|
|
989
1031
|
* version (see {@link NodeOptions}).
|
|
990
1032
|
* @returns This builder, after every arm has rejoined.
|
|
991
1033
|
*/
|
|
992
|
-
switch(name: string, on: Expr, cases:
|
|
993
|
-
value: CaseValue;
|
|
994
|
-
body: (b: ArmBuilder) => void;
|
|
995
|
-
label?: string;
|
|
996
|
-
id?: string;
|
|
997
|
-
}[], defaultFn?: (b: StepList) => void, options?: NodeOptions): this;
|
|
1034
|
+
switch(name: string, on: Expr | undefined, cases: SwitchCase[], defaultFn?: (b: StepList) => void, options?: NodeOptions): this;
|
|
998
1035
|
/**
|
|
999
1036
|
* Run two or more arms **in parallel** from this point and join them back
|
|
1000
1037
|
* together on a Merge node — the one place the builder stops being a straight
|
|
@@ -1216,6 +1253,7 @@ declare class FlowBuilder extends StepList {
|
|
|
1216
1253
|
private _stickyNotes?;
|
|
1217
1254
|
private _version;
|
|
1218
1255
|
private _schemaVersion?;
|
|
1256
|
+
private _definitions;
|
|
1219
1257
|
private _inputs;
|
|
1220
1258
|
private _outputs;
|
|
1221
1259
|
private _vars;
|
|
@@ -1423,6 +1461,47 @@ declare class FlowBuilder extends StepList {
|
|
|
1423
1461
|
* @defaultValue `'1.9'` — `FLOW_FORMAT_PROFILE.version`, the fleet write floor.
|
|
1424
1462
|
*/
|
|
1425
1463
|
schemaVersion(version: string): this;
|
|
1464
|
+
/**
|
|
1465
|
+
* Carry node definitions this SDK does not bundle, so a `{ version }` pin on
|
|
1466
|
+
* one of those versions compiles.
|
|
1467
|
+
*
|
|
1468
|
+
* @remarks
|
|
1469
|
+
* A pinned `{ version }` resolves exactly (design §2.6): first against the
|
|
1470
|
+
* definitions the SDK was released with, then against the ones carried here.
|
|
1471
|
+
* Nothing else changes — an unpinned node still takes the SDK's default, and
|
|
1472
|
+
* a bundled version still compiles from the bundle.
|
|
1473
|
+
*
|
|
1474
|
+
* Its job is BROWNFIELD PRESERVATION. A `.flow` file holds the exact
|
|
1475
|
+
* definition of every node it uses, but the registry moves faster than an SDK
|
|
1476
|
+
* release: `uip maestro flow node add` writes
|
|
1477
|
+
* `uipath.human-in-the-loop.quick-form@1.6` while this SDK bundles 1.0 and
|
|
1478
|
+
* 1.1. `flow-decompile` copies the file's own entry into this call for each
|
|
1479
|
+
* such pair, so the decompiled source compiles unedited and a node you edit
|
|
1480
|
+
* keeps the file's version instead of moving to the SDK's.
|
|
1481
|
+
*
|
|
1482
|
+
* Each manifest must be an exact copy of one the platform served (from the
|
|
1483
|
+
* file's `definitions[]`, or `uip maestro flow registry get`): it names its
|
|
1484
|
+
* `nodeType` and `version`, and has a `display.label`. The SDK still emits
|
|
1485
|
+
* the node's inputs from its own factory, so a version whose inputs differ
|
|
1486
|
+
* from the SDK's shape needs `rawNode` instead.
|
|
1487
|
+
*
|
|
1488
|
+
* @example
|
|
1489
|
+
* **Keep a quick-form task at the version the file pins**
|
|
1490
|
+
* ```ts
|
|
1491
|
+
* // The .flow file's whole definitions[] entry for quick-form@1.6 (or
|
|
1492
|
+
* // `uip maestro flow registry get`), copied verbatim, as flow-decompile hoists it.
|
|
1493
|
+
* // A stub with only nodeType/version/display passes validation but breaks the canvas.
|
|
1494
|
+
* declare const quickFormV1_6Definition: Record<string, unknown>;
|
|
1495
|
+
* flow('review')
|
|
1496
|
+
* .definitions([quickFormV1_6Definition])
|
|
1497
|
+
* .step('review', hitl({ variant: 'quick-form', fields: [], outcomes: ['Submit'] }), { version: '1.6' });
|
|
1498
|
+
* ```
|
|
1499
|
+
*
|
|
1500
|
+
* @param manifests - Node manifests, each with its own `nodeType`, `version` and `display.label`.
|
|
1501
|
+
* @returns This builder, so calls chain.
|
|
1502
|
+
* @throws Error when a manifest is not a node manifest, or two differ for one `nodeType@version`.
|
|
1503
|
+
*/
|
|
1504
|
+
definitions(manifests: readonly Record<string, unknown>[]): this;
|
|
1426
1505
|
/**
|
|
1427
1506
|
* Declare the flow's inputs. Read them with `input('<name>')`.
|
|
1428
1507
|
*
|
package/dist/flow-sdk.js
CHANGED
|
@@ -45,6 +45,7 @@ import { builtinActionSpec, builtinTriggerSpec, subscriptionFrom } from './core/
|
|
|
45
45
|
import { checkFlowExpressions, FlowBuildError } from './flow-expr-check.js';
|
|
46
46
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
47
47
|
import { schemaVersionRefusal } from './schema-version.js';
|
|
48
|
+
import { indexCarriedDefinitions } from './core/carried-definitions.js';
|
|
48
49
|
export { FlowBuildError, checkFlowExpressions } from './flow-expr-check.js';
|
|
49
50
|
const TYPE_NAMES = Object.values(types);
|
|
50
51
|
/**
|
|
@@ -611,11 +612,23 @@ class StepList {
|
|
|
611
612
|
* ], (other) => other.step('unknown', script({ code: 'return "Unknown";' })))
|
|
612
613
|
* ```
|
|
613
614
|
*
|
|
615
|
+
* @example
|
|
616
|
+
* **Cases with their own conditions** (`on` is `undefined`)
|
|
617
|
+
* ```ts
|
|
618
|
+
* .switch('route', undefined, [
|
|
619
|
+
* { when: js`$vars.start.output.amount > 100`, label: 'High', body: (b) => b.step('review', script({ code: 'return 1;' })) },
|
|
620
|
+
* { when: js`$vars.start.output.amount <= 100`, label: 'Low', body: (b) => b.step('auto', script({ code: 'return 2;' })) },
|
|
621
|
+
* ])
|
|
622
|
+
* ```
|
|
623
|
+
*
|
|
614
624
|
* @param name - The switch node's id.
|
|
615
|
-
* @param on - The value to compare each case against
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
625
|
+
* @param on - The value to compare each `value` case against; `undefined`
|
|
626
|
+
* when every case carries its own `when` condition.
|
|
627
|
+
* @param cases - One entry per arm, evaluated top to bottom; the first match
|
|
628
|
+
* wins. A case is `{ value }` (strict `on === value`) or `{ when }` (its own
|
|
629
|
+
* boolean expression). `label` names the arm's outgoing edge. `id` keeps a
|
|
630
|
+
* case's wire id (`inputs.cases[].id`, port `case-<id>`); omitted, it is
|
|
631
|
+
* `case_<n>`.
|
|
619
632
|
* @param defaultFn - Receives a sub-builder for the arm taken when nothing
|
|
620
633
|
* matches. Omit it and an unmatched value simply continues.
|
|
621
634
|
* @param options - Node options; `version` selects the exact definition
|
|
@@ -633,7 +646,14 @@ class StepList {
|
|
|
633
646
|
// An arm's own `.label(...)` wins over the case config's `label`; either
|
|
634
647
|
// overrides the serializer's derived `Case "<value>"`.
|
|
635
648
|
const label = arm.armLabel ?? c.label;
|
|
636
|
-
return {
|
|
649
|
+
return {
|
|
650
|
+
// Both kept as given, so check can refuse a case with both or neither.
|
|
651
|
+
...(c.value !== undefined ? { value: c.value } : {}),
|
|
652
|
+
...(c.when !== undefined ? { when: toExpr(c.when) } : {}),
|
|
653
|
+
...(label !== undefined ? { label } : {}),
|
|
654
|
+
...(c.id !== undefined ? { id: c.id } : {}),
|
|
655
|
+
body: arm.steps,
|
|
656
|
+
};
|
|
637
657
|
});
|
|
638
658
|
let dflt;
|
|
639
659
|
if (defaultFn) {
|
|
@@ -648,7 +668,7 @@ class StepList {
|
|
|
648
668
|
this.steps.push({
|
|
649
669
|
kind: 'switch',
|
|
650
670
|
name,
|
|
651
|
-
on: toExpr(on),
|
|
671
|
+
...(on !== undefined ? { on: toExpr(on) } : {}),
|
|
652
672
|
cases: arms,
|
|
653
673
|
...(dflt !== undefined ? { default: dflt } : {}),
|
|
654
674
|
...(options ? { options } : {}),
|
|
@@ -881,6 +901,7 @@ class FlowBuilder extends StepList {
|
|
|
881
901
|
_stickyNotes;
|
|
882
902
|
_version = '1.0.0';
|
|
883
903
|
_schemaVersion;
|
|
904
|
+
_definitions = [];
|
|
884
905
|
_inputs = [];
|
|
885
906
|
_outputs = [];
|
|
886
907
|
_vars = [];
|
|
@@ -1144,6 +1165,52 @@ class FlowBuilder extends StepList {
|
|
|
1144
1165
|
this._schemaVersion = version;
|
|
1145
1166
|
return this;
|
|
1146
1167
|
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Carry node definitions this SDK does not bundle, so a `{ version }` pin on
|
|
1170
|
+
* one of those versions compiles.
|
|
1171
|
+
*
|
|
1172
|
+
* @remarks
|
|
1173
|
+
* A pinned `{ version }` resolves exactly (design §2.6): first against the
|
|
1174
|
+
* definitions the SDK was released with, then against the ones carried here.
|
|
1175
|
+
* Nothing else changes — an unpinned node still takes the SDK's default, and
|
|
1176
|
+
* a bundled version still compiles from the bundle.
|
|
1177
|
+
*
|
|
1178
|
+
* Its job is BROWNFIELD PRESERVATION. A `.flow` file holds the exact
|
|
1179
|
+
* definition of every node it uses, but the registry moves faster than an SDK
|
|
1180
|
+
* release: `uip maestro flow node add` writes
|
|
1181
|
+
* `uipath.human-in-the-loop.quick-form@1.6` while this SDK bundles 1.0 and
|
|
1182
|
+
* 1.1. `flow-decompile` copies the file's own entry into this call for each
|
|
1183
|
+
* such pair, so the decompiled source compiles unedited and a node you edit
|
|
1184
|
+
* keeps the file's version instead of moving to the SDK's.
|
|
1185
|
+
*
|
|
1186
|
+
* Each manifest must be an exact copy of one the platform served (from the
|
|
1187
|
+
* file's `definitions[]`, or `uip maestro flow registry get`): it names its
|
|
1188
|
+
* `nodeType` and `version`, and has a `display.label`. The SDK still emits
|
|
1189
|
+
* the node's inputs from its own factory, so a version whose inputs differ
|
|
1190
|
+
* from the SDK's shape needs `rawNode` instead.
|
|
1191
|
+
*
|
|
1192
|
+
* @example
|
|
1193
|
+
* **Keep a quick-form task at the version the file pins**
|
|
1194
|
+
* ```ts
|
|
1195
|
+
* // The .flow file's whole definitions[] entry for quick-form@1.6 (or
|
|
1196
|
+
* // `uip maestro flow registry get`), copied verbatim, as flow-decompile hoists it.
|
|
1197
|
+
* // A stub with only nodeType/version/display passes validation but breaks the canvas.
|
|
1198
|
+
* declare const quickFormV1_6Definition: Record<string, unknown>;
|
|
1199
|
+
* flow('review')
|
|
1200
|
+
* .definitions([quickFormV1_6Definition])
|
|
1201
|
+
* .step('review', hitl({ variant: 'quick-form', fields: [], outcomes: ['Submit'] }), { version: '1.6' });
|
|
1202
|
+
* ```
|
|
1203
|
+
*
|
|
1204
|
+
* @param manifests - Node manifests, each with its own `nodeType`, `version` and `display.label`.
|
|
1205
|
+
* @returns This builder, so calls chain.
|
|
1206
|
+
* @throws Error when a manifest is not a node manifest, or two differ for one `nodeType@version`.
|
|
1207
|
+
*/
|
|
1208
|
+
definitions(manifests) {
|
|
1209
|
+
const known = indexCarriedDefinitions(this._definitions, 'definitions()');
|
|
1210
|
+
indexCarriedDefinitions(manifests, 'definitions()', known);
|
|
1211
|
+
this._definitions = [...known.values()];
|
|
1212
|
+
return this;
|
|
1213
|
+
}
|
|
1147
1214
|
/**
|
|
1148
1215
|
* Declare the flow's inputs. Read them with `input('<name>')`.
|
|
1149
1216
|
*
|
|
@@ -1234,6 +1301,7 @@ class FlowBuilder extends StepList {
|
|
|
1234
1301
|
...(this._stickyNotes !== undefined ? { stickyNotes: this._stickyNotes } : {}),
|
|
1235
1302
|
version: this._version,
|
|
1236
1303
|
...(this._schemaVersion !== undefined ? { schemaVersion: this._schemaVersion } : {}),
|
|
1304
|
+
...(this._definitions.length ? { definitions: this._definitions } : {}),
|
|
1237
1305
|
inputs: this._inputs,
|
|
1238
1306
|
outputs: this._outputs,
|
|
1239
1307
|
vars: this._vars,
|
package/dist/merge.js
CHANGED
|
@@ -142,7 +142,8 @@ export function mergeFlow(original, edited, options = {}) {
|
|
|
142
142
|
finalNodes.push(withOriginalLayout(jsonClone(e), orig, layoutNodesOf(original)));
|
|
143
143
|
if (orig && orig.type === e.type && orig.typeVersion !== e.typeVersion) {
|
|
144
144
|
options.warn?.(`node ${JSON.stringify(e.id)} moved ${e.type}@${String(orig.typeVersion)} -> ${String(e.typeVersion)} `
|
|
145
|
-
+ `(the edited
|
|
145
|
+
+ `(the edited source does not pin ${String(orig.typeVersion)}; keep the { version } pin, `
|
|
146
|
+
+ `with .definitions([...]) when the SDK does not bundle that version).`);
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
}
|