@uipath/maestro-builder-sdk 6.49.2 → 6.50.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/check.d.ts +5 -0
- package/dist/check.js +50 -52
- package/dist/cli-library.js +8 -0
- package/dist/core/connector-inputs.d.ts +2 -0
- package/dist/core/connector-inputs.js +59 -2
- package/dist/core/nodes/node-type.d.ts +6 -1
- package/dist/core/nodes/versioned.d.ts +7 -0
- package/dist/core/nodes/versioned.js +11 -0
- package/dist/decompile.js +2 -65
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +59 -0
- package/dist/nodes/connector/flow/family.d.ts +3 -0
- package/dist/nodes/connector/flow/family.js +8 -0
- package/dist/nodes/connector/flow/node-type.d.ts +25 -0
- package/dist/nodes/connector/flow/node-type.js +294 -0
- package/dist/serialize.d.ts +2 -3
- package/dist/serialize.js +46 -182
- package/package.json +2 -2
package/dist/check.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export interface ListEnvelopeFields {
|
|
|
29
29
|
export interface CheckOpts extends ConnectorCheckOpts, EventCheckOpts {
|
|
30
30
|
/** Resolve the measured collection envelope for one connector operation. */
|
|
31
31
|
listEnvelope?: (connector: string, operation: string, version?: string, object?: string) => ListEnvelopeFields | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The library's definition for one connector operation, the one compile places:
|
|
34
|
+
* whether it grants an error port is then the same answer in both rungs.
|
|
35
|
+
*/
|
|
36
|
+
connectorDefinition?: (connector: string, operation: string, version?: string, object?: string) => unknown;
|
|
32
37
|
}
|
|
33
38
|
export declare function check(built: BuiltFlow, opts?: CheckOpts): Diagnostic[];
|
|
34
39
|
/**
|
package/dist/check.js
CHANGED
|
@@ -230,7 +230,6 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
|
|
|
230
230
|
// Step name → the read rule for its output, bound to its inputs. Filled from
|
|
231
231
|
// OUTPUT_READ_RULES for every kind whose inputs take a `returns`.
|
|
232
232
|
const outputReaders = new Map();
|
|
233
|
-
const connectorSteps = new Map();
|
|
234
233
|
// Subflow step name → the child flow it runs. A subflow's output is the
|
|
235
234
|
// OBJECT of the child's declared outputs, so a read of it is checked against
|
|
236
235
|
// that child rather than waved through (see SUBFLOW_* below).
|
|
@@ -346,7 +345,7 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
|
|
|
346
345
|
declared.add(v);
|
|
347
346
|
}
|
|
348
347
|
if ((s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port === 'error')
|
|
349
|
-
checkOnError(s, prevPortSource, diags, prevPortSource && checkedSteps.get(prevPortSource.name), carriedLookup([built, ...ancestors]));
|
|
348
|
+
checkOnError(s, prevPortSource, diags, prevPortSource && checkedSteps.get(prevPortSource.name), opts, carriedLookup([built, ...ancestors]));
|
|
350
349
|
if ((s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port.startsWith('branch-')) {
|
|
351
350
|
const want = s.port.slice('branch-'.length);
|
|
352
351
|
const prevSpec = prevAction?.spec;
|
|
@@ -465,12 +464,6 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
|
|
|
465
464
|
else {
|
|
466
465
|
collectActionExprs(s, exprs, errorSource);
|
|
467
466
|
}
|
|
468
|
-
if (s.spec.kind === 'connector') {
|
|
469
|
-
connectorSteps.set(s.name, s.spec);
|
|
470
|
-
checkConnectorLookups(s.name, s.spec, opts, diags);
|
|
471
|
-
checkConnectorSchema(s.name, s.spec, opts, diags);
|
|
472
|
-
checkConnectorContract(s.name, s.spec, opts, diags);
|
|
473
|
-
}
|
|
474
467
|
// A custom FlowAction subclass has no `kind`: the family rules below
|
|
475
468
|
// don't apply, and it reports through its own diagnostics() hook.
|
|
476
469
|
const maybeCustom = s.spec;
|
|
@@ -715,7 +708,7 @@ function checkFlow(built, ancestors, opts = {}, ledger = newResourceIdLedger())
|
|
|
715
708
|
// handle); it is not a legal source for anything else.
|
|
716
709
|
prevPortSource = s;
|
|
717
710
|
addExpr(s.collection.js, s.name);
|
|
718
|
-
checkLoopListEnvelope(s,
|
|
711
|
+
checkLoopListEnvelope(s, registeredSteps, opts, diags);
|
|
719
712
|
if (s.options?.completionCondition)
|
|
720
713
|
addExpr(s.options.completionCondition.js, s.name);
|
|
721
714
|
// The same refusal serialize throws, from the same helper: an option or
|
|
@@ -1377,16 +1370,18 @@ function checkConnectorContract(step, spec, opts, diags) {
|
|
|
1377
1370
|
coreCheckConnectorContract(where, spec, opts, report, step);
|
|
1378
1371
|
checkBindingPresence(`connector "${step}"`, spec, (code, message) => report({ level: 'error', code, message }), { key: spec.key, action: spec.action });
|
|
1379
1372
|
}
|
|
1380
|
-
/**
|
|
1381
|
-
|
|
1373
|
+
/**
|
|
1374
|
+
* Reject a wrapper hop when the runtime shape of a list has been measured: the
|
|
1375
|
+
* step the loop reads says what its list output looks like (`listShape`, a
|
|
1376
|
+
* connector operation's measured envelope).
|
|
1377
|
+
*/
|
|
1378
|
+
function checkLoopListEnvelope(loop, registered, opts, diags) {
|
|
1382
1379
|
const read = /^\$vars\.([^.]+)\.output\.([A-Za-z_$][\w$]*)$/.exec(loop.collection.js);
|
|
1383
|
-
if (!read
|
|
1380
|
+
if (!read)
|
|
1384
1381
|
return;
|
|
1385
1382
|
const [, step, field] = read;
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
return;
|
|
1389
|
-
const measured = opts.listEnvelope(connector.key, connector.action, connector.version, connector.object);
|
|
1383
|
+
const source = registered.get(step);
|
|
1384
|
+
const measured = source?.impl.listShape?.(source.spec, opts);
|
|
1390
1385
|
if (!measured || measured.envelope === field)
|
|
1391
1386
|
return;
|
|
1392
1387
|
const shape = measured.envelope === null
|
|
@@ -2723,6 +2718,12 @@ function checkRegisteredStep(entry, s, exprs, errorSource, opts, diags, typeOf,
|
|
|
2723
2718
|
},
|
|
2724
2719
|
options: opts,
|
|
2725
2720
|
bindingLabels: (where, labels, use, target) => checkBindingLabels(where, labels, opts, (code, message) => diags.push({ level: 'error', code, step: s.name, message }), target, use),
|
|
2721
|
+
connector: (c) => {
|
|
2722
|
+
const spec = c;
|
|
2723
|
+
checkConnectorLookups(s.name, spec, opts, diags);
|
|
2724
|
+
checkConnectorSchema(s.name, spec, opts, diags);
|
|
2725
|
+
checkConnectorContract(s.name, spec, opts, diags);
|
|
2726
|
+
},
|
|
2726
2727
|
resources: {
|
|
2727
2728
|
derivedNodeId: (id, what) => { if (!ledger.taken.has(id))
|
|
2728
2729
|
ledger.taken.set(id, what); },
|
|
@@ -2861,39 +2862,7 @@ function collectActionExprs(s, out, errorSource) {
|
|
|
2861
2862
|
push(val);
|
|
2862
2863
|
return;
|
|
2863
2864
|
}
|
|
2864
|
-
if (s.spec.kind === 'connector') {
|
|
2865
|
-
// Same gap on the connector surface: `messageToSend: tmpl`…${v('typo')}``
|
|
2866
|
-
// reached the runtime unchecked.
|
|
2867
|
-
pushDeep(s.spec.inputs);
|
|
2868
|
-
}
|
|
2869
2865
|
}
|
|
2870
|
-
/**
|
|
2871
|
-
* The families with no definition this SDK can ask, and an error port anyway:
|
|
2872
|
-
* an Integration Service connector activity's definition comes from the
|
|
2873
|
-
* connector library, and every connector activity declares the `error` handle.
|
|
2874
|
-
*
|
|
2875
|
-
* Every other family is decided by a definition, never by a list:
|
|
2876
|
-
*
|
|
2877
|
-
* - a bundled family by its bundled definition (`bundledActionNodeType` +
|
|
2878
|
-
* `bundledDefinition`, #1011: `documentClassify` was missing from the old
|
|
2879
|
-
* list although `uipath.document.classify@1.0` sets the flag);
|
|
2880
|
-
* - a per-tenant family by the definition serialize synthesizes for it: a
|
|
2881
|
-
* registered one (the process family, `agent` among it) through its node
|
|
2882
|
-
* type's `errorPort`, which asks that definition, and `ixpExtract` through
|
|
2883
|
-
* `perCallDefinition` (#1040: `publishedFunction` was refused here although
|
|
2884
|
-
* `publishedFunctionDef` sets the flag).
|
|
2885
|
-
*
|
|
2886
|
-
* `publishedFunction`'s flag is measured, not assumed: on 2026-10-08 four
|
|
2887
|
-
* `flow debug` jobs on alpha/codereval called `acme-echo` with a non-string
|
|
2888
|
-
* `message` (null, object, number, array). In each, the function job failed
|
|
2889
|
-
* (`Python.FUNCTION_EXECUTION_ERROR`, category User, status 502), the node's
|
|
2890
|
-
* error boundary fired, and the handler's `err('message')` read
|
|
2891
|
-
* "Failure in the Orchestrator Job".
|
|
2892
|
-
*
|
|
2893
|
-
* A kind in none of these (`waitEvent`) has no error port: an event wait
|
|
2894
|
-
* resumes or does not.
|
|
2895
|
-
*/
|
|
2896
|
-
const NO_DEFINITION_ERROR_PORT_KINDS = new Set(['connector']);
|
|
2897
2866
|
/**
|
|
2898
2867
|
* The ONERROR_NO_ERROR_PORT sentence for a typed step — a built-in family or a
|
|
2899
2868
|
* registered node type — whose node type has no error port. One spelling for
|
|
@@ -2913,10 +2882,33 @@ const ERROR_ENVELOPE_FIELDS = ['code', 'message', 'detail', 'category', 'status'
|
|
|
2913
2882
|
* `.onError(...)`-specific checks. The surface already refuses the structural
|
|
2914
2883
|
* mistakes at build time (no previous step, two handlers on one node, an empty
|
|
2915
2884
|
* handler); these are the ones only a checker can see.
|
|
2885
|
+
*
|
|
2886
|
+
* Whether the step has an error port at all: every family is decided by a
|
|
2887
|
+
* definition, never by a list:
|
|
2888
|
+
*
|
|
2889
|
+
* - a bundled family by its bundled definition (`bundledActionNodeType` +
|
|
2890
|
+
* `bundledDefinition`, #1011: `documentClassify` was missing from the old
|
|
2891
|
+
* list although `uipath.document.classify@1.0` sets the flag);
|
|
2892
|
+
* - a per-tenant family — every one is registered — through its node type's
|
|
2893
|
+
* `errorPort`, which asks the definition serialize synthesizes (#1040:
|
|
2894
|
+
* `publishedFunction` was refused here although `publishedFunctionDef` sets
|
|
2895
|
+
* the flag), or for a connector the library's definition, which check reads
|
|
2896
|
+
* through `connectorDefinition` and, without a library, assumes grants the
|
|
2897
|
+
* port, as every operation the library serves does.
|
|
2898
|
+
*
|
|
2899
|
+
* `publishedFunction`'s flag is measured, not assumed: on 2026-10-08 four
|
|
2900
|
+
* `flow debug` jobs on alpha/codereval called `acme-echo` with a non-string
|
|
2901
|
+
* `message` (null, object, number, array). In each, the function job failed
|
|
2902
|
+
* (`Python.FUNCTION_EXECUTION_ERROR`, category User, status 502), the node's
|
|
2903
|
+
* error boundary fired, and the handler's `err('message')` read
|
|
2904
|
+
* "Failure in the Orchestrator Job".
|
|
2905
|
+
*
|
|
2906
|
+
* A kind in none of these (`waitEvent`) has no error port: an event wait
|
|
2907
|
+
* resumes or does not.
|
|
2916
2908
|
*/
|
|
2917
2909
|
function checkOnError(_handler, step, diags,
|
|
2918
2910
|
/** The step's registry record, when its kind is registered and its version resolved. */
|
|
2919
|
-
placed, carried) {
|
|
2911
|
+
placed, opts, carried) {
|
|
2920
2912
|
// The builder refuses a handler with no eligible step before it, so a missing
|
|
2921
2913
|
// source here means a hand-built `BuiltFlow`; there is nothing to say about it.
|
|
2922
2914
|
if (!step)
|
|
@@ -2986,11 +2978,17 @@ placed, carried) {
|
|
|
2986
2978
|
}
|
|
2987
2979
|
}
|
|
2988
2980
|
else if (impl.definition === 'per-tenant') {
|
|
2989
|
-
|
|
2981
|
+
// The definition serialize will be supplied, when check's options can name
|
|
2982
|
+
// it (a connector's, from the library); the type answers without it otherwise.
|
|
2983
|
+
const def = impl.checkDefinition?.(placed.spec, opts);
|
|
2984
|
+
const port = typeof impl.errorPort === 'function' ? impl.errorPort(spec, def) : impl.errorPort === true;
|
|
2990
2985
|
if (!port) {
|
|
2986
|
+
const named = def !== undefined && typeof def.nodeType === 'string' && typeof def.version === 'string';
|
|
2991
2987
|
diags.push({
|
|
2992
2988
|
level: 'error', code: 'ONERROR_NO_ERROR_PORT', step: step.name,
|
|
2993
|
-
message:
|
|
2989
|
+
message: named
|
|
2990
|
+
? declaredErrorPortRefusal(step.name, def.nodeType, def.version)
|
|
2991
|
+
: declaredErrorPortRefusal(step.name, impl.typeName, impl.version),
|
|
2994
2992
|
});
|
|
2995
2993
|
return;
|
|
2996
2994
|
}
|
|
@@ -3025,7 +3023,7 @@ placed, carried) {
|
|
|
3025
3023
|
// a carried definition); what is left is bundled, or has no definition here.
|
|
3026
3024
|
const noPort = bundledDef !== undefined
|
|
3027
3025
|
? !definitionHasErrorPort(bundledDef)
|
|
3028
|
-
: !bundledType
|
|
3026
|
+
: !bundledType;
|
|
3029
3027
|
if (noPort) {
|
|
3030
3028
|
diags.push({
|
|
3031
3029
|
level: 'error',
|
package/dist/cli-library.js
CHANGED
|
@@ -25,5 +25,13 @@ export function flowCheckOpts(library, bindings) {
|
|
|
25
25
|
return undefined; // an unresolvable operation is connectorResolveError's to report
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
+
connectorDefinition: (connector, operation, version, object) => {
|
|
29
|
+
try {
|
|
30
|
+
return library.resolve(connector, operation, version, object).v1def;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined; // as above
|
|
34
|
+
}
|
|
35
|
+
},
|
|
28
36
|
};
|
|
29
37
|
}
|
|
@@ -46,6 +46,8 @@ export interface ConnectorInputOpts {
|
|
|
46
46
|
}
|
|
47
47
|
/** Flow's dialect, re-exported so `serialize.ts` need not reach past this module. */
|
|
48
48
|
export { FLOW_EXPR_DIALECT } from './connector-checks.js';
|
|
49
|
+
/** A required connector input left empty — the one draft code with no error class of its own. */
|
|
50
|
+
export declare const CONNECTOR_INPUT_MISSING = "CONNECTOR_INPUT_MISSING";
|
|
49
51
|
/**
|
|
50
52
|
* What `compile` would refuse these connector inputs with, or `undefined` when it
|
|
51
53
|
* would accept them — the SAME code path `buildConnectorInputs` runs at emit time,
|
|
@@ -30,6 +30,8 @@ export { FLOW_EXPR_DIALECT } from './connector-checks.js';
|
|
|
30
30
|
import { FLOW_EXPR_DIALECT as FLOW_DIALECT } from './connector-checks.js';
|
|
31
31
|
import { ownEntry } from './own-entry.js';
|
|
32
32
|
import { isRecord } from './node-envelope.js';
|
|
33
|
+
/** A required connector input left empty — the one draft code with no error class of its own. */
|
|
34
|
+
export const CONNECTOR_INPUT_MISSING = 'CONNECTOR_INPUT_MISSING';
|
|
33
35
|
/**
|
|
34
36
|
* Validate a connector's activity inputs against its declared field list and
|
|
35
37
|
* return them ROUTED to the wire slots the platform reads. Catches the three
|
|
@@ -106,10 +108,12 @@ export function buildConnectorInputs(rc, inputs, opts) {
|
|
|
106
108
|
// container the runtime composes from `bodyParameters`, so it is listed without
|
|
107
109
|
// a value and is never an author input — see `readFields` in core/library.ts.
|
|
108
110
|
const partValues = new Map();
|
|
111
|
+
const bare = bareArraySpellings(fields, byName);
|
|
109
112
|
for (const f of fields) {
|
|
110
|
-
const
|
|
113
|
+
const flatKey = flatInputKey(inputs, f.name, bare, dialect);
|
|
114
|
+
const flat = flatKey !== undefined;
|
|
111
115
|
let nested = flat
|
|
112
|
-
? { found: true, value: inputs[
|
|
116
|
+
? { found: true, value: inputs[flatKey], projectedArrays: 0 }
|
|
113
117
|
: readConnectorPath(inputs, f.name, dialect);
|
|
114
118
|
if (!nested.found) {
|
|
115
119
|
// A required LOOKUP field is missing far more often because the author does
|
|
@@ -326,6 +330,51 @@ function place(target, parts, index, value) {
|
|
|
326
330
|
target[part.key] = next;
|
|
327
331
|
place(next, parts, index + 1, value);
|
|
328
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* The flat key the product writes for each array LEAF field, by its wire name:
|
|
335
|
+
* `fields.components_arrayRemap_name` for `fields.components_arrayRemap_name[*]`.
|
|
336
|
+
*
|
|
337
|
+
* Product-authored flows key `bodyParameters` either way (86 Slack
|
|
338
|
+
* `create-group-direct-message` nodes in the eval archive, almost all from the v1
|
|
339
|
+
* CLI arm, carry bare `users`; 85 carry `users[*]`; a Studio Web save in
|
|
340
|
+
* #1402 carried a bare Jira key), and the runtime reads both the same for an array
|
|
341
|
+
* value (flow-v1 `convertDotNotationToNested`). The nested reader already strips
|
|
342
|
+
* the marker, so `{ users: [...] }` compiled, but a DOTTED bare key reached only
|
|
343
|
+
* the exact flat lookup and was refused as unknown — so a designer-saved Jira
|
|
344
|
+
* flow decompiled to source that did not compile back (#1402).
|
|
345
|
+
*
|
|
346
|
+
* Only a trailing `[*]` with none before it: `contacts.id` is not `contacts[*].id`
|
|
347
|
+
* (the runtime nests it as an object, not a list). A bare name that is itself a
|
|
348
|
+
* declared field keeps its own meaning.
|
|
349
|
+
*/
|
|
350
|
+
function bareArraySpellings(fields, declared) {
|
|
351
|
+
const bare = new Map();
|
|
352
|
+
for (const f of fields) {
|
|
353
|
+
if (!f.name.endsWith('[*]'))
|
|
354
|
+
continue;
|
|
355
|
+
const key = f.name.slice(0, -3);
|
|
356
|
+
if (key.includes('[*]') || declared.has(key))
|
|
357
|
+
continue;
|
|
358
|
+
bare.set(f.name, key);
|
|
359
|
+
}
|
|
360
|
+
return bare;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* The flat key `inputs` carries field `name` under: its wire name, or its bare
|
|
364
|
+
* spelling when the value there is an array or opaque. A bare SCALAR is left to
|
|
365
|
+
* the nested reader, which refuses it ("expected array"), as before: under
|
|
366
|
+
* `users[*]` the runtime would wrap `"U1,U2"` into `["U1,U2"]`, a different
|
|
367
|
+
* request from the one the bare key sends.
|
|
368
|
+
*/
|
|
369
|
+
function flatInputKey(inputs, name, bare, dialect) {
|
|
370
|
+
if (Object.prototype.hasOwnProperty.call(inputs, name))
|
|
371
|
+
return name;
|
|
372
|
+
const key = bare.get(name);
|
|
373
|
+
if (key === undefined || !Object.prototype.hasOwnProperty.call(inputs, key))
|
|
374
|
+
return undefined;
|
|
375
|
+
const value = inputs[key];
|
|
376
|
+
return Array.isArray(value) || dialect.isOpaque(value) ? key : undefined;
|
|
377
|
+
}
|
|
329
378
|
function connectorPath(name) {
|
|
330
379
|
return name.split('.').map((raw) => ({
|
|
331
380
|
key: raw.endsWith('[*]') ? raw.slice(0, -3) : raw,
|
|
@@ -385,6 +434,7 @@ function buildConnectorInputTrie(fields) {
|
|
|
385
434
|
function validateNestedConnectorKeys(fields, inputs, exact, dialect) {
|
|
386
435
|
const root = buildConnectorInputTrie(fields);
|
|
387
436
|
const errs = [];
|
|
437
|
+
const bare = new Set(bareArraySpellings(fields, exact).values());
|
|
388
438
|
const visit = (value, node, path) => {
|
|
389
439
|
if (dialect.isOpaque(value))
|
|
390
440
|
return;
|
|
@@ -422,6 +472,13 @@ function validateNestedConnectorKeys(fields, inputs, exact, dialect) {
|
|
|
422
472
|
if (exact.has(key))
|
|
423
473
|
continue; // backwards-compatible flat authoring
|
|
424
474
|
const node = root.children.get(key);
|
|
475
|
+
// A dotted bare array key (`fields.labels` for `fields.labels[*]`); a
|
|
476
|
+
// top-level one (`users`) is a trie child and is checked by `visit` below.
|
|
477
|
+
if (!node && bare.has(key)) {
|
|
478
|
+
if (!Array.isArray(value) && !dialect.isOpaque(value))
|
|
479
|
+
errs.push(`input "${key}" expected array, got ${typeof value}`);
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
425
482
|
if (!node) {
|
|
426
483
|
const near = nearestField(key, [...exact.keys()]);
|
|
427
484
|
errs.push(`unknown input "${key}"${near ? ` — did you mean "${near}"?` : ''}`);
|
|
@@ -106,6 +106,8 @@ export interface FactoryCall {
|
|
|
106
106
|
readonly factory: string;
|
|
107
107
|
readonly positional?: readonly string[];
|
|
108
108
|
readonly options?: ReadonlyArray<readonly [name: string, source: string | undefined]>;
|
|
109
|
+
/** A note for the reader, written as a block comment after the call (the artifact escapes it). */
|
|
110
|
+
readonly comment?: string;
|
|
109
111
|
}
|
|
110
112
|
/** The contexts an artifact lends its node types, one per job. */
|
|
111
113
|
export interface ArtifactContexts {
|
|
@@ -131,8 +133,11 @@ export interface NodeType<Spec, Serialized, Ctx extends ArtifactContexts> {
|
|
|
131
133
|
* Whether the node has an error port. A `bundled` type must omit it — the answer
|
|
132
134
|
* is the definition's, at this version. A `per-tenant` type must declare it,
|
|
133
135
|
* because there is no definition to ask. The registry refuses either mistake.
|
|
136
|
+
* Serialize passes the definition the type supplied as `definition`, for a type
|
|
137
|
+
* whose definition comes from outside the SDK (a connector's, from its
|
|
138
|
+
* library); check, which has no definition, passes none.
|
|
134
139
|
*/
|
|
135
|
-
readonly errorPort?: boolean | ((spec: Spec) => boolean);
|
|
140
|
+
readonly errorPort?: boolean | ((spec: Spec, definition?: unknown) => boolean);
|
|
136
141
|
/** Spec → the node's type-owned fields. The artifact still owns id, label, layout, edges and dedupe. */
|
|
137
142
|
serialize(spec: Spec, ctx: Ctx['serialize']): Serialized;
|
|
138
143
|
/** The lift: one serialized node → the spec it came from, or `undefined` when it is not this type's. */
|
|
@@ -33,6 +33,13 @@ Impl extends NodeType<Spec, Serialized, Ctx> = NodeType<Spec, Serialized, Ctx>>
|
|
|
33
33
|
* exact name sit under the prefix as long as this rejects it.
|
|
34
34
|
*/
|
|
35
35
|
typeNameMatches?(typeName: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* One implementation answers for every version: the definition the type
|
|
38
|
+
* supplies names the node's version, as a connector operation's library entry
|
|
39
|
+
* does (1.0.0 for most, 4.0.0 for some). The registry then neither defaults nor
|
|
40
|
+
* refuses a `{ version }`; serialize holds the pin to that definition.
|
|
41
|
+
*/
|
|
42
|
+
readonly versionedByDefinition?: boolean;
|
|
36
43
|
readonly factory: string;
|
|
37
44
|
/** The version used when the author names none. Changing it is a breaking change. */
|
|
38
45
|
readonly defaultVersion: string;
|
|
@@ -25,6 +25,11 @@ export function asVersioned(t) {
|
|
|
25
25
|
* node type's bug, not the author's, and throws as an internal SDK error.
|
|
26
26
|
*/
|
|
27
27
|
export function resolveVersion(t, spec, requested, where) {
|
|
28
|
+
if (t.versionedByDefinition) {
|
|
29
|
+
const only = ownEntry(t.versions, t.defaultVersion);
|
|
30
|
+
if (only)
|
|
31
|
+
return only;
|
|
32
|
+
}
|
|
28
33
|
const selected = requested === undefined ? t.select?.(spec) : undefined;
|
|
29
34
|
const version = requested ?? selected ?? t.defaultVersion;
|
|
30
35
|
const impl = ownEntry(t.versions, version);
|
|
@@ -63,6 +68,12 @@ export function resolveVersion(t, spec, requested, where) {
|
|
|
63
68
|
* implementation.
|
|
64
69
|
*/
|
|
65
70
|
export function resolveVersionCarried(t, spec, requested, where, carried) {
|
|
71
|
+
// The definition names the version (see versionedByDefinition): a pin is the
|
|
72
|
+
// definition's to accept or refuse, not the registry's.
|
|
73
|
+
if (t.versionedByDefinition) {
|
|
74
|
+
const impl = resolveVersion(t, spec, undefined, where);
|
|
75
|
+
return { impl, version: requested ?? impl.version };
|
|
76
|
+
}
|
|
66
77
|
if (requested !== undefined && !ownEntry(t.versions, requested)) {
|
|
67
78
|
const def = carried?.(t.typeName, requested);
|
|
68
79
|
if (def !== undefined) {
|
package/dist/decompile.js
CHANGED
|
@@ -774,7 +774,8 @@ function emitRegisteredSpec(node, imp, inputNames, graph) {
|
|
|
774
774
|
};
|
|
775
775
|
const options = opts((call.options ?? []).map(([k, v]) => [k, v]));
|
|
776
776
|
const args = [...(call.positional ?? []), ...(options ? [options] : [])];
|
|
777
|
-
return `${imp.use(call.factory)}(${args.join(', ')})
|
|
777
|
+
return `${imp.use(call.factory)}(${args.join(', ')})`
|
|
778
|
+
+ (call.comment !== undefined ? ` /* ${commentText(call.comment)} */` : '');
|
|
778
779
|
}
|
|
779
780
|
/**
|
|
780
781
|
* Emit the action-spec argument for a `.step(id, <spec>)` call — e.g.
|
|
@@ -789,11 +790,6 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
|
|
|
789
790
|
return lifted;
|
|
790
791
|
switch (node.type) {
|
|
791
792
|
default:
|
|
792
|
-
// A connector action node: `uipath.connector.<key>.<action>` (but NOT the
|
|
793
|
-
// event nodes `uipath.connector.event.*` / `.trigger.*`, which are triggers).
|
|
794
|
-
if (node.type.startsWith('uipath.connector.') && !node.type.startsWith('uipath.connector.event.') && !node.type.startsWith('uipath.connector.trigger.')) {
|
|
795
|
-
return emitConnector(node, imp, inputNames, o);
|
|
796
|
-
}
|
|
797
793
|
// A mid-flow connector EVENT wait (`uipath.connector.event.<key>.<event>`
|
|
798
794
|
// placed as a step rather than as the start node).
|
|
799
795
|
if (node.type.startsWith('uipath.connector.event.')) {
|
|
@@ -809,30 +805,6 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
|
|
|
809
805
|
return unsupportedSpec(node, imp, o);
|
|
810
806
|
}
|
|
811
807
|
}
|
|
812
|
-
/**
|
|
813
|
-
* Split a connector `nodeType` (`uipath.connector.<key>.<action>`) into key +
|
|
814
|
-
* action. Mirrors `core/connectors.ts`'s `splitConnectorNodeType`.
|
|
815
|
-
*/
|
|
816
|
-
function splitConnector(nodeType) {
|
|
817
|
-
const rest = nodeType.replace(/^uipath\.connector\./, '');
|
|
818
|
-
const dot = rest.lastIndexOf('.');
|
|
819
|
-
if (dot <= 0)
|
|
820
|
-
return undefined;
|
|
821
|
-
return { key: rest.slice(0, dot), action: rest.slice(dot + 1) };
|
|
822
|
-
}
|
|
823
|
-
/** Pull `objectName` out of a connector's `=jsonString:` configuration blob, if present. */
|
|
824
|
-
function connectorObjectName(configuration) {
|
|
825
|
-
if (typeof configuration !== 'string' || !configuration.startsWith('=jsonString:'))
|
|
826
|
-
return undefined;
|
|
827
|
-
try {
|
|
828
|
-
const cfg = JSON.parse(configuration.slice('=jsonString:'.length));
|
|
829
|
-
const name = cfg?.instanceParameters?.objectName ?? cfg?.objectName;
|
|
830
|
-
return typeof name === 'string' ? name : undefined;
|
|
831
|
-
}
|
|
832
|
-
catch {
|
|
833
|
-
return undefined;
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
808
|
/**
|
|
837
809
|
* Invert a connector action node into a `connector(key, action, inputs, opts)`
|
|
838
810
|
* call. serialize routes author inputs into `detail.bodyParameters` /
|
|
@@ -948,41 +920,6 @@ function emitEventSubscription(node, imp, inputNames) {
|
|
|
948
920
|
['folder', folder !== undefined ? tsLiteral(folder) : undefined],
|
|
949
921
|
]) + unboundMarker(node, { connection: conn, folder }, connector, event);
|
|
950
922
|
}
|
|
951
|
-
function emitConnector(node, imp, inputNames, o) {
|
|
952
|
-
const split = splitConnector(node.type);
|
|
953
|
-
const detail = (node.inputs?.detail ?? {});
|
|
954
|
-
if (!split)
|
|
955
|
-
return unsupportedSpec(node, imp, o);
|
|
956
|
-
const slots = ['bodyParameters', 'queryParameters', 'pathParameters'];
|
|
957
|
-
const flat = [];
|
|
958
|
-
for (const s of slots) {
|
|
959
|
-
const m = detail[s];
|
|
960
|
-
if (m && typeof m === 'object' && !Array.isArray(m)) {
|
|
961
|
-
for (const [k, v] of Object.entries(m))
|
|
962
|
-
flat.push([k, emitVal(v, imp, inputNames)]);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
const multipart = detail.multipartParameters;
|
|
966
|
-
if (Array.isArray(multipart)) {
|
|
967
|
-
for (const part of multipart) {
|
|
968
|
-
if (typeof part?.name === 'string' && Object.prototype.hasOwnProperty.call(part, 'value')) {
|
|
969
|
-
flat.push([part.name, emitVal(part.value, imp, inputNames)]);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
const connection = typeof detail.connectionId === 'string' ? detail.connectionId : undefined;
|
|
974
|
-
const folder = typeof detail.connectionFolderKey === 'string' ? detail.connectionFolderKey : undefined;
|
|
975
|
-
const object = connectorObjectName(detail.configuration);
|
|
976
|
-
const optsSrc = opts([
|
|
977
|
-
['connection', connection !== undefined ? tsLiteral(connection) : undefined],
|
|
978
|
-
['folder', folder !== undefined ? tsLiteral(folder) : undefined],
|
|
979
|
-
['object', object !== undefined ? tsLiteral(object) : undefined],
|
|
980
|
-
]);
|
|
981
|
-
const inputsSrc = objLit(flat) || '{}';
|
|
982
|
-
const args = [tsLiteral(split.key), tsLiteral(split.action), inputsSrc, ...(optsSrc ? [optsSrc] : [])];
|
|
983
|
-
return `${imp.use('connector')}(${args.join(', ')})`
|
|
984
|
-
+ unboundMarker(node, { connection, folder }, split.key, split.action);
|
|
985
|
-
}
|
|
986
923
|
/** Best-effort placeholder for a node type the reverse-map does not yet cover. */
|
|
987
924
|
function unsupportedSpec(node, imp, o) {
|
|
988
925
|
// The file's own definition for this node — the manifest the platform served.
|
|
@@ -10,6 +10,7 @@ import { NodeTypeRegistry } from './core/nodes/registry.js';
|
|
|
10
10
|
import { hasBundledDefinition } from './flow-definitions.js';
|
|
11
11
|
import { agentFlowFamily } from './nodes/agent/flow/family.js';
|
|
12
12
|
import { aiPatternsFlowFamily } from './nodes/ai-patterns/flow/family.js';
|
|
13
|
+
import { connectorFlowFamily } from './nodes/connector/flow/family.js';
|
|
13
14
|
import { conversationFlowFamily } from './nodes/conversation/flow/family.js';
|
|
14
15
|
import { dataFabricFlowFamily } from './nodes/data-fabric/flow/family.js';
|
|
15
16
|
import { delayFlowFamily } from './nodes/delay/flow/family.js';
|
|
@@ -22,5 +23,5 @@ import { queueFlowFamily } from './nodes/queue/flow/family.js';
|
|
|
22
23
|
import { scriptFlowFamily } from './nodes/script/flow/family.js';
|
|
23
24
|
import { transformFlowFamily } from './nodes/transform/flow/family.js';
|
|
24
25
|
/** The families, in no significant order. Adding one is the whole of registering it. */
|
|
25
|
-
export const FLOW_NODE_FAMILIES = [agentFlowFamily, aiPatternsFlowFamily, conversationFlowFamily, dataFabricFlowFamily, delayFlowFamily, documentFlowFamily, hitlFlowFamily, httpFlowFamily, mockFlowFamily, processFlowFamily, queueFlowFamily, scriptFlowFamily, transformFlowFamily];
|
|
26
|
+
export const FLOW_NODE_FAMILIES = [agentFlowFamily, aiPatternsFlowFamily, connectorFlowFamily, conversationFlowFamily, dataFabricFlowFamily, delayFlowFamily, documentFlowFamily, hitlFlowFamily, httpFlowFamily, mockFlowFamily, processFlowFamily, queueFlowFamily, scriptFlowFamily, transformFlowFamily];
|
|
26
27
|
export const FLOW_NODE_TYPES = new NodeTypeRegistry('flow', FLOW_NODE_FAMILIES, { hasBundledDefinition });
|
package/dist/flow-node-type.d.ts
CHANGED
|
@@ -87,6 +87,19 @@ export interface FlowNodeContribution extends Omit<NodeContribution, 'definition
|
|
|
87
87
|
/** Display fields set over the definition's, after `shape: 'circle'` (a tool's `description`). */
|
|
88
88
|
readonly display?: Readonly<Record<string, unknown>>;
|
|
89
89
|
}>;
|
|
90
|
+
/**
|
|
91
|
+
* In a draft compile ({@link FlowSerializeContext.drafting}), the node the type
|
|
92
|
+
* cannot build — a generic connector operation saved before its object was
|
|
93
|
+
* picked has no definition — written as this stand-in: the given type,
|
|
94
|
+
* `typeVersion` and inputs (written as given), the step's label, no definition,
|
|
95
|
+
* `ui` or `model`. Merge restores the original when it is untouched. Everything
|
|
96
|
+
* else in the contribution is ignored but `port`.
|
|
97
|
+
*/
|
|
98
|
+
standIn?: {
|
|
99
|
+
readonly type: string;
|
|
100
|
+
readonly typeVersion: string;
|
|
101
|
+
readonly inputs: Readonly<Record<string, unknown>>;
|
|
102
|
+
};
|
|
90
103
|
}
|
|
91
104
|
/** What `serialize` may ask of the serializer, for one placement. */
|
|
92
105
|
export interface FlowSerializeContext {
|
|
@@ -213,6 +226,21 @@ export interface FlowSerializeContext {
|
|
|
213
226
|
emitted(typeName: string, version: string): unknown;
|
|
214
227
|
/** The connector library serialize was given, for a type that resolves a connector operation. */
|
|
215
228
|
readonly library: SerializeOpts['library'];
|
|
229
|
+
/**
|
|
230
|
+
* Render a value and every `Expr` nested in its objects and arrays, as a
|
|
231
|
+
* connector's inputs are. A `lookup()` token is refused: it belongs only where a
|
|
232
|
+
* connector input resolves it (the whole value of a field, or an array element).
|
|
233
|
+
* An object result is a JSON copy that placement writes as is, so a `lit()`'s
|
|
234
|
+
* contents are not rendered a second time.
|
|
235
|
+
*/
|
|
236
|
+
renderDeep(value: unknown): unknown;
|
|
237
|
+
/**
|
|
238
|
+
* Whether this is a draft compile (merge's): a refusal the author can still fix
|
|
239
|
+
* is reported through {@link draft} and the step emitted as far as it can be.
|
|
240
|
+
*/
|
|
241
|
+
readonly drafting: boolean;
|
|
242
|
+
/** Report a draft finding on this node: what a non-draft compile would have refused. Only when {@link drafting}. */
|
|
243
|
+
draft(code: string, message: string): void;
|
|
216
244
|
}
|
|
217
245
|
/** What `deserialize` may ask of the decompiler, for one serialized node. */
|
|
218
246
|
export interface FlowDeserializeContext {
|
|
@@ -349,6 +377,22 @@ export interface FlowCheckContext {
|
|
|
349
377
|
* An explicit id equal to one another node gets is reported once the whole
|
|
350
378
|
* file is checked (`INLINE_AGENT_RESOURCE_NODE_ID`).
|
|
351
379
|
*/
|
|
380
|
+
/**
|
|
381
|
+
* Run the rules every Integration Service operation shares — its lookups, its
|
|
382
|
+
* schema readiness, its contract against the library facts check was given
|
|
383
|
+
* (`options`), and BINDING_MISSING — on this step. Their codes are the
|
|
384
|
+
* pipeline's, shared with an event subscription's, so they belong to no
|
|
385
|
+
* family catalog; each is reported at its own severity.
|
|
386
|
+
*/
|
|
387
|
+
connector(spec: {
|
|
388
|
+
key: string;
|
|
389
|
+
action: string;
|
|
390
|
+
version?: string;
|
|
391
|
+
object?: string;
|
|
392
|
+
connection?: unknown;
|
|
393
|
+
folder?: unknown;
|
|
394
|
+
inputs?: unknown;
|
|
395
|
+
}): void;
|
|
352
396
|
readonly resources: {
|
|
353
397
|
/** An id compile derives for one of this step's resources, and what it is, for that report. */
|
|
354
398
|
derivedNodeId(id: string, what: string): void;
|
|
@@ -461,6 +505,21 @@ export interface FlowNodeType<Spec> extends NodeType<Spec, FlowNodeContribution,
|
|
|
461
505
|
* the definition, an exit of the type, nor derived.
|
|
462
506
|
*/
|
|
463
507
|
derivesPort?(spec: Spec, port: string): boolean;
|
|
508
|
+
/**
|
|
509
|
+
* The measured runtime shape of this step's LIST output, when it has one: a
|
|
510
|
+
* bare array (`envelope: null`) or an object holding it under `envelope`. A
|
|
511
|
+
* loop over the output checks its read against it (LIST_OUTPUT_WRAPPER).
|
|
512
|
+
* `options` are check's (a connector's measured list envelopes among them).
|
|
513
|
+
*/
|
|
514
|
+
listShape?(spec: Spec, options: CheckOpts): {
|
|
515
|
+
envelope: string | null;
|
|
516
|
+
} | undefined;
|
|
517
|
+
/**
|
|
518
|
+
* The definition serialize will be supplied for a per-tenant type, when check's
|
|
519
|
+
* `options` can name it (a connector's, from the library). Check passes it to
|
|
520
|
+
* `errorPort`, so the two rungs give one answer; undefined when it cannot.
|
|
521
|
+
*/
|
|
522
|
+
checkDefinition?(spec: Spec, options: CheckOpts): unknown;
|
|
464
523
|
}
|
|
465
524
|
/** A Flow node type with several versions. */
|
|
466
525
|
export type FlowVersionedNodeType<Spec> = VersionedNodeType<Spec, FlowNodeContribution, FlowContexts, FlowNodeType<Spec>>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { connectorNodeType } from './node-type.js';
|
|
2
|
+
export const connectorFlowFamily = {
|
|
3
|
+
id: 'connector',
|
|
4
|
+
title: 'Integration Service connector',
|
|
5
|
+
types: [connectorNodeType],
|
|
6
|
+
// Its rules' codes are the pipeline's, shared with an event subscription (ctx.connector).
|
|
7
|
+
diagnostics: {},
|
|
8
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Integration Service connector operation — `connector(key, action, inputs,
|
|
3
|
+
* opts)`, a node type per operation: `uipath.connector.<connectorKey>.<action>`.
|
|
4
|
+
* The type owns every name under `uipath.connector.` that is an operation
|
|
5
|
+
* (`typeNameMatches`): not an event subscription's `uipath.connector.event.*` or a
|
|
6
|
+
* trigger's `uipath.connector.trigger.*`, which belong to `waitForEvent()` and
|
|
7
|
+
* `onEvent()`. Per-tenant: the definition is the library entry's, and so is its
|
|
8
|
+
* version (`versionedByDefinition`; most operations are at 1.0.0, some at 4.0.0).
|
|
9
|
+
*
|
|
10
|
+
* The spec and `connector()` stay in core/actions.ts: Case imports the spec, and
|
|
11
|
+
* Case may not import a node family. The rules the connector shares with an event
|
|
12
|
+
* subscription — lookups, schema readiness, the library contract, the binding
|
|
13
|
+
* labels — stay in core and run through `ctx.connector`, with their codes.
|
|
14
|
+
*
|
|
15
|
+
* Moved from serialize.ts / check.ts / decompile.ts in Phase 4 of
|
|
16
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; serialized output, printed source,
|
|
17
|
+
* codes and messages unchanged except for the three deliberate changes its 4o
|
|
18
|
+
* entry lists.
|
|
19
|
+
*/
|
|
20
|
+
import type { ActionSpec } from '../../../core/actions.js';
|
|
21
|
+
import type { FlowVersionedNodeType } from '../../../flow-node-type.js';
|
|
22
|
+
export type ConnectorSpec = Extract<ActionSpec, {
|
|
23
|
+
kind: 'connector';
|
|
24
|
+
}>;
|
|
25
|
+
export declare const connectorNodeType: FlowVersionedNodeType<ConnectorSpec>;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { BindingRefusal, bindingMissingMessage, missingBindingLabels } from '../../../core/binding-messages.js';
|
|
2
|
+
import { prepareCommand } from '../../../core/cli-spelling.js';
|
|
3
|
+
import { buildConfiguration } from '../../../core/connector-configuration.js';
|
|
4
|
+
import { CONNECTOR_INPUT_MISSING, FLOW_EXPR_DIALECT, buildConnectorInputs, transportHttpMethod } from '../../../core/connector-inputs.js';
|
|
5
|
+
import { ConnectorResolveError } from '../../../core/connector-resolve-error.js';
|
|
6
|
+
import { definitionHasErrorPort } from '../../../core/error-port.js';
|
|
7
|
+
import { Expr } from '../../../core/expr.js';
|
|
8
|
+
import { jsonClone } from '../../../core/json-clone.js';
|
|
9
|
+
import { subNodeId } from '../../../core/node-id-placeholder.js';
|
|
10
|
+
import { synthesizedDefinitionRefusal } from '../../../core/synthesized-definition.js';
|
|
11
|
+
import { tsLiteral, tsObject } from '../../../core/ts-literal.js';
|
|
12
|
+
const CONNECTOR_PREFIX = 'uipath.connector.';
|
|
13
|
+
/** `<key>.<action>` after the prefix, split on the last dot; undefined for an event, a trigger, or a name with no action. */
|
|
14
|
+
function splitOperation(typeName) {
|
|
15
|
+
if (!typeName.startsWith(CONNECTOR_PREFIX))
|
|
16
|
+
return undefined;
|
|
17
|
+
const rest = typeName.slice(CONNECTOR_PREFIX.length);
|
|
18
|
+
if (rest.startsWith('event.') || rest.startsWith('trigger.'))
|
|
19
|
+
return undefined;
|
|
20
|
+
const dot = rest.lastIndexOf('.');
|
|
21
|
+
if (dot <= 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
return { key: rest.slice(0, dot), action: rest.slice(dot + 1) };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The node's `output` slot for a LIST operation, declared as the ARRAY it is when
|
|
27
|
+
* the ledger has MEASURED that it is one ([#203](https://github.com/UiPath/flow-builder-sdk/issues/203)).
|
|
28
|
+
*
|
|
29
|
+
* Every connector definition declares `outputs.output.type: 'object'` — list
|
|
30
|
+
* operations included, and **0 of 544 deployed connector nodes declare `array`
|
|
31
|
+
* anywhere** (card CLEANUP's corpus scan). For most of the library that is the
|
|
32
|
+
* honest thing to emit, because the SDK does not know better. For an operation in
|
|
33
|
+
* the list-envelope ledger it does:
|
|
34
|
+
*
|
|
35
|
+
* · `envelope: null` (a measured BARE array) ⇒ **`type: 'array'`**;
|
|
36
|
+
* · a measured WRAPPER ⇒ `object`, unchanged — the value IS an object then;
|
|
37
|
+
* · UNDECLARED ⇒ untouched, which is #202's status quo.
|
|
38
|
+
*
|
|
39
|
+
* Why this matters beyond tidiness: `typeAgentInputSchemas` builds an agent's
|
|
40
|
+
* pydantic input model from these declarations, and card ga7's scored run r2 died
|
|
41
|
+
* at `AGENT_STARTUP.INPUT_VALIDATION_ERROR` because a Data Service list node said
|
|
42
|
+
* `object` and the runtime sent 8 rows. #202 made that property TYPELESS, which
|
|
43
|
+
* unblocked the agent without asserting anything; this says the true thing.
|
|
44
|
+
*
|
|
45
|
+
* ## And deliberately NO item schema
|
|
46
|
+
*
|
|
47
|
+
* #203 suggested emitting the item fields alongside. They are not emitted, and the
|
|
48
|
+
* reason is a measurement rather than caution: a list operation's declared fields
|
|
49
|
+
* are **neither a superset nor a subset** of the real response (card LT, three live
|
|
50
|
+
* responses — ServiceNow `incident` declares 0 of the 103 fields it returns; Entra
|
|
51
|
+
* declares 20, returns 15, misses 2; Jira declares 253, returns 116, misses 42).
|
|
52
|
+
* Asserting them into an agent's pydantic model would reject real rows — the exact
|
|
53
|
+
* fault class above, moved rather than fixed. The library lift stays a READ-checking
|
|
54
|
+
* aid, where it fails open by design (lesson 26). The emitted bytes claim only what
|
|
55
|
+
* is certain: this is an array.
|
|
56
|
+
*/
|
|
57
|
+
function listOutputs(rc, nodeId) {
|
|
58
|
+
if (!rc.listEnvelope || rc.listEnvelope.envelope !== null)
|
|
59
|
+
return undefined;
|
|
60
|
+
const out = rc.v1def?.outputDefinition?.output;
|
|
61
|
+
if (!out || typeof out !== 'object' || out.type !== 'object')
|
|
62
|
+
return undefined;
|
|
63
|
+
return { output: { ...subNodeId(jsonClone(out), nodeId), type: 'array' } };
|
|
64
|
+
}
|
|
65
|
+
function serializeConnector(spec, ctx) {
|
|
66
|
+
const step = ctx.step;
|
|
67
|
+
if (!ctx.library)
|
|
68
|
+
throw new Error(`connector "${step}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
|
|
69
|
+
let rc;
|
|
70
|
+
try {
|
|
71
|
+
rc = ctx.library.resolve(spec.key, spec.action, spec.version, spec.object);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (!ctx.drafting || !(error instanceof ConnectorResolveError) || error.code !== 'CONNECTOR_OBJECT_REQUIRED')
|
|
75
|
+
throw error;
|
|
76
|
+
// A generic operation saved before its object was picked has no definition
|
|
77
|
+
// to emit against. Draft tolerance emits a stable stand-in under the node's
|
|
78
|
+
// id; merge restores the original when it is untouched and refuses it
|
|
79
|
+
// otherwise. It leaves on the port every connector action leaves on, so the
|
|
80
|
+
// no-edit round trip rewires nothing. Its inputs carry what the author wrote,
|
|
81
|
+
// so an edit to the node (an input, a connection) shows up as a change and
|
|
82
|
+
// merge refuses it instead of restoring the original.
|
|
83
|
+
ctx.draft(error.code, error.message);
|
|
84
|
+
return {
|
|
85
|
+
inputs: {},
|
|
86
|
+
port: 'output',
|
|
87
|
+
standIn: {
|
|
88
|
+
type: `${CONNECTOR_PREFIX}${spec.key}.${spec.action}`,
|
|
89
|
+
typeVersion: spec.version ?? '1.0.0',
|
|
90
|
+
inputs: {
|
|
91
|
+
draft: true,
|
|
92
|
+
spec: ctx.renderDeep(spec.inputs ?? {}),
|
|
93
|
+
...(spec.connection !== undefined ? { connection: spec.connection } : {}),
|
|
94
|
+
...(spec.folder !== undefined ? { folder: spec.folder } : {}),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// An entry `prepare` synthesized from Integration Service compiles in BPMN only.
|
|
100
|
+
const noDefinition = synthesizedDefinitionRefusal(rc);
|
|
101
|
+
if (noDefinition)
|
|
102
|
+
throw noDefinition;
|
|
103
|
+
const where = `connector "${step}"`;
|
|
104
|
+
const target = { key: spec.key, action: spec.action };
|
|
105
|
+
// A connector-bound node needs both labels (#890); check says so first
|
|
106
|
+
// (BINDING_MISSING). A draft that names no connection gets no binding rows
|
|
107
|
+
// either: there is nothing to bind, and rows with an empty resourceKey would
|
|
108
|
+
// leak into the merged file through merge's bindings union.
|
|
109
|
+
let unbound = false;
|
|
110
|
+
const missing = missingBindingLabels(spec);
|
|
111
|
+
if (missing.length > 0) {
|
|
112
|
+
const refusal = new BindingRefusal('BINDING_MISSING', bindingMissingMessage(where, missing, target));
|
|
113
|
+
if (!ctx.drafting)
|
|
114
|
+
throw refusal;
|
|
115
|
+
ctx.draft(refusal.code, refusal.message);
|
|
116
|
+
unbound = true;
|
|
117
|
+
}
|
|
118
|
+
const conn = ctx.resolveBinding(spec.connection, 'connection', where, target);
|
|
119
|
+
const folder = ctx.resolveBinding(spec.folder, 'folder', where, target);
|
|
120
|
+
// The platform reaches the connection through the flow's `bindings[]`, never
|
|
121
|
+
// through `inputs.detail` — without this pair the run faults with `[102010]`
|
|
122
|
+
// (board G-19).
|
|
123
|
+
if (!unbound) {
|
|
124
|
+
ctx.bindConnection({
|
|
125
|
+
model: rc.v1def?.model,
|
|
126
|
+
connectorKey: rc.connectorKey, connectionId: conn,
|
|
127
|
+
...(folder !== undefined ? { folderKey: folder } : {}),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const library = ctx.library;
|
|
131
|
+
const connectorInputs = buildConnectorInputs(rc, spec.inputs, {
|
|
132
|
+
...(ctx.drafting
|
|
133
|
+
? { onMissingRequired: (m) => ctx.draft(CONNECTOR_INPUT_MISSING, `connector ${rc.nodeType}: ${m}`) }
|
|
134
|
+
: {}),
|
|
135
|
+
dialect: FLOW_EXPR_DIALECT,
|
|
136
|
+
render: (value) => ctx.renderDeep(value),
|
|
137
|
+
resolutions: library.lookupResolutions ?? {},
|
|
138
|
+
// Only for the missing-required hint; a test double that is not a full
|
|
139
|
+
// Library simply gets the plain message.
|
|
140
|
+
lookups: typeof library.lookupSpecs === 'function'
|
|
141
|
+
? library.lookupSpecs(spec.key, spec.action, spec.version, spec.object)
|
|
142
|
+
: {},
|
|
143
|
+
});
|
|
144
|
+
const replayInputs = {
|
|
145
|
+
...(connectorInputs.bodyParameters ?? {}),
|
|
146
|
+
...(connectorInputs.pathParameters ?? {}),
|
|
147
|
+
...(connectorInputs.queryParameters ?? {}),
|
|
148
|
+
};
|
|
149
|
+
const detail = {
|
|
150
|
+
connector: rc.connectorKey,
|
|
151
|
+
method: transportHttpMethod(rc.httpMethod),
|
|
152
|
+
endpoint: rc.endpoint,
|
|
153
|
+
// The op's object identity, as a SIBLING of the copy inside `configuration`
|
|
154
|
+
// — the designer emits both, and every connector node in a product-exported
|
|
155
|
+
// flow carries it. Conditional because the library defaults a missing
|
|
156
|
+
// `operation.objectName` to '' — an op that names no object gets no key.
|
|
157
|
+
...(rc.objectName ? { objectName: rc.objectName } : {}),
|
|
158
|
+
inputMetadata: {},
|
|
159
|
+
errorState: { hasError: false },
|
|
160
|
+
telemetryData: { connectorKey: rc.connectorKey, connectorName: rc.connectorName },
|
|
161
|
+
configuration: buildConfiguration(rc.entry, rc.version, replayInputs, ctx.nodeId),
|
|
162
|
+
...(rc.activityTypeId ? { uiPathActivityTypeId: rc.activityTypeId } : {}),
|
|
163
|
+
...(conn ? { connectionId: conn, connectionResourceId: conn } : {}),
|
|
164
|
+
...(folder ? { connectionFolderKey: folder } : {}),
|
|
165
|
+
};
|
|
166
|
+
// Each input in the slot its LOCATION names — body → detail.bodyParameters,
|
|
167
|
+
// query → queryParameters, path → pathParameters, multipart →
|
|
168
|
+
// multipartParameters — checked against the operation's field list.
|
|
169
|
+
// `bodyParameters` is emitted unconditionally (3 of 1,283 deployed connector
|
|
170
|
+
// nodes carry an empty one); the others only when the op has inputs there.
|
|
171
|
+
Object.assign(detail, connectorInputs);
|
|
172
|
+
// The node is the operation the author named (`rc.nodeType`); a definition
|
|
173
|
+
// naming another — a library entry that borrowed one, as a synthetic test
|
|
174
|
+
// library does — is written under that name, so the node and its
|
|
175
|
+
// definitions[] entry agree.
|
|
176
|
+
const v1def = rc.v1def;
|
|
177
|
+
const def = v1def.nodeType === rc.nodeType ? v1def : { ...v1def, nodeType: rc.nodeType };
|
|
178
|
+
const outputs = listOutputs(rc, ctx.nodeId);
|
|
179
|
+
return {
|
|
180
|
+
definition: { source: 'snapshot', nodeType: def.nodeType, version: def.version, manifest: def },
|
|
181
|
+
inputs: { detail },
|
|
182
|
+
// Every connector operation leaves on `output`, named: a definition with no
|
|
183
|
+
// handles has no default to fall back on.
|
|
184
|
+
port: 'output',
|
|
185
|
+
...(outputs !== undefined ? { outputs } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/** A connector input's value as decompile reads it: an expression back to an `Expr`, wire text as text, any other JSON as is. */
|
|
189
|
+
function liftValue(v, ctx) {
|
|
190
|
+
const raw = Array.isArray(v) ? undefined : ctx.sourceText(v);
|
|
191
|
+
if (raw === undefined)
|
|
192
|
+
return v;
|
|
193
|
+
return raw.startsWith('=js:') ? new Expr(raw.slice(4)) : raw;
|
|
194
|
+
}
|
|
195
|
+
/** Pull `objectName` out of a connector's `=jsonString:` configuration blob, if present. */
|
|
196
|
+
function configurationObjectName(configuration) {
|
|
197
|
+
if (typeof configuration !== 'string' || !configuration.startsWith('=jsonString:'))
|
|
198
|
+
return undefined;
|
|
199
|
+
try {
|
|
200
|
+
const cfg = JSON.parse(configuration.slice('=jsonString:'.length));
|
|
201
|
+
const name = cfg?.instanceParameters?.objectName ?? cfg?.objectName;
|
|
202
|
+
return typeof name === 'string' ? name : undefined;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function liftConnector(node, ctx) {
|
|
209
|
+
const n = node;
|
|
210
|
+
const split = splitOperation(n.type);
|
|
211
|
+
if (!split)
|
|
212
|
+
return undefined;
|
|
213
|
+
const detail = (n.inputs?.detail ?? {});
|
|
214
|
+
const inputs = [];
|
|
215
|
+
for (const slot of ['bodyParameters', 'queryParameters', 'pathParameters']) {
|
|
216
|
+
const m = detail[slot];
|
|
217
|
+
if (m && typeof m === 'object' && !Array.isArray(m)) {
|
|
218
|
+
for (const [k, v] of Object.entries(m))
|
|
219
|
+
inputs.push([k, liftValue(v, ctx)]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const multipart = detail.multipartParameters;
|
|
223
|
+
if (Array.isArray(multipart)) {
|
|
224
|
+
for (const part of multipart) {
|
|
225
|
+
if (typeof part?.name === 'string' && Object.prototype.hasOwnProperty.call(part, 'value')) {
|
|
226
|
+
inputs.push([part.name, liftValue(part.value, ctx)]);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const connection = typeof detail.connectionId === 'string' ? detail.connectionId : undefined;
|
|
231
|
+
const folder = typeof detail.connectionFolderKey === 'string' ? detail.connectionFolderKey : undefined;
|
|
232
|
+
// A connection the file never picked (`connectionId: null`): the source cannot
|
|
233
|
+
// invent labels for it, and compile refuses a node without both
|
|
234
|
+
// (BINDING_MISSING, flow-builder-sdk#890), so the source says so at the node
|
|
235
|
+
// and the run warns once.
|
|
236
|
+
const missing = missingBindingLabels({ connection, folder });
|
|
237
|
+
if (missing.length > 0) {
|
|
238
|
+
ctx.warn(`node ${tsLiteral(n.id)} (${n.type}) has no ${missing.join(' or ')} in the .flow (an unbound draft). `
|
|
239
|
+
+ `The source leaves ${missing.length > 1 ? 'them' : 'it'} out, and compile refuses the node (BINDING_MISSING) `
|
|
240
|
+
+ `until it names both labels: run \`${prepareCommand(split.key, split.action)}\`, then pass `
|
|
241
|
+
+ `\`connection\` and \`folder\`.`);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
kind: 'connector', ...split, inputs, connection, folder,
|
|
245
|
+
object: configurationObjectName(detail.configuration),
|
|
246
|
+
missing,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function printConnector(l, ctx) {
|
|
250
|
+
// A present input whose value is `undefined` (the inputs check accepts one, and
|
|
251
|
+
// serialize keeps it in memory) prints as `undefined`, as it did before the port.
|
|
252
|
+
const value = (v) => (v === undefined ? 'undefined' : v instanceof Expr ? ctx.value(v) : ctx.json(v));
|
|
253
|
+
const optional = (v) => (v === undefined ? undefined : ctx.string(v));
|
|
254
|
+
return {
|
|
255
|
+
factory: 'connector',
|
|
256
|
+
positional: [ctx.string(l.key), ctx.string(l.action), tsObject(l.inputs.map(([k, v]) => [k, value(v)])) || '{}'],
|
|
257
|
+
options: [
|
|
258
|
+
['connection', optional(l.connection)],
|
|
259
|
+
['folder', optional(l.folder)],
|
|
260
|
+
['object', optional(l.object)],
|
|
261
|
+
],
|
|
262
|
+
...(l.missing.length > 0
|
|
263
|
+
? { comment: `TODO: bind ${l.missing.join(' and ')}; compile refuses this node until it has both labels (BINDING_MISSING)` }
|
|
264
|
+
: {}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const connector10 = {
|
|
268
|
+
kind: 'connector', typeName: `${CONNECTOR_PREFIX}*`, version: '1.0.0', factory: 'connector',
|
|
269
|
+
definition: 'per-tenant',
|
|
270
|
+
// The library's definition answers, in serialize and, given a library, in check
|
|
271
|
+
// (checkDefinition). Without one check assumes the port every operation the
|
|
272
|
+
// library serves declares.
|
|
273
|
+
errorPort: (_spec, definition) => definition === undefined || definitionHasErrorPort(definition),
|
|
274
|
+
checkDefinition: (spec, options) => {
|
|
275
|
+
const def = options.connectorDefinition?.(spec.key, spec.action, spec.version, spec.object);
|
|
276
|
+
// Named as serialize places it: under the operation the author named.
|
|
277
|
+
return def !== null && typeof def === 'object' ? { ...def, nodeType: `${CONNECTOR_PREFIX}${spec.key}.${spec.action}` } : undefined;
|
|
278
|
+
},
|
|
279
|
+
serialize: (spec, ctx) => serializeConnector(spec, ctx),
|
|
280
|
+
deserialize: (node, ctx) => liftConnector(node, ctx),
|
|
281
|
+
print: (spec, ctx) => printConnector(spec, ctx),
|
|
282
|
+
check: (spec, ctx) => ctx.connector(spec),
|
|
283
|
+
// Every Expr in the inputs, nested ones included, resolves against the flow's names.
|
|
284
|
+
expressions: (spec) => [{ value: spec.inputs }],
|
|
285
|
+
output: () => ({ kind: 'opaque' }),
|
|
286
|
+
listShape: (spec, options) => options.listEnvelope?.(spec.key, spec.action, spec.version, spec.object),
|
|
287
|
+
};
|
|
288
|
+
export const connectorNodeType = {
|
|
289
|
+
kind: 'connector', typeName: `${CONNECTOR_PREFIX}*`, typeNamePrefix: CONNECTOR_PREFIX, factory: 'connector',
|
|
290
|
+
typeNameMatches: (typeName) => splitOperation(typeName) !== undefined,
|
|
291
|
+
versionedByDefinition: true,
|
|
292
|
+
defaultVersion: connector10.version,
|
|
293
|
+
versions: { [connector10.version]: connector10 },
|
|
294
|
+
};
|
package/dist/serialize.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { BuiltFlow } from './flow-sdk.js';
|
|
|
3
3
|
import type { Library } from './library.js';
|
|
4
4
|
import { type BindingRefusalCode } from './core/binding-messages.js';
|
|
5
5
|
import type { Bindings } from './bindings.js';
|
|
6
|
-
import { connectorInputErrors as coreConnectorInputErrors } from './core/connector-inputs.js';
|
|
6
|
+
import { CONNECTOR_INPUT_MISSING, connectorInputErrors as coreConnectorInputErrors } from './core/connector-inputs.js';
|
|
7
7
|
/**
|
|
8
8
|
* What `compile` would refuse these connector inputs with, or `undefined` when it
|
|
9
9
|
* would accept them — Flow's binding of the shared validator.
|
|
@@ -23,8 +23,7 @@ export interface SerializeWarning {
|
|
|
23
23
|
code: string;
|
|
24
24
|
message: string;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
export declare const CONNECTOR_INPUT_MISSING = "CONNECTOR_INPUT_MISSING";
|
|
26
|
+
export { CONNECTOR_INPUT_MISSING } from './core/connector-inputs.js';
|
|
28
27
|
/** The refusals a draft-tolerant compile reports instead of throwing. */
|
|
29
28
|
export type DraftCode = BindingRefusalCode | typeof CONNECTOR_INPUT_MISSING | 'CONNECTOR_OBJECT_REQUIRED';
|
|
30
29
|
/** A refusal a draft-tolerant compile reported instead of throwing (see {@link SerializeOpts.draft}). */
|
package/dist/serialize.js
CHANGED
|
@@ -18,8 +18,6 @@ import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
|
18
18
|
import { stepExits } from './flow-step-exits.js';
|
|
19
19
|
import { resolveVersionCarried } from './core/nodes/versioned.js';
|
|
20
20
|
import { indexCarriedDefinitions } from './core/carried-definitions.js';
|
|
21
|
-
import { ConnectorResolveError } from './core/connector-resolve-error.js';
|
|
22
|
-
import { synthesizedDefinitionRefusal } from './core/synthesized-definition.js';
|
|
23
21
|
import { connectionConnectors } from './bindings-v2.js';
|
|
24
22
|
import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
|
|
25
23
|
import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
|
|
@@ -30,11 +28,10 @@ import { placeholderPosition } from './node-placement.js';
|
|
|
30
28
|
import { hasExits } from './core/step-ports.js';
|
|
31
29
|
import { stopsAtEnd } from './flow-step-tree.js';
|
|
32
30
|
import { assertNodeEnvelope } from './core/node-envelope.js';
|
|
33
|
-
import { buildConfiguration } from './core/connector-configuration.js';
|
|
34
31
|
import { fnv1aX4Uuid, stableId } from './core/stable-id.js';
|
|
35
32
|
import { readEventFilter, eventFilterProblem, eventFilterJmes, eventFilterTreeLeaf, jmesString, jmesStringProblem } from './core/event-filters.js';
|
|
36
33
|
import { FLOW_EXPR_DIALECT } from './core/connector-checks.js';
|
|
37
|
-
import {
|
|
34
|
+
import { connectorInputErrors as coreConnectorInputErrors } from './core/connector-inputs.js';
|
|
38
35
|
/**
|
|
39
36
|
* What `compile` would refuse these connector inputs with, or `undefined` when it
|
|
40
37
|
* would accept them — Flow's binding of the shared validator.
|
|
@@ -118,48 +115,6 @@ const SUBFLOW_OUTPUT = {
|
|
|
118
115
|
source: '=result.response',
|
|
119
116
|
var: 'output',
|
|
120
117
|
};
|
|
121
|
-
/** The inline (autonomous) agent's node type — fixed, not per-instance. */
|
|
122
|
-
/**
|
|
123
|
-
* Declare a LIST operation's output as the ARRAY it is, when the ledger has
|
|
124
|
-
* MEASURED that it is one ([#203](https://github.com/UiPath/flow-builder-sdk/issues/203)).
|
|
125
|
-
*
|
|
126
|
-
* Every connector definition declares `outputs.output.type: 'object'` — list
|
|
127
|
-
* operations included, and **0 of 544 deployed connector nodes declare `array`
|
|
128
|
-
* anywhere** (card CLEANUP's corpus scan). For most of the library that is the
|
|
129
|
-
* honest thing to emit, because the SDK does not know better. For an operation in
|
|
130
|
-
* the list-envelope ledger it does:
|
|
131
|
-
*
|
|
132
|
-
* · `envelope: null` (a measured BARE array) ⇒ **`type: 'array'`**;
|
|
133
|
-
* · a measured WRAPPER ⇒ `object`, unchanged — the value IS an object then;
|
|
134
|
-
* · UNDECLARED ⇒ untouched, which is #202's status quo.
|
|
135
|
-
*
|
|
136
|
-
* Why this matters beyond tidiness: `typeAgentInputSchemas` builds an agent's
|
|
137
|
-
* pydantic input model from these declarations, and card ga7's scored run r2 died
|
|
138
|
-
* at `AGENT_STARTUP.INPUT_VALIDATION_ERROR` because a Data Service list node said
|
|
139
|
-
* `object` and the runtime sent 8 rows. #202 made that property TYPELESS, which
|
|
140
|
-
* unblocked the agent without asserting anything; this says the true thing.
|
|
141
|
-
*
|
|
142
|
-
* ## And deliberately NO item schema
|
|
143
|
-
*
|
|
144
|
-
* #203 suggested emitting the item fields alongside. They are not emitted, and the
|
|
145
|
-
* reason is a measurement rather than caution: a list operation's declared fields
|
|
146
|
-
* are **neither a superset nor a subset** of the real response (card LT, three live
|
|
147
|
-
* responses — ServiceNow `incident` declares 0 of the 103 fields it returns; Entra
|
|
148
|
-
* declares 20, returns 15, misses 2; Jira declares 253, returns 116, misses 42).
|
|
149
|
-
* Asserting them into an agent's pydantic model would reject real rows — the exact
|
|
150
|
-
* fault class above, moved rather than fixed. The library lift stays a READ-checking
|
|
151
|
-
* aid, where it fails open by design (lesson 26). The emitted bytes claim only what
|
|
152
|
-
* is certain: this is an array.
|
|
153
|
-
*/
|
|
154
|
-
function declareListOutputType(node, rc) {
|
|
155
|
-
const declared = rc.listEnvelope;
|
|
156
|
-
if (!declared || declared.envelope !== null)
|
|
157
|
-
return;
|
|
158
|
-
const out = node.outputs?.output;
|
|
159
|
-
if (!out || typeof out !== 'object' || out.type !== 'object')
|
|
160
|
-
return;
|
|
161
|
-
out.type = 'array';
|
|
162
|
-
}
|
|
163
118
|
/**
|
|
164
119
|
* Read a connector definition's connection requirements without collapsing
|
|
165
120
|
* three distinct states:
|
|
@@ -315,8 +270,7 @@ function scheduleInputs(inputs, nodeId) {
|
|
|
315
270
|
...(isPreset ? {} : { timerValue: every }),
|
|
316
271
|
};
|
|
317
272
|
}
|
|
318
|
-
|
|
319
|
-
export const CONNECTOR_INPUT_MISSING = 'CONNECTOR_INPUT_MISSING';
|
|
273
|
+
export { CONNECTOR_INPUT_MISSING } from './core/connector-inputs.js';
|
|
320
274
|
/**
|
|
321
275
|
* `serialize`, plus the extra files the flow needs on disk beside it.
|
|
322
276
|
*
|
|
@@ -2346,6 +2300,14 @@ export function serialize(built, opts = {}) {
|
|
|
2346
2300
|
if (impl.errorEnvelope)
|
|
2347
2301
|
scope.rename.errorEnvelopes.set(step.name, impl.errorEnvelope);
|
|
2348
2302
|
const rendered = new WeakSet();
|
|
2303
|
+
// A rendered value's JSON copy, marked as the file's: placement writes it as
|
|
2304
|
+
// is instead of rendering it again. Both renderers below go through it.
|
|
2305
|
+
const fileCopy = (out) => {
|
|
2306
|
+
const copy = jsonClone(out);
|
|
2307
|
+
if (copy !== null && typeof copy === 'object')
|
|
2308
|
+
rendered.add(copy);
|
|
2309
|
+
return copy;
|
|
2310
|
+
};
|
|
2349
2311
|
const ctx = {
|
|
2350
2312
|
step: step.name,
|
|
2351
2313
|
nodeId: id,
|
|
@@ -2357,13 +2319,7 @@ export function serialize(built, opts = {}) {
|
|
|
2357
2319
|
// Only a `lit` unwraps to a new value. Its JSON copy is the file's: a
|
|
2358
2320
|
// copy, so an object the author also passes outside the `lit` is not
|
|
2359
2321
|
// mistaken for this one and still has its expressions rendered.
|
|
2360
|
-
|
|
2361
|
-
const copy = JSON.parse(JSON.stringify(out));
|
|
2362
|
-
if (copy !== null && typeof copy === 'object')
|
|
2363
|
-
rendered.add(copy);
|
|
2364
|
-
return copy;
|
|
2365
|
-
}
|
|
2366
|
-
return out;
|
|
2322
|
+
return out !== value && out !== null && typeof out === 'object' ? fileCopy(out) : out;
|
|
2367
2323
|
},
|
|
2368
2324
|
renameRefs: (js) => renameRefs(js, scope.rename),
|
|
2369
2325
|
stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
|
|
@@ -2397,9 +2353,34 @@ export function serialize(built, opts = {}) {
|
|
|
2397
2353
|
resourceNodeId: (explicit, seed, at) => resourceNodeId(scope, explicit, seed, at),
|
|
2398
2354
|
emitted: (typeName, v) => emittedDefs.get(`${typeName}@${v}`),
|
|
2399
2355
|
library: opts.library,
|
|
2356
|
+
// Rendered once: placement renders the contribution's inputs again, so the
|
|
2357
|
+
// result is marked as the file's, as `render` marks a `lit`'s copy. A copy,
|
|
2358
|
+
// so an author object the walk returned as is (a `lit`'s own) stays unmarked
|
|
2359
|
+
// where it is also used outside the `lit`.
|
|
2360
|
+
renderDeep: (value) => {
|
|
2361
|
+
const out = renderDeep(value, scope.rename, rendered);
|
|
2362
|
+
return out !== null && typeof out === 'object' ? fileCopy(out) : out;
|
|
2363
|
+
},
|
|
2364
|
+
drafting: opts.draft !== undefined,
|
|
2365
|
+
draft: (code, message) => opts.draft?.({ nodeId: id, code: code, message }),
|
|
2400
2366
|
};
|
|
2401
2367
|
const bound = [];
|
|
2402
2368
|
const contribution = impl.serialize(spec, ctx);
|
|
2369
|
+
if (contribution.standIn !== undefined) {
|
|
2370
|
+
// A draft stand-in: the node as the draft has it, with no definition to
|
|
2371
|
+
// place it against (see FlowNodeContribution.standIn).
|
|
2372
|
+
if (!ctx.drafting)
|
|
2373
|
+
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName} returned a draft stand-in outside a draft compile.`);
|
|
2374
|
+
const { type, typeVersion, inputs } = contribution.standIn;
|
|
2375
|
+
scope.nodes.push({
|
|
2376
|
+
id, type, typeVersion,
|
|
2377
|
+
...(parentId ? { parentId } : {}), display: { label: step.name },
|
|
2378
|
+
inputs: { ...inputs },
|
|
2379
|
+
});
|
|
2380
|
+
for (const t of tails)
|
|
2381
|
+
addEdge(scope, t, id);
|
|
2382
|
+
return [{ node: id, port: contribution.port ?? 'output' }];
|
|
2383
|
+
}
|
|
2403
2384
|
// The definition contract, both halves: a per-tenant type supplies its
|
|
2404
2385
|
// definition, and a bundled type does not (its definition is the bundle's, and
|
|
2405
2386
|
// makeNode records it, so the error-edge refusal and check ask the same one).
|
|
@@ -2414,7 +2395,9 @@ export function serialize(built, opts = {}) {
|
|
|
2414
2395
|
// instance's edits (a queue step's own resourceKey, for instance). Only a type
|
|
2415
2396
|
// that supplies none at a carried version takes the carried definition as is.
|
|
2416
2397
|
if (contribution.definition !== undefined) {
|
|
2417
|
-
|
|
2398
|
+
// A type whose definition names its version is held to the author's pin by
|
|
2399
|
+
// makeNode, with the message every definition's mismatch gets.
|
|
2400
|
+
def = resolveContributionDefinition(contribution.definition, entry.type.versionedByDefinition ? undefined : version, where);
|
|
2418
2401
|
}
|
|
2419
2402
|
else if (impl.definition === 'per-tenant' && resolved.carried === undefined) {
|
|
2420
2403
|
throw new Error(`internal SDK error: ${where}: per-tenant node type ${impl.typeName}@${version} returned no definition.`);
|
|
@@ -2436,14 +2419,14 @@ export function serialize(built, opts = {}) {
|
|
|
2436
2419
|
// tenant definition (an inline agent at 1.1) may have no error handle.
|
|
2437
2420
|
const port = resolved.carried !== undefined
|
|
2438
2421
|
? definitionHasErrorPort(def ?? resolved.carried)
|
|
2439
|
-
: typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
|
|
2422
|
+
: typeof impl.errorPort === 'function' ? impl.errorPort(spec, def) : impl.errorPort === true;
|
|
2440
2423
|
// The declaration answers for check, which has no definition; here the
|
|
2441
2424
|
// definition is in hand, and the two must agree (#1011).
|
|
2442
2425
|
if (port && def !== undefined && !definitionHasErrorPort(def)) {
|
|
2443
2426
|
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${version} declares an error port, `
|
|
2444
2427
|
+ 'but the definition it supplied has no error handle and no supportsErrorHandling.');
|
|
2445
2428
|
}
|
|
2446
|
-
declaredErrorPorts.set(id, { port, typeName: nodeType, version });
|
|
2429
|
+
declaredErrorPorts.set(id, { port, typeName: nodeType, version: def?.version ?? version });
|
|
2447
2430
|
}
|
|
2448
2431
|
// A forward port is a handle the definition declares, one of the type's
|
|
2449
2432
|
// author-named exits, or one the product derives (`derivesPort`: a human
|
|
@@ -2462,7 +2445,11 @@ export function serialize(built, opts = {}) {
|
|
|
2462
2445
|
if (resolved.carried !== undefined)
|
|
2463
2446
|
bundledNodeDefs.set(id, def ?? resolved.carried);
|
|
2464
2447
|
return placeStepNode({
|
|
2465
|
-
where, stepName: step.name, id, typeName: nodeType,
|
|
2448
|
+
where, stepName: step.name, id, typeName: nodeType,
|
|
2449
|
+
// A type whose definition names its version is placed at that version,
|
|
2450
|
+
// held to the author's pin, as makeNode holds any definition to one.
|
|
2451
|
+
requestedVersion: entry.type.versionedByDefinition ? step.options?.version : version,
|
|
2452
|
+
def, contribution, bound, tails, scope, parentId,
|
|
2466
2453
|
trackOutcomes: true,
|
|
2467
2454
|
rendered,
|
|
2468
2455
|
});
|
|
@@ -2501,129 +2488,6 @@ export function serialize(built, opts = {}) {
|
|
|
2501
2488
|
subflows[id] = emitSubflowBody(spec.child, id);
|
|
2502
2489
|
return [{ node: id, port: successPort(NODE_TYPE.subflow) }];
|
|
2503
2490
|
}
|
|
2504
|
-
if (spec.kind === 'connector') {
|
|
2505
|
-
if (!opts.library)
|
|
2506
|
-
throw new Error(`connector "${step.name}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
|
|
2507
|
-
let rc;
|
|
2508
|
-
try {
|
|
2509
|
-
rc = opts.library.resolve(spec.key, spec.action, spec.version, spec.object);
|
|
2510
|
-
}
|
|
2511
|
-
catch (error) {
|
|
2512
|
-
if (!opts.draft || !(error instanceof ConnectorResolveError) || error.code !== 'CONNECTOR_OBJECT_REQUIRED')
|
|
2513
|
-
throw error;
|
|
2514
|
-
// A generic operation saved before its object was picked has no
|
|
2515
|
-
// definition to emit against. Draft tolerance emits a stable stand-in
|
|
2516
|
-
// under the node's id; merge restores the original when it is untouched
|
|
2517
|
-
// and refuses it otherwise.
|
|
2518
|
-
const id = stepUid(scope, step.name);
|
|
2519
|
-
opts.draft({ nodeId: id, code: error.code, message: error.message });
|
|
2520
|
-
// It leaves on the port every connector action leaves on, so the
|
|
2521
|
-
// no-edit round trip rewires nothing. Its inputs carry what the author
|
|
2522
|
-
// wrote, so an edit to the node (an input, a connection) shows up as a
|
|
2523
|
-
// change and merge refuses it instead of restoring the original.
|
|
2524
|
-
const type = `uipath.connector.${spec.key}.${spec.action}`;
|
|
2525
|
-
scope.nodes.push({
|
|
2526
|
-
id, type, typeVersion: spec.version ?? '1.0.0',
|
|
2527
|
-
...(parentId ? { parentId } : {}), display: { label: step.name },
|
|
2528
|
-
inputs: {
|
|
2529
|
-
draft: true,
|
|
2530
|
-
spec: renderDeep(spec.inputs ?? {}, scope.rename),
|
|
2531
|
-
...(spec.connection !== undefined ? { connection: spec.connection } : {}),
|
|
2532
|
-
...(spec.folder !== undefined ? { folder: spec.folder } : {}),
|
|
2533
|
-
},
|
|
2534
|
-
});
|
|
2535
|
-
for (const t of tails)
|
|
2536
|
-
addEdge(scope, t, id);
|
|
2537
|
-
return [{ node: id, port: successPort(type) }];
|
|
2538
|
-
}
|
|
2539
|
-
// An entry `prepare` synthesized from Integration Service compiles in BPMN only.
|
|
2540
|
-
const noDefinition = synthesizedDefinitionRefusal(rc);
|
|
2541
|
-
if (noDefinition)
|
|
2542
|
-
throw noDefinition;
|
|
2543
|
-
const id = stepUid(scope, step.name);
|
|
2544
|
-
const node = makeNode(rc.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def: rc.v1def });
|
|
2545
|
-
declareListOutputType(node, rc);
|
|
2546
|
-
// A draft that names no connection gets no binding rows either: there
|
|
2547
|
-
// is nothing to bind, and rows with an empty resourceKey would leak into
|
|
2548
|
-
// the merged file through merge's bindings union.
|
|
2549
|
-
let unbound = false;
|
|
2550
|
-
try {
|
|
2551
|
-
requireBindingPair(`connector "${step.name}"`, spec, { key: spec.key, action: spec.action });
|
|
2552
|
-
}
|
|
2553
|
-
catch (error) {
|
|
2554
|
-
if (!opts.draft || !(error instanceof BindingRefusal))
|
|
2555
|
-
throw error;
|
|
2556
|
-
opts.draft({ nodeId: id, code: error.code, message: error.message });
|
|
2557
|
-
unbound = true;
|
|
2558
|
-
}
|
|
2559
|
-
const conn = resolveBinding(opts, spec.connection, 'connection', `connector "${step.name}"`, { key: spec.key, action: spec.action });
|
|
2560
|
-
const folder = resolveBinding(opts, spec.folder, 'folder', `connector "${step.name}"`, { key: spec.key, action: spec.action });
|
|
2561
|
-
// The platform reaches the connection through the flow's `bindings[]`,
|
|
2562
|
-
// never through `inputs.detail` — without this pair the run faults with
|
|
2563
|
-
// `[102010]` (board G-19; see connectionResourceBindings).
|
|
2564
|
-
const { entries: connBindings } = connectionResourceBindings({ def: rc.v1def, connectorKey: rc.connectorKey, connectionId: conn, folderKey: folder }, resourceBindings);
|
|
2565
|
-
if (!unbound)
|
|
2566
|
-
for (const b of connBindings)
|
|
2567
|
-
resourceBindings.set(b.id, b);
|
|
2568
|
-
const connectorInputs = buildConnectorInputs(rc, spec.inputs, {
|
|
2569
|
-
...(opts.draft
|
|
2570
|
-
? { onMissingRequired: (m) => opts.draft({ nodeId: id, code: CONNECTOR_INPUT_MISSING, message: `connector ${rc.nodeType}: ${m}` }) }
|
|
2571
|
-
: {}),
|
|
2572
|
-
dialect: FLOW_EXPR_DIALECT,
|
|
2573
|
-
render: (value) => renderDeep(value, scope.rename),
|
|
2574
|
-
resolutions: opts.library.lookupResolutions ?? {},
|
|
2575
|
-
// Only for the missing-required hint; a test double that is not a full
|
|
2576
|
-
// Library simply gets the plain message.
|
|
2577
|
-
lookups: typeof opts.library.lookupSpecs === 'function'
|
|
2578
|
-
? opts.library.lookupSpecs(spec.key, spec.action, spec.version, spec.object)
|
|
2579
|
-
: {},
|
|
2580
|
-
});
|
|
2581
|
-
const replayInputs = {
|
|
2582
|
-
...(connectorInputs.bodyParameters ?? {}),
|
|
2583
|
-
...(connectorInputs.pathParameters ?? {}),
|
|
2584
|
-
...(connectorInputs.queryParameters ?? {}),
|
|
2585
|
-
};
|
|
2586
|
-
const detail = {
|
|
2587
|
-
connector: rc.connectorKey,
|
|
2588
|
-
method: transportHttpMethod(rc.httpMethod),
|
|
2589
|
-
endpoint: rc.endpoint,
|
|
2590
|
-
// The op's object identity, as a SIBLING of the copy inside
|
|
2591
|
-
// `configuration` — the designer emits both, and every connector node in
|
|
2592
|
-
// a product-exported flow carries it (`curated_create_issue`,
|
|
2593
|
-
// `Company__c`, `send_message_to_channel_v2` on the reference export).
|
|
2594
|
-
// The event path already did; this action path had dropped it.
|
|
2595
|
-
// Conditional because the library defaults a missing
|
|
2596
|
-
// `operation.objectName` to '' — an op that names no object gets no key,
|
|
2597
|
-
// not an empty one.
|
|
2598
|
-
...(rc.objectName ? { objectName: rc.objectName } : {}),
|
|
2599
|
-
inputMetadata: {},
|
|
2600
|
-
errorState: { hasError: false },
|
|
2601
|
-
telemetryData: { connectorKey: rc.connectorKey, connectorName: rc.connectorName },
|
|
2602
|
-
configuration: buildConfiguration(rc.entry, rc.version, replayInputs, id),
|
|
2603
|
-
...(rc.activityTypeId ? { uiPathActivityTypeId: rc.activityTypeId } : {}),
|
|
2604
|
-
...(conn ? { connectionId: conn, connectionResourceId: conn } : {}),
|
|
2605
|
-
...(folder ? { connectionFolderKey: folder } : {}),
|
|
2606
|
-
};
|
|
2607
|
-
// Validate activity inputs against the connector's field list and place
|
|
2608
|
-
// each one in the slot its LOCATION names — body → detail.bodyParameters,
|
|
2609
|
-
// query → detail.queryParameters, path → detail.pathParameters, multipart
|
|
2610
|
-
// → detail.multipartParameters (where the runtime looks) — so
|
|
2611
|
-
// a schema-valid-but-contract-wrong connector node fails at compile with
|
|
2612
|
-
// an actionable message instead of silently at run time.
|
|
2613
|
-
//
|
|
2614
|
-
// `bodyParameters` is emitted unconditionally, as it always has been (3 of
|
|
2615
|
-
// 1,283 deployed connector nodes carry an empty one, so it is a legal
|
|
2616
|
-
// shape); the other three appear only when the op has inputs there, which
|
|
2617
|
-
// is the platform's dominant convention — of those 1,283 nodes, 899 omit
|
|
2618
|
-
// `pathParameters` entirely and NONE carries an empty one. That also keeps
|
|
2619
|
-
// every body-only connector's emission byte-identical to before.
|
|
2620
|
-
Object.assign(detail, connectorInputs);
|
|
2621
|
-
node.inputs = { detail };
|
|
2622
|
-
scope.nodes.push(node);
|
|
2623
|
-
for (const t of tails)
|
|
2624
|
-
addEdge(scope, t, id);
|
|
2625
|
-
return [{ node: id, port: successPort(rc.nodeType) }];
|
|
2626
|
-
}
|
|
2627
2491
|
if (spec.kind === 'waitEvent') {
|
|
2628
2492
|
if (!opts.library)
|
|
2629
2493
|
throw new Error(`waitForEvent "${step.name}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.50.0",
|
|
4
4
|
"description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.uipath.com/maestro",
|
|
@@ -92,5 +92,5 @@
|
|
|
92
92
|
"@types/node": "^22.7.0",
|
|
93
93
|
"esbuild": "^0.28.1"
|
|
94
94
|
},
|
|
95
|
-
"gitref": "
|
|
95
|
+
"gitref": "d674fc1a9ed1b8aa61dcc0b30a95a434503eb61b"
|
|
96
96
|
}
|