@uipath/maestro-builder-sdk 6.49.3 → 6.50.1

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/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}). */