@uipath/maestro-builder-sdk 6.25.0 → 6.26.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 +6 -6
- package/dist/api-members.md +39 -37
- package/dist/case/compile-cli.js +58 -2
- package/dist/check.js +36 -13
- 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 +117 -12
- package/dist/flow-node-type.d.ts +8 -3
- package/dist/flow-sdk.d.ts +48 -0
- package/dist/flow-sdk.js +49 -0
- package/dist/merge.js +2 -1
- package/dist/serialize.js +70 -26
- package/package.json +2 -2
|
@@ -48,3 +48,38 @@ export declare function asVersioned<Ctx extends ArtifactContexts>(t: AnyNodeType
|
|
|
48
48
|
* node type's bug, not the author's, and throws as an internal SDK error.
|
|
49
49
|
*/
|
|
50
50
|
export declare function resolveVersion<Spec, Ctx extends ArtifactContexts>(t: VersionedNodeType<Spec, unknown, Ctx>, spec: Spec, requested: string | undefined, where: string): NodeType<Spec, unknown, Ctx>;
|
|
51
|
+
/**
|
|
52
|
+
* Looks up a definition the flow carries (`.definitions()`) for `typeName@version`.
|
|
53
|
+
* `versions`, when present, lists every version of `typeName` the flow carries,
|
|
54
|
+
* so a refusal can name them.
|
|
55
|
+
*/
|
|
56
|
+
export type CarriedDefinitionLookup = ((typeName: string, version: string) => Record<string, unknown> | undefined) & {
|
|
57
|
+
readonly versions?: (typeName: string) => readonly string[];
|
|
58
|
+
};
|
|
59
|
+
/** What {@link resolveVersionCarried} resolved: the implementation, the version to emit, and a carried definition when one answered. */
|
|
60
|
+
export interface ResolvedNodeVersion<Impl> {
|
|
61
|
+
readonly impl: Impl;
|
|
62
|
+
/** The version the node is emitted at — `impl.version`, or the carried version. */
|
|
63
|
+
readonly version: string;
|
|
64
|
+
/** The definition the FLOW carries for `typeName@version`, when the registry has no implementation at that version. */
|
|
65
|
+
readonly carried?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* {@link resolveVersion} with the third definition source (#1128): a definition
|
|
69
|
+
* the flow CARRIES (`.definitions()`, decompile's copy of the file's own
|
|
70
|
+
* `definitions[]` entry).
|
|
71
|
+
*
|
|
72
|
+
* Order: a registered version resolves as before. An explicit version the type
|
|
73
|
+
* does not register, but the flow carries, is emitted AT that version with the
|
|
74
|
+
* carried definition, through the implementation the type would use unpinned
|
|
75
|
+
* (`select(spec)`, else the default) — the registry has no implementation of
|
|
76
|
+
* its own for it, and the node keeps the version the file pins instead of being
|
|
77
|
+
* refused. Anything else is refused as {@link resolveVersion} refuses it, except
|
|
78
|
+
* that an unregistered pin also names the versions the flow carries and the
|
|
79
|
+
* `.definitions()` remedy.
|
|
80
|
+
*
|
|
81
|
+
* For instance a file pinning `core.action.script@1.0` (registered: 1.0.0, 1.1)
|
|
82
|
+
* and carrying that definition emits the node at 1.0 through the default
|
|
83
|
+
* implementation.
|
|
84
|
+
*/
|
|
85
|
+
export declare function resolveVersionCarried<Spec, Ctx extends ArtifactContexts>(t: VersionedNodeType<Spec, unknown, Ctx>, spec: Spec, requested: string | undefined, where: string, carried: CarriedDefinitionLookup | undefined): ResolvedNodeVersion<NodeType<Spec, unknown, Ctx>>;
|
|
@@ -44,3 +44,38 @@ export function resolveVersion(t, spec, requested, where) {
|
|
|
44
44
|
}
|
|
45
45
|
return impl;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* {@link resolveVersion} with the third definition source (#1128): a definition
|
|
49
|
+
* the flow CARRIES (`.definitions()`, decompile's copy of the file's own
|
|
50
|
+
* `definitions[]` entry).
|
|
51
|
+
*
|
|
52
|
+
* Order: a registered version resolves as before. An explicit version the type
|
|
53
|
+
* does not register, but the flow carries, is emitted AT that version with the
|
|
54
|
+
* carried definition, through the implementation the type would use unpinned
|
|
55
|
+
* (`select(spec)`, else the default) — the registry has no implementation of
|
|
56
|
+
* its own for it, and the node keeps the version the file pins instead of being
|
|
57
|
+
* refused. Anything else is refused as {@link resolveVersion} refuses it, except
|
|
58
|
+
* that an unregistered pin also names the versions the flow carries and the
|
|
59
|
+
* `.definitions()` remedy.
|
|
60
|
+
*
|
|
61
|
+
* For instance a file pinning `core.action.script@1.0` (registered: 1.0.0, 1.1)
|
|
62
|
+
* and carrying that definition emits the node at 1.0 through the default
|
|
63
|
+
* implementation.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveVersionCarried(t, spec, requested, where, carried) {
|
|
66
|
+
if (requested !== undefined && !ownEntry(t.versions, requested)) {
|
|
67
|
+
const def = carried?.(t.typeName, requested);
|
|
68
|
+
if (def !== undefined) {
|
|
69
|
+
return { impl: resolveVersion(t, spec, undefined, where), version: requested, carried: def };
|
|
70
|
+
}
|
|
71
|
+
// Refused: name both sources, and the way to add the second one.
|
|
72
|
+
const registered = Object.keys(t.versions).sort().join(', ');
|
|
73
|
+
const carriedVersions = [...(carried?.versions?.(t.typeName) ?? [])].sort();
|
|
74
|
+
throw new Error(`${where} requests ${t.typeName}@${requested}, but this SDK registers only ${registered}`
|
|
75
|
+
+ (carriedVersions.length > 0 ? ` and the flow carries only ${carriedVersions.join(', ')}` : '')
|
|
76
|
+
+ '. Versions are exact — omit { version } to use the default, request one listed here, or carry '
|
|
77
|
+
+ "that version's definition (the .flow file's definitions[] entry) with .definitions([...]).");
|
|
78
|
+
}
|
|
79
|
+
const impl = resolveVersion(t, spec, requested, where);
|
|
80
|
+
return { impl, version: impl.version };
|
|
81
|
+
}
|
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);
|
|
@@ -2923,6 +3024,7 @@ export function decompile(flow, options = {}) {
|
|
|
2923
3024
|
if (!runDefs.has(d.nodeType))
|
|
2924
3025
|
runDefs.set(d.nodeType, d);
|
|
2925
3026
|
}
|
|
3027
|
+
runCarried = carriedPairs(flow);
|
|
2926
3028
|
const imp = new Imports();
|
|
2927
3029
|
const graph = new Graph(flow.nodes ?? [], flow.edges ?? []);
|
|
2928
3030
|
const globals = readFlowGlobals(flow.variables, runStrict);
|
|
@@ -2956,6 +3058,8 @@ export function decompile(flow, options = {}) {
|
|
|
2956
3058
|
if (typeof flow.description === 'string') {
|
|
2957
3059
|
e.line(d, `.description(${tsLiteral(flow.description)})`);
|
|
2958
3060
|
}
|
|
3061
|
+
if (runCarried.size > 0)
|
|
3062
|
+
e.line(d, `.definitions([${[...runCarried.values()].join(', ')}])`);
|
|
2959
3063
|
// Trigger nodes. One root is the default: the isDefaultEntryPoint carrier
|
|
2960
3064
|
// when the file marks one (a multi-root flow always does), else `start`.
|
|
2961
3065
|
// A node's TYPE is not enough: `uipath.connector.event.<key>.<event>` is a
|
|
@@ -3042,7 +3146,8 @@ export function decompile(flow, options = {}) {
|
|
|
3042
3146
|
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
3147
|
const layout = lay ? reindent(lay, ' ') : '';
|
|
3044
3148
|
const chain = [header, eps, body, sticky, layout, ' .build();'].filter((s) => s.length > 0).join('\n');
|
|
3045
|
-
const
|
|
3149
|
+
const carriedConsts = [...runCarried].map(([key, name]) => [name, JSON.stringify(runDefs.get(key), null, 2)]);
|
|
3150
|
+
const hoisted = [...carriedConsts, ...runRawManifests]
|
|
3046
3151
|
.map(([name, json]) => `const ${name} = ${json};`)
|
|
3047
3152
|
.join('\n\n');
|
|
3048
3153
|
return [imp.line(importSpecifier), hoisted, chain].filter((p) => p.length > 0).join('\n\n') + '\n';
|
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
|
@@ -731,6 +731,12 @@ export interface BuiltFlow {
|
|
|
731
731
|
layout?: FlowLayout;
|
|
732
732
|
/** Canvas sticky notes from `.stickyNotes()`. Inert annotations. */
|
|
733
733
|
stickyNotes?: StickyNote[];
|
|
734
|
+
/**
|
|
735
|
+
* Node definitions the flow carries, from `.definitions()`: exact manifests a
|
|
736
|
+
* pinned `{ version }` resolves against when the SDK does not bundle that
|
|
737
|
+
* version.
|
|
738
|
+
*/
|
|
739
|
+
definitions?: Record<string, unknown>[];
|
|
734
740
|
/** Additional roots from `.entryPoint()`. The default root stays `.trigger()`/`.input()`. */
|
|
735
741
|
entryPoints?: BuiltEntryPoint[];
|
|
736
742
|
/**
|
|
@@ -1216,6 +1222,7 @@ declare class FlowBuilder extends StepList {
|
|
|
1216
1222
|
private _stickyNotes?;
|
|
1217
1223
|
private _version;
|
|
1218
1224
|
private _schemaVersion?;
|
|
1225
|
+
private _definitions;
|
|
1219
1226
|
private _inputs;
|
|
1220
1227
|
private _outputs;
|
|
1221
1228
|
private _vars;
|
|
@@ -1423,6 +1430,47 @@ declare class FlowBuilder extends StepList {
|
|
|
1423
1430
|
* @defaultValue `'1.9'` — `FLOW_FORMAT_PROFILE.version`, the fleet write floor.
|
|
1424
1431
|
*/
|
|
1425
1432
|
schemaVersion(version: string): this;
|
|
1433
|
+
/**
|
|
1434
|
+
* Carry node definitions this SDK does not bundle, so a `{ version }` pin on
|
|
1435
|
+
* one of those versions compiles.
|
|
1436
|
+
*
|
|
1437
|
+
* @remarks
|
|
1438
|
+
* A pinned `{ version }` resolves exactly (design §2.6): first against the
|
|
1439
|
+
* definitions the SDK was released with, then against the ones carried here.
|
|
1440
|
+
* Nothing else changes — an unpinned node still takes the SDK's default, and
|
|
1441
|
+
* a bundled version still compiles from the bundle.
|
|
1442
|
+
*
|
|
1443
|
+
* Its job is BROWNFIELD PRESERVATION. A `.flow` file holds the exact
|
|
1444
|
+
* definition of every node it uses, but the registry moves faster than an SDK
|
|
1445
|
+
* release: `uip maestro flow node add` writes
|
|
1446
|
+
* `uipath.human-in-the-loop.quick-form@1.6` while this SDK bundles 1.0 and
|
|
1447
|
+
* 1.1. `flow-decompile` copies the file's own entry into this call for each
|
|
1448
|
+
* such pair, so the decompiled source compiles unedited and a node you edit
|
|
1449
|
+
* keeps the file's version instead of moving to the SDK's.
|
|
1450
|
+
*
|
|
1451
|
+
* Each manifest must be an exact copy of one the platform served (from the
|
|
1452
|
+
* file's `definitions[]`, or `uip maestro flow registry get`): it names its
|
|
1453
|
+
* `nodeType` and `version`, and has a `display.label`. The SDK still emits
|
|
1454
|
+
* the node's inputs from its own factory, so a version whose inputs differ
|
|
1455
|
+
* from the SDK's shape needs `rawNode` instead.
|
|
1456
|
+
*
|
|
1457
|
+
* @example
|
|
1458
|
+
* **Keep a quick-form task at the version the file pins**
|
|
1459
|
+
* ```ts
|
|
1460
|
+
* // The .flow file's whole definitions[] entry for quick-form@1.6 (or
|
|
1461
|
+
* // `uip maestro flow registry get`), copied verbatim, as flow-decompile hoists it.
|
|
1462
|
+
* // A stub with only nodeType/version/display passes validation but breaks the canvas.
|
|
1463
|
+
* declare const quickFormV1_6Definition: Record<string, unknown>;
|
|
1464
|
+
* flow('review')
|
|
1465
|
+
* .definitions([quickFormV1_6Definition])
|
|
1466
|
+
* .step('review', hitl({ variant: 'quick-form', fields: [], outcomes: ['Submit'] }), { version: '1.6' });
|
|
1467
|
+
* ```
|
|
1468
|
+
*
|
|
1469
|
+
* @param manifests - Node manifests, each with its own `nodeType`, `version` and `display.label`.
|
|
1470
|
+
* @returns This builder, so calls chain.
|
|
1471
|
+
* @throws Error when a manifest is not a node manifest, or two differ for one `nodeType@version`.
|
|
1472
|
+
*/
|
|
1473
|
+
definitions(manifests: readonly Record<string, unknown>[]): this;
|
|
1426
1474
|
/**
|
|
1427
1475
|
* Declare the flow's inputs. Read them with `input('<name>')`.
|
|
1428
1476
|
*
|
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
|
/**
|
|
@@ -881,6 +882,7 @@ class FlowBuilder extends StepList {
|
|
|
881
882
|
_stickyNotes;
|
|
882
883
|
_version = '1.0.0';
|
|
883
884
|
_schemaVersion;
|
|
885
|
+
_definitions = [];
|
|
884
886
|
_inputs = [];
|
|
885
887
|
_outputs = [];
|
|
886
888
|
_vars = [];
|
|
@@ -1144,6 +1146,52 @@ class FlowBuilder extends StepList {
|
|
|
1144
1146
|
this._schemaVersion = version;
|
|
1145
1147
|
return this;
|
|
1146
1148
|
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Carry node definitions this SDK does not bundle, so a `{ version }` pin on
|
|
1151
|
+
* one of those versions compiles.
|
|
1152
|
+
*
|
|
1153
|
+
* @remarks
|
|
1154
|
+
* A pinned `{ version }` resolves exactly (design §2.6): first against the
|
|
1155
|
+
* definitions the SDK was released with, then against the ones carried here.
|
|
1156
|
+
* Nothing else changes — an unpinned node still takes the SDK's default, and
|
|
1157
|
+
* a bundled version still compiles from the bundle.
|
|
1158
|
+
*
|
|
1159
|
+
* Its job is BROWNFIELD PRESERVATION. A `.flow` file holds the exact
|
|
1160
|
+
* definition of every node it uses, but the registry moves faster than an SDK
|
|
1161
|
+
* release: `uip maestro flow node add` writes
|
|
1162
|
+
* `uipath.human-in-the-loop.quick-form@1.6` while this SDK bundles 1.0 and
|
|
1163
|
+
* 1.1. `flow-decompile` copies the file's own entry into this call for each
|
|
1164
|
+
* such pair, so the decompiled source compiles unedited and a node you edit
|
|
1165
|
+
* keeps the file's version instead of moving to the SDK's.
|
|
1166
|
+
*
|
|
1167
|
+
* Each manifest must be an exact copy of one the platform served (from the
|
|
1168
|
+
* file's `definitions[]`, or `uip maestro flow registry get`): it names its
|
|
1169
|
+
* `nodeType` and `version`, and has a `display.label`. The SDK still emits
|
|
1170
|
+
* the node's inputs from its own factory, so a version whose inputs differ
|
|
1171
|
+
* from the SDK's shape needs `rawNode` instead.
|
|
1172
|
+
*
|
|
1173
|
+
* @example
|
|
1174
|
+
* **Keep a quick-form task at the version the file pins**
|
|
1175
|
+
* ```ts
|
|
1176
|
+
* // The .flow file's whole definitions[] entry for quick-form@1.6 (or
|
|
1177
|
+
* // `uip maestro flow registry get`), copied verbatim, as flow-decompile hoists it.
|
|
1178
|
+
* // A stub with only nodeType/version/display passes validation but breaks the canvas.
|
|
1179
|
+
* declare const quickFormV1_6Definition: Record<string, unknown>;
|
|
1180
|
+
* flow('review')
|
|
1181
|
+
* .definitions([quickFormV1_6Definition])
|
|
1182
|
+
* .step('review', hitl({ variant: 'quick-form', fields: [], outcomes: ['Submit'] }), { version: '1.6' });
|
|
1183
|
+
* ```
|
|
1184
|
+
*
|
|
1185
|
+
* @param manifests - Node manifests, each with its own `nodeType`, `version` and `display.label`.
|
|
1186
|
+
* @returns This builder, so calls chain.
|
|
1187
|
+
* @throws Error when a manifest is not a node manifest, or two differ for one `nodeType@version`.
|
|
1188
|
+
*/
|
|
1189
|
+
definitions(manifests) {
|
|
1190
|
+
const known = indexCarriedDefinitions(this._definitions, 'definitions()');
|
|
1191
|
+
indexCarriedDefinitions(manifests, 'definitions()', known);
|
|
1192
|
+
this._definitions = [...known.values()];
|
|
1193
|
+
return this;
|
|
1194
|
+
}
|
|
1147
1195
|
/**
|
|
1148
1196
|
* Declare the flow's inputs. Read them with `input('<name>')`.
|
|
1149
1197
|
*
|
|
@@ -1234,6 +1282,7 @@ class FlowBuilder extends StepList {
|
|
|
1234
1282
|
...(this._stickyNotes !== undefined ? { stickyNotes: this._stickyNotes } : {}),
|
|
1235
1283
|
version: this._version,
|
|
1236
1284
|
...(this._schemaVersion !== undefined ? { schemaVersion: this._schemaVersion } : {}),
|
|
1285
|
+
...(this._definitions.length ? { definitions: this._definitions } : {}),
|
|
1237
1286
|
inputs: this._inputs,
|
|
1238
1287
|
outputs: this._outputs,
|
|
1239
1288
|
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
|
}
|
package/dist/serialize.js
CHANGED
|
@@ -19,7 +19,8 @@ import { planContributionBindings } from './core/contribution-bindings.js';
|
|
|
19
19
|
import { declaredErrorPortRefusal, definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
|
|
20
20
|
import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
21
21
|
import { stepExits } from './flow-step-exits.js';
|
|
22
|
-
import {
|
|
22
|
+
import { resolveVersionCarried } from './core/nodes/versioned.js';
|
|
23
|
+
import { indexCarriedDefinitions } from './core/carried-definitions.js';
|
|
23
24
|
import { FORM_FIELD_JSON_TYPE } from './core/form-field-type.js';
|
|
24
25
|
import { connectionConnectors } from './bindings-v2.js';
|
|
25
26
|
import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
|
|
@@ -2271,8 +2272,8 @@ function declaresMemoryHandle(def) {
|
|
|
2271
2272
|
* compiles. The corpus also carries 1.1 and 1.3; `shared/sample-flows/
|
|
2272
2273
|
* inline-agent.flow` says 1.0, which appears in none of the 29.
|
|
2273
2274
|
*/
|
|
2274
|
-
function inlineAgentDef(version, withMemory) {
|
|
2275
|
-
const bundled =
|
|
2275
|
+
function inlineAgentDef(version, withMemory, resolve = (t, v) => DEFS[`${t}@${v}`]) {
|
|
2276
|
+
const bundled = resolve(INLINE_AGENT_TYPE, version);
|
|
2276
2277
|
if (!bundled)
|
|
2277
2278
|
return undefined;
|
|
2278
2279
|
const def = jsonClone(bundled);
|
|
@@ -4240,6 +4241,17 @@ function stepNamesOf(steps, out = []) {
|
|
|
4240
4241
|
return out;
|
|
4241
4242
|
}
|
|
4242
4243
|
export function serialize(built, opts = {}) {
|
|
4244
|
+
/**
|
|
4245
|
+
* Definitions the flow carries (`.definitions()`, #1128), by `type@version` —
|
|
4246
|
+
* the third source a pinned `{ version }` resolves from, after the bundle and
|
|
4247
|
+
* the node-type registry. A flow that carries nothing compiles exactly as
|
|
4248
|
+
* before. A subflow body sees its own carried definitions plus its
|
|
4249
|
+
* ancestors' (as `check` does), only while it is emitted.
|
|
4250
|
+
*/
|
|
4251
|
+
let carriedDefs = indexCarriedDefinitions(built.definitions ?? [], `flow "${built.id}" .definitions()`);
|
|
4252
|
+
const carriedFor = Object.assign((type, version) => carriedDefs.get(`${type}@${version}`), { versions: (type) => [...carriedDefs.keys()].filter((k) => k.startsWith(`${type}@`)).map((k) => k.slice(type.length + 1)) });
|
|
4253
|
+
/** The exact definition of `type@version`: bundled, else carried, else `undefined`. */
|
|
4254
|
+
const definitionFor = (type, version) => DEFS[`${type}@${version}`] ?? carriedFor(type, version);
|
|
4243
4255
|
/**
|
|
4244
4256
|
* The `definitions[]` a flow carries, keyed by `type@version` — NOT by type.
|
|
4245
4257
|
* One flow can legitimately hold two versions of the same family (a plain
|
|
@@ -4451,19 +4463,22 @@ export function serialize(built, opts = {}) {
|
|
|
4451
4463
|
function makeNode(type, id, label, o = {}) {
|
|
4452
4464
|
let def = o.def ?? defByType.get(type);
|
|
4453
4465
|
if (o.requestedVersion !== undefined && def?.version !== o.requestedVersion) {
|
|
4454
|
-
const exact = o.def ? undefined :
|
|
4466
|
+
const exact = o.def ? undefined : definitionFor(type, o.requestedVersion);
|
|
4455
4467
|
if (!exact) {
|
|
4456
|
-
// Name every version the SDK could have resolved: every bundled
|
|
4457
|
-
// version of the type, else (a type built per resource, a
|
|
4458
|
-
// the definition in hand. Naming only the default sent the
|
|
4459
|
-
// looking for a version that was shipped all along (#1128:
|
|
4468
|
+
// Name every version the SDK could have resolved: every bundled or
|
|
4469
|
+
// carried version of the type, else (a type built per resource, a
|
|
4470
|
+
// connector) the definition in hand. Naming only the default sent the
|
|
4471
|
+
// reader looking for a version that was shipped all along (#1128:
|
|
4460
4472
|
// quick-form bundles 1.0 AND 1.1, and the message said 1.0).
|
|
4461
|
-
const
|
|
4462
|
-
const
|
|
4473
|
+
const prefix = `${type}@`;
|
|
4474
|
+
const carriedVersions = [...carriedDefs.keys()].filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
|
|
4475
|
+
const known = o.def ? [] : [...bundledVersions(type), ...carriedVersions];
|
|
4476
|
+
const resolvable = known.length > 0 ? known
|
|
4463
4477
|
: [def?.version].filter((v) => typeof v === 'string');
|
|
4464
4478
|
throw new Error(`"${label ?? id}" requests ${type}@${o.requestedVersion}, but this SDK resolves only `
|
|
4465
4479
|
+ `${type}@${resolvable.join(', ') || '<none>'}. Versions are exact — omit { version } to use `
|
|
4466
|
-
+ `the pinned default,
|
|
4480
|
+
+ `the pinned default, request a version this SDK ships, or carry that version's definition `
|
|
4481
|
+
+ `(the .flow file's definitions[] entry) with .definitions([...]).`);
|
|
4467
4482
|
}
|
|
4468
4483
|
def = exact;
|
|
4469
4484
|
}
|
|
@@ -4532,6 +4547,16 @@ export function serialize(built, opts = {}) {
|
|
|
4532
4547
|
* a null. It is emitted here, always, so it cannot be forgotten.
|
|
4533
4548
|
*/
|
|
4534
4549
|
function emitSubflowBody(child, nodeId) {
|
|
4550
|
+
const outer = carriedDefs;
|
|
4551
|
+
carriedDefs = indexCarriedDefinitions(child.definitions ?? [], `subflow "${nodeId}" .definitions()`, new Map(outer));
|
|
4552
|
+
try {
|
|
4553
|
+
return emitSubflowBodyIn(child, nodeId);
|
|
4554
|
+
}
|
|
4555
|
+
finally {
|
|
4556
|
+
carriedDefs = outer;
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4559
|
+
function emitSubflowBodyIn(child, nodeId) {
|
|
4535
4560
|
const childGlobals = emittableGlobals(child, `subflow "${nodeId}"`, 'subflow');
|
|
4536
4561
|
const scope = newScope(childGlobals.outputs, childGlobals.vars, childGlobals.backing, childGlobals.backingReads);
|
|
4537
4562
|
// Claim this copy's node ids UP FRONT, so the rename map is complete before
|
|
@@ -4807,7 +4832,11 @@ export function serialize(built, opts = {}) {
|
|
|
4807
4832
|
function emitRegistered(entry, step, tails, scope, parentId) {
|
|
4808
4833
|
const where = `step "${step.name}"`;
|
|
4809
4834
|
const spec = step.spec;
|
|
4810
|
-
|
|
4835
|
+
// A pin the registry has no implementation for, but the flow carries, is
|
|
4836
|
+
// emitted at that version with the carried definition (#1128).
|
|
4837
|
+
const resolved = resolveVersionCarried(entry.type, spec, step.options?.version, where, carriedFor);
|
|
4838
|
+
const impl = resolved.impl;
|
|
4839
|
+
const version = resolved.version;
|
|
4811
4840
|
const id = stepUid(scope, step.name);
|
|
4812
4841
|
// As for every typed family: record the failure-envelope boundary before any
|
|
4813
4842
|
// expression renders, so a handler reading this step resolves correctly.
|
|
@@ -4817,14 +4846,19 @@ export function serialize(built, opts = {}) {
|
|
|
4817
4846
|
step: step.name,
|
|
4818
4847
|
nodeId: id,
|
|
4819
4848
|
flowId: built.id,
|
|
4820
|
-
version
|
|
4849
|
+
version,
|
|
4821
4850
|
pinnedVersion: step.options?.version,
|
|
4822
4851
|
render: (value) => renderValue(value, scope.rename),
|
|
4823
4852
|
renameRefs: (js) => renameRefs(js, scope.rename),
|
|
4824
4853
|
stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
|
|
4825
4854
|
mint: (seed) => stableId(`${built.id}:${id}:${seed}`),
|
|
4826
4855
|
bind: (b) => { bound.push(b); },
|
|
4827
|
-
|
|
4856
|
+
// At a carried version the node's own definition IS the carried one,
|
|
4857
|
+
// whatever version the implementation asks for (it was written for a
|
|
4858
|
+
// bundled one), so its defaults and outputs come from the file's entry.
|
|
4859
|
+
definition: (typeName, v) => (resolved.carried !== undefined && typeName === impl.typeName
|
|
4860
|
+
? resolved.carried
|
|
4861
|
+
: bundledDefinition(typeName, v) ?? carriedFor(typeName, v)),
|
|
4828
4862
|
bindings: resourceBindings,
|
|
4829
4863
|
resolveBinding: (name, attribute, at) => resolveBinding(opts, name, attribute, at),
|
|
4830
4864
|
bindConnection: (c) => {
|
|
@@ -4843,26 +4877,31 @@ export function serialize(built, opts = {}) {
|
|
|
4843
4877
|
// The definition contract, both halves: a per-tenant type supplies its
|
|
4844
4878
|
// definition, and a bundled type does not (its definition is the bundle's, and
|
|
4845
4879
|
// makeNode records it, so the error-edge refusal and check ask the same one).
|
|
4846
|
-
let def;
|
|
4880
|
+
let def = resolved.carried;
|
|
4847
4881
|
if (impl.definition === 'bundled' && contribution.definition !== undefined) {
|
|
4848
|
-
throw new Error(`internal SDK error: ${where}: bundled node type ${impl.typeName}@${
|
|
4882
|
+
throw new Error(`internal SDK error: ${where}: bundled node type ${impl.typeName}@${version} returned a definition; `
|
|
4849
4883
|
+ 'the bundle owns it — omit `definition`, or declare the type per-tenant.');
|
|
4850
4884
|
}
|
|
4885
|
+
// A per-tenant type builds its definition from `ctx.definition(type, version)`,
|
|
4886
|
+
// which answers with the carried definition at a carried version, so the
|
|
4887
|
+
// contribution's own definition wins: it is the carried template plus this
|
|
4888
|
+
// instance's edits (a queue step's own resourceKey, for instance). Only a type
|
|
4889
|
+
// that supplies none at a carried version takes the carried definition as is.
|
|
4851
4890
|
if (contribution.definition !== undefined) {
|
|
4852
|
-
def = resolveContributionDefinition(contribution.definition,
|
|
4891
|
+
def = resolveContributionDefinition(contribution.definition, version, where);
|
|
4853
4892
|
}
|
|
4854
|
-
else if (impl.definition === 'per-tenant') {
|
|
4855
|
-
throw new Error(`internal SDK error: ${where}: per-tenant node type ${impl.typeName}@${
|
|
4893
|
+
else if (impl.definition === 'per-tenant' && resolved.carried === undefined) {
|
|
4894
|
+
throw new Error(`internal SDK error: ${where}: per-tenant node type ${impl.typeName}@${version} returned no definition.`);
|
|
4856
4895
|
}
|
|
4857
4896
|
if (impl.definition === 'per-tenant') {
|
|
4858
4897
|
const port = typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
|
|
4859
4898
|
// The declaration answers for check, which has no definition; here the
|
|
4860
4899
|
// definition is in hand, and the two must agree (#1011).
|
|
4861
4900
|
if (port && def !== undefined && !definitionHasErrorPort(def)) {
|
|
4862
|
-
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${
|
|
4901
|
+
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${version} declares an error port, `
|
|
4863
4902
|
+ 'but the definition it supplied has no error handle and no supportsErrorHandling.');
|
|
4864
4903
|
}
|
|
4865
|
-
declaredErrorPorts.set(id, { port, typeName: impl.typeName, version
|
|
4904
|
+
declaredErrorPorts.set(id, { port, typeName: impl.typeName, version });
|
|
4866
4905
|
}
|
|
4867
4906
|
// A forward port is a handle the definition declares, one of the type's
|
|
4868
4907
|
// author-named exits, or one the product derives (`derivesPort`: a human
|
|
@@ -4871,13 +4910,17 @@ export function serialize(built, opts = {}) {
|
|
|
4871
4910
|
const exitPorts = new Set(exits && hasExits(exits) ? exits.map((e) => e.port) : []);
|
|
4872
4911
|
for (const p of [...(contribution.port !== undefined ? [contribution.port] : []), ...(contribution.fanOut ?? []).map((f) => f.port)]) {
|
|
4873
4912
|
if (exitPorts.has(p) || impl.derivesPort?.(spec, p)
|
|
4874
|
-
|| declaresForwardPort(def ?? bundledDefinition(impl.typeName,
|
|
4913
|
+
|| declaresForwardPort(def ?? bundledDefinition(impl.typeName, version), p))
|
|
4875
4914
|
continue;
|
|
4876
|
-
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${
|
|
4915
|
+
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${version} leaves by port "${p}", `
|
|
4877
4916
|
+ 'which its definition declares no source handle for, and the type neither lists as an exit nor derives.');
|
|
4878
4917
|
}
|
|
4918
|
+
// A carried definition answers the error-edge refusal as a bundled one does
|
|
4919
|
+
// (makeNode records only the definitions it resolved itself).
|
|
4920
|
+
if (resolved.carried !== undefined)
|
|
4921
|
+
bundledNodeDefs.set(id, def ?? resolved.carried);
|
|
4879
4922
|
return placeStepNode({
|
|
4880
|
-
where, stepName: step.name, id, typeName: impl.typeName, requestedVersion:
|
|
4923
|
+
where, stepName: step.name, id, typeName: impl.typeName, requestedVersion: version, def, contribution, bound, tails, scope, parentId,
|
|
4881
4924
|
trackOutcomes: true,
|
|
4882
4925
|
});
|
|
4883
4926
|
}
|
|
@@ -5041,11 +5084,12 @@ export function serialize(built, opts = {}) {
|
|
|
5041
5084
|
// agent attaches memory keeps the handle for all of them.
|
|
5042
5085
|
const withMemory = s.memory !== undefined
|
|
5043
5086
|
|| declaresMemoryHandle(emittedDefs.get(`${INLINE_AGENT_TYPE}@${version}`));
|
|
5044
|
-
const def = inlineAgentDef(version, withMemory);
|
|
5087
|
+
const def = inlineAgentDef(version, withMemory, definitionFor);
|
|
5045
5088
|
if (!def) {
|
|
5046
5089
|
throw new Error(`"${step.name}" requests ${INLINE_AGENT_TYPE}@${version}, but this SDK resolves only `
|
|
5047
5090
|
+ `${INLINE_AGENT_VERSIONS.map((v) => `${INLINE_AGENT_TYPE}@${v}`).join(', ')}. Versions are exact — `
|
|
5048
|
-
+ `omit { version } to use the default (${INLINE_AGENT_DEFAULT_VERSION}),
|
|
5091
|
+
+ `omit { version } to use the default (${INLINE_AGENT_DEFAULT_VERSION}), request a version this SDK ships, `
|
|
5092
|
+
+ `or carry that version's definition with .definitions([...]).`);
|
|
5049
5093
|
}
|
|
5050
5094
|
if (s.memory !== undefined && !declaresMemoryHandle(def)) {
|
|
5051
5095
|
throw new Error(`"${step.name}" attaches \`memory\`, but ${INLINE_AGENT_TYPE}@${version} declares no \`memory\` handle. `
|