@uipath/maestro-builder-sdk 6.49.3 → 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 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, connectorSteps, opts, diags);
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
- /** Reject a wrapper hop when the runtime shape of a list has been measured. */
1381
- function checkLoopListEnvelope(loop, connectors, opts, diags) {
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 || !opts.listEnvelope)
1380
+ if (!read)
1384
1381
  return;
1385
1382
  const [, step, field] = read;
1386
- const connector = connectors.get(step);
1387
- if (!connector)
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
- const port = typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
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: declaredErrorPortRefusal(step.name, impl.typeName, impl.version),
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 && !NO_DEFINITION_ERROR_PORT_KINDS.has(spec.kind);
3026
+ : !bundledType;
3029
3027
  if (noPort) {
3030
3028
  diags.push({
3031
3029
  level: 'error',
@@ -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,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 });
@@ -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,3 @@
1
+ /** The connector node family, as flow-node-registry.ts lists it: an Integration Service connector operation. */
2
+ import type { FlowNodeFamily } from '../../../flow-node-type.js';
3
+ export declare const connectorFlowFamily: FlowNodeFamily;
@@ -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
+ };
@@ -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
- /** A required connector input left empty — the one draft code with no error class of its own. */
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 { buildConnectorInputs, connectorInputErrors as coreConnectorInputErrors, transportHttpMethod } from './core/connector-inputs.js';
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
- /** A required connector input left empty — the one draft code with no error class of its own. */
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
- if (out !== value && out !== null && typeof out === 'object') {
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
- def = resolveContributionDefinition(contribution.definition, version, where);
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, requestedVersion: version, def, contribution, bound, tails, scope, parentId,
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.49.3",
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": "0187c4bea37cd0a8fd78aae6125b040583097f96"
95
+ "gitref": "d674fc1a9ed1b8aa61dcc0b30a95a434503eb61b"
96
96
  }