@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/case/check-cli.js +10 -9
- package/dist/case/compile-cli.d.ts +21 -0
- package/dist/case/compile-cli.js +26 -3
- package/dist/check.d.ts +5 -0
- package/dist/check.js +50 -52
- package/dist/cli-library.js +8 -0
- package/dist/core/binding-messages.d.ts +2 -0
- package/dist/core/binding-messages.js +5 -0
- package/dist/core/bindings.d.ts +42 -3
- package/dist/core/bindings.js +67 -5
- package/dist/core/connector-inputs.d.ts +2 -0
- package/dist/core/connector-inputs.js +2 -0
- package/dist/core/nodes/node-type.d.ts +6 -1
- package/dist/core/nodes/versioned.d.ts +7 -0
- package/dist/core/nodes/versioned.js +11 -0
- package/dist/decompile.js +2 -65
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +59 -0
- package/dist/nodes/connector/flow/family.d.ts +3 -0
- package/dist/nodes/connector/flow/family.js +8 -0
- package/dist/nodes/connector/flow/node-type.d.ts +25 -0
- package/dist/nodes/connector/flow/node-type.js +294 -0
- package/dist/serialize.d.ts +2 -3
- package/dist/serialize.js +46 -182
- package/package.json +2 -2
package/dist/decompile.js
CHANGED
|
@@ -774,7 +774,8 @@ function emitRegisteredSpec(node, imp, inputNames, graph) {
|
|
|
774
774
|
};
|
|
775
775
|
const options = opts((call.options ?? []).map(([k, v]) => [k, v]));
|
|
776
776
|
const args = [...(call.positional ?? []), ...(options ? [options] : [])];
|
|
777
|
-
return `${imp.use(call.factory)}(${args.join(', ')})
|
|
777
|
+
return `${imp.use(call.factory)}(${args.join(', ')})`
|
|
778
|
+
+ (call.comment !== undefined ? ` /* ${commentText(call.comment)} */` : '');
|
|
778
779
|
}
|
|
779
780
|
/**
|
|
780
781
|
* Emit the action-spec argument for a `.step(id, <spec>)` call — e.g.
|
|
@@ -789,11 +790,6 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
|
|
|
789
790
|
return lifted;
|
|
790
791
|
switch (node.type) {
|
|
791
792
|
default:
|
|
792
|
-
// A connector action node: `uipath.connector.<key>.<action>` (but NOT the
|
|
793
|
-
// event nodes `uipath.connector.event.*` / `.trigger.*`, which are triggers).
|
|
794
|
-
if (node.type.startsWith('uipath.connector.') && !node.type.startsWith('uipath.connector.event.') && !node.type.startsWith('uipath.connector.trigger.')) {
|
|
795
|
-
return emitConnector(node, imp, inputNames, o);
|
|
796
|
-
}
|
|
797
793
|
// A mid-flow connector EVENT wait (`uipath.connector.event.<key>.<event>`
|
|
798
794
|
// placed as a step rather than as the start node).
|
|
799
795
|
if (node.type.startsWith('uipath.connector.event.')) {
|
|
@@ -809,30 +805,6 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
|
|
|
809
805
|
return unsupportedSpec(node, imp, o);
|
|
810
806
|
}
|
|
811
807
|
}
|
|
812
|
-
/**
|
|
813
|
-
* Split a connector `nodeType` (`uipath.connector.<key>.<action>`) into key +
|
|
814
|
-
* action. Mirrors `core/connectors.ts`'s `splitConnectorNodeType`.
|
|
815
|
-
*/
|
|
816
|
-
function splitConnector(nodeType) {
|
|
817
|
-
const rest = nodeType.replace(/^uipath\.connector\./, '');
|
|
818
|
-
const dot = rest.lastIndexOf('.');
|
|
819
|
-
if (dot <= 0)
|
|
820
|
-
return undefined;
|
|
821
|
-
return { key: rest.slice(0, dot), action: rest.slice(dot + 1) };
|
|
822
|
-
}
|
|
823
|
-
/** Pull `objectName` out of a connector's `=jsonString:` configuration blob, if present. */
|
|
824
|
-
function connectorObjectName(configuration) {
|
|
825
|
-
if (typeof configuration !== 'string' || !configuration.startsWith('=jsonString:'))
|
|
826
|
-
return undefined;
|
|
827
|
-
try {
|
|
828
|
-
const cfg = JSON.parse(configuration.slice('=jsonString:'.length));
|
|
829
|
-
const name = cfg?.instanceParameters?.objectName ?? cfg?.objectName;
|
|
830
|
-
return typeof name === 'string' ? name : undefined;
|
|
831
|
-
}
|
|
832
|
-
catch {
|
|
833
|
-
return undefined;
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
808
|
/**
|
|
837
809
|
* Invert a connector action node into a `connector(key, action, inputs, opts)`
|
|
838
810
|
* call. serialize routes author inputs into `detail.bodyParameters` /
|
|
@@ -948,41 +920,6 @@ function emitEventSubscription(node, imp, inputNames) {
|
|
|
948
920
|
['folder', folder !== undefined ? tsLiteral(folder) : undefined],
|
|
949
921
|
]) + unboundMarker(node, { connection: conn, folder }, connector, event);
|
|
950
922
|
}
|
|
951
|
-
function emitConnector(node, imp, inputNames, o) {
|
|
952
|
-
const split = splitConnector(node.type);
|
|
953
|
-
const detail = (node.inputs?.detail ?? {});
|
|
954
|
-
if (!split)
|
|
955
|
-
return unsupportedSpec(node, imp, o);
|
|
956
|
-
const slots = ['bodyParameters', 'queryParameters', 'pathParameters'];
|
|
957
|
-
const flat = [];
|
|
958
|
-
for (const s of slots) {
|
|
959
|
-
const m = detail[s];
|
|
960
|
-
if (m && typeof m === 'object' && !Array.isArray(m)) {
|
|
961
|
-
for (const [k, v] of Object.entries(m))
|
|
962
|
-
flat.push([k, emitVal(v, imp, inputNames)]);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
const multipart = detail.multipartParameters;
|
|
966
|
-
if (Array.isArray(multipart)) {
|
|
967
|
-
for (const part of multipart) {
|
|
968
|
-
if (typeof part?.name === 'string' && Object.prototype.hasOwnProperty.call(part, 'value')) {
|
|
969
|
-
flat.push([part.name, emitVal(part.value, imp, inputNames)]);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
const connection = typeof detail.connectionId === 'string' ? detail.connectionId : undefined;
|
|
974
|
-
const folder = typeof detail.connectionFolderKey === 'string' ? detail.connectionFolderKey : undefined;
|
|
975
|
-
const object = connectorObjectName(detail.configuration);
|
|
976
|
-
const optsSrc = opts([
|
|
977
|
-
['connection', connection !== undefined ? tsLiteral(connection) : undefined],
|
|
978
|
-
['folder', folder !== undefined ? tsLiteral(folder) : undefined],
|
|
979
|
-
['object', object !== undefined ? tsLiteral(object) : undefined],
|
|
980
|
-
]);
|
|
981
|
-
const inputsSrc = objLit(flat) || '{}';
|
|
982
|
-
const args = [tsLiteral(split.key), tsLiteral(split.action), inputsSrc, ...(optsSrc ? [optsSrc] : [])];
|
|
983
|
-
return `${imp.use('connector')}(${args.join(', ')})`
|
|
984
|
-
+ unboundMarker(node, { connection, folder }, split.key, split.action);
|
|
985
|
-
}
|
|
986
923
|
/** Best-effort placeholder for a node type the reverse-map does not yet cover. */
|
|
987
924
|
function unsupportedSpec(node, imp, o) {
|
|
988
925
|
// The file's own definition for this node — the manifest the platform served.
|
|
@@ -10,6 +10,7 @@ import { NodeTypeRegistry } from './core/nodes/registry.js';
|
|
|
10
10
|
import { hasBundledDefinition } from './flow-definitions.js';
|
|
11
11
|
import { agentFlowFamily } from './nodes/agent/flow/family.js';
|
|
12
12
|
import { aiPatternsFlowFamily } from './nodes/ai-patterns/flow/family.js';
|
|
13
|
+
import { connectorFlowFamily } from './nodes/connector/flow/family.js';
|
|
13
14
|
import { conversationFlowFamily } from './nodes/conversation/flow/family.js';
|
|
14
15
|
import { dataFabricFlowFamily } from './nodes/data-fabric/flow/family.js';
|
|
15
16
|
import { delayFlowFamily } from './nodes/delay/flow/family.js';
|
|
@@ -22,5 +23,5 @@ import { queueFlowFamily } from './nodes/queue/flow/family.js';
|
|
|
22
23
|
import { scriptFlowFamily } from './nodes/script/flow/family.js';
|
|
23
24
|
import { transformFlowFamily } from './nodes/transform/flow/family.js';
|
|
24
25
|
/** The families, in no significant order. Adding one is the whole of registering it. */
|
|
25
|
-
export const FLOW_NODE_FAMILIES = [agentFlowFamily, aiPatternsFlowFamily, conversationFlowFamily, dataFabricFlowFamily, delayFlowFamily, documentFlowFamily, hitlFlowFamily, httpFlowFamily, mockFlowFamily, processFlowFamily, queueFlowFamily, scriptFlowFamily, transformFlowFamily];
|
|
26
|
+
export const FLOW_NODE_FAMILIES = [agentFlowFamily, aiPatternsFlowFamily, connectorFlowFamily, conversationFlowFamily, dataFabricFlowFamily, delayFlowFamily, documentFlowFamily, hitlFlowFamily, httpFlowFamily, mockFlowFamily, processFlowFamily, queueFlowFamily, scriptFlowFamily, transformFlowFamily];
|
|
26
27
|
export const FLOW_NODE_TYPES = new NodeTypeRegistry('flow', FLOW_NODE_FAMILIES, { hasBundledDefinition });
|
package/dist/flow-node-type.d.ts
CHANGED
|
@@ -87,6 +87,19 @@ export interface FlowNodeContribution extends Omit<NodeContribution, 'definition
|
|
|
87
87
|
/** Display fields set over the definition's, after `shape: 'circle'` (a tool's `description`). */
|
|
88
88
|
readonly display?: Readonly<Record<string, unknown>>;
|
|
89
89
|
}>;
|
|
90
|
+
/**
|
|
91
|
+
* In a draft compile ({@link FlowSerializeContext.drafting}), the node the type
|
|
92
|
+
* cannot build — a generic connector operation saved before its object was
|
|
93
|
+
* picked has no definition — written as this stand-in: the given type,
|
|
94
|
+
* `typeVersion` and inputs (written as given), the step's label, no definition,
|
|
95
|
+
* `ui` or `model`. Merge restores the original when it is untouched. Everything
|
|
96
|
+
* else in the contribution is ignored but `port`.
|
|
97
|
+
*/
|
|
98
|
+
standIn?: {
|
|
99
|
+
readonly type: string;
|
|
100
|
+
readonly typeVersion: string;
|
|
101
|
+
readonly inputs: Readonly<Record<string, unknown>>;
|
|
102
|
+
};
|
|
90
103
|
}
|
|
91
104
|
/** What `serialize` may ask of the serializer, for one placement. */
|
|
92
105
|
export interface FlowSerializeContext {
|
|
@@ -213,6 +226,21 @@ export interface FlowSerializeContext {
|
|
|
213
226
|
emitted(typeName: string, version: string): unknown;
|
|
214
227
|
/** The connector library serialize was given, for a type that resolves a connector operation. */
|
|
215
228
|
readonly library: SerializeOpts['library'];
|
|
229
|
+
/**
|
|
230
|
+
* Render a value and every `Expr` nested in its objects and arrays, as a
|
|
231
|
+
* connector's inputs are. A `lookup()` token is refused: it belongs only where a
|
|
232
|
+
* connector input resolves it (the whole value of a field, or an array element).
|
|
233
|
+
* An object result is a JSON copy that placement writes as is, so a `lit()`'s
|
|
234
|
+
* contents are not rendered a second time.
|
|
235
|
+
*/
|
|
236
|
+
renderDeep(value: unknown): unknown;
|
|
237
|
+
/**
|
|
238
|
+
* Whether this is a draft compile (merge's): a refusal the author can still fix
|
|
239
|
+
* is reported through {@link draft} and the step emitted as far as it can be.
|
|
240
|
+
*/
|
|
241
|
+
readonly drafting: boolean;
|
|
242
|
+
/** Report a draft finding on this node: what a non-draft compile would have refused. Only when {@link drafting}. */
|
|
243
|
+
draft(code: string, message: string): void;
|
|
216
244
|
}
|
|
217
245
|
/** What `deserialize` may ask of the decompiler, for one serialized node. */
|
|
218
246
|
export interface FlowDeserializeContext {
|
|
@@ -349,6 +377,22 @@ export interface FlowCheckContext {
|
|
|
349
377
|
* An explicit id equal to one another node gets is reported once the whole
|
|
350
378
|
* file is checked (`INLINE_AGENT_RESOURCE_NODE_ID`).
|
|
351
379
|
*/
|
|
380
|
+
/**
|
|
381
|
+
* Run the rules every Integration Service operation shares — its lookups, its
|
|
382
|
+
* schema readiness, its contract against the library facts check was given
|
|
383
|
+
* (`options`), and BINDING_MISSING — on this step. Their codes are the
|
|
384
|
+
* pipeline's, shared with an event subscription's, so they belong to no
|
|
385
|
+
* family catalog; each is reported at its own severity.
|
|
386
|
+
*/
|
|
387
|
+
connector(spec: {
|
|
388
|
+
key: string;
|
|
389
|
+
action: string;
|
|
390
|
+
version?: string;
|
|
391
|
+
object?: string;
|
|
392
|
+
connection?: unknown;
|
|
393
|
+
folder?: unknown;
|
|
394
|
+
inputs?: unknown;
|
|
395
|
+
}): void;
|
|
352
396
|
readonly resources: {
|
|
353
397
|
/** An id compile derives for one of this step's resources, and what it is, for that report. */
|
|
354
398
|
derivedNodeId(id: string, what: string): void;
|
|
@@ -461,6 +505,21 @@ export interface FlowNodeType<Spec> extends NodeType<Spec, FlowNodeContribution,
|
|
|
461
505
|
* the definition, an exit of the type, nor derived.
|
|
462
506
|
*/
|
|
463
507
|
derivesPort?(spec: Spec, port: string): boolean;
|
|
508
|
+
/**
|
|
509
|
+
* The measured runtime shape of this step's LIST output, when it has one: a
|
|
510
|
+
* bare array (`envelope: null`) or an object holding it under `envelope`. A
|
|
511
|
+
* loop over the output checks its read against it (LIST_OUTPUT_WRAPPER).
|
|
512
|
+
* `options` are check's (a connector's measured list envelopes among them).
|
|
513
|
+
*/
|
|
514
|
+
listShape?(spec: Spec, options: CheckOpts): {
|
|
515
|
+
envelope: string | null;
|
|
516
|
+
} | undefined;
|
|
517
|
+
/**
|
|
518
|
+
* The definition serialize will be supplied for a per-tenant type, when check's
|
|
519
|
+
* `options` can name it (a connector's, from the library). Check passes it to
|
|
520
|
+
* `errorPort`, so the two rungs give one answer; undefined when it cannot.
|
|
521
|
+
*/
|
|
522
|
+
checkDefinition?(spec: Spec, options: CheckOpts): unknown;
|
|
464
523
|
}
|
|
465
524
|
/** A Flow node type with several versions. */
|
|
466
525
|
export type FlowVersionedNodeType<Spec> = VersionedNodeType<Spec, FlowNodeContribution, FlowContexts, FlowNodeType<Spec>>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { connectorNodeType } from './node-type.js';
|
|
2
|
+
export const connectorFlowFamily = {
|
|
3
|
+
id: 'connector',
|
|
4
|
+
title: 'Integration Service connector',
|
|
5
|
+
types: [connectorNodeType],
|
|
6
|
+
// Its rules' codes are the pipeline's, shared with an event subscription (ctx.connector).
|
|
7
|
+
diagnostics: {},
|
|
8
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An Integration Service connector operation — `connector(key, action, inputs,
|
|
3
|
+
* opts)`, a node type per operation: `uipath.connector.<connectorKey>.<action>`.
|
|
4
|
+
* The type owns every name under `uipath.connector.` that is an operation
|
|
5
|
+
* (`typeNameMatches`): not an event subscription's `uipath.connector.event.*` or a
|
|
6
|
+
* trigger's `uipath.connector.trigger.*`, which belong to `waitForEvent()` and
|
|
7
|
+
* `onEvent()`. Per-tenant: the definition is the library entry's, and so is its
|
|
8
|
+
* version (`versionedByDefinition`; most operations are at 1.0.0, some at 4.0.0).
|
|
9
|
+
*
|
|
10
|
+
* The spec and `connector()` stay in core/actions.ts: Case imports the spec, and
|
|
11
|
+
* Case may not import a node family. The rules the connector shares with an event
|
|
12
|
+
* subscription — lookups, schema readiness, the library contract, the binding
|
|
13
|
+
* labels — stay in core and run through `ctx.connector`, with their codes.
|
|
14
|
+
*
|
|
15
|
+
* Moved from serialize.ts / check.ts / decompile.ts in Phase 4 of
|
|
16
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; serialized output, printed source,
|
|
17
|
+
* codes and messages unchanged except for the three deliberate changes its 4o
|
|
18
|
+
* entry lists.
|
|
19
|
+
*/
|
|
20
|
+
import type { ActionSpec } from '../../../core/actions.js';
|
|
21
|
+
import type { FlowVersionedNodeType } from '../../../flow-node-type.js';
|
|
22
|
+
export type ConnectorSpec = Extract<ActionSpec, {
|
|
23
|
+
kind: 'connector';
|
|
24
|
+
}>;
|
|
25
|
+
export declare const connectorNodeType: FlowVersionedNodeType<ConnectorSpec>;
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { BindingRefusal, bindingMissingMessage, missingBindingLabels } from '../../../core/binding-messages.js';
|
|
2
|
+
import { prepareCommand } from '../../../core/cli-spelling.js';
|
|
3
|
+
import { buildConfiguration } from '../../../core/connector-configuration.js';
|
|
4
|
+
import { CONNECTOR_INPUT_MISSING, FLOW_EXPR_DIALECT, buildConnectorInputs, transportHttpMethod } from '../../../core/connector-inputs.js';
|
|
5
|
+
import { ConnectorResolveError } from '../../../core/connector-resolve-error.js';
|
|
6
|
+
import { definitionHasErrorPort } from '../../../core/error-port.js';
|
|
7
|
+
import { Expr } from '../../../core/expr.js';
|
|
8
|
+
import { jsonClone } from '../../../core/json-clone.js';
|
|
9
|
+
import { subNodeId } from '../../../core/node-id-placeholder.js';
|
|
10
|
+
import { synthesizedDefinitionRefusal } from '../../../core/synthesized-definition.js';
|
|
11
|
+
import { tsLiteral, tsObject } from '../../../core/ts-literal.js';
|
|
12
|
+
const CONNECTOR_PREFIX = 'uipath.connector.';
|
|
13
|
+
/** `<key>.<action>` after the prefix, split on the last dot; undefined for an event, a trigger, or a name with no action. */
|
|
14
|
+
function splitOperation(typeName) {
|
|
15
|
+
if (!typeName.startsWith(CONNECTOR_PREFIX))
|
|
16
|
+
return undefined;
|
|
17
|
+
const rest = typeName.slice(CONNECTOR_PREFIX.length);
|
|
18
|
+
if (rest.startsWith('event.') || rest.startsWith('trigger.'))
|
|
19
|
+
return undefined;
|
|
20
|
+
const dot = rest.lastIndexOf('.');
|
|
21
|
+
if (dot <= 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
return { key: rest.slice(0, dot), action: rest.slice(dot + 1) };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The node's `output` slot for a LIST operation, declared as the ARRAY it is when
|
|
27
|
+
* the ledger has MEASURED that it is one ([#203](https://github.com/UiPath/flow-builder-sdk/issues/203)).
|
|
28
|
+
*
|
|
29
|
+
* Every connector definition declares `outputs.output.type: 'object'` — list
|
|
30
|
+
* operations included, and **0 of 544 deployed connector nodes declare `array`
|
|
31
|
+
* anywhere** (card CLEANUP's corpus scan). For most of the library that is the
|
|
32
|
+
* honest thing to emit, because the SDK does not know better. For an operation in
|
|
33
|
+
* the list-envelope ledger it does:
|
|
34
|
+
*
|
|
35
|
+
* · `envelope: null` (a measured BARE array) ⇒ **`type: 'array'`**;
|
|
36
|
+
* · a measured WRAPPER ⇒ `object`, unchanged — the value IS an object then;
|
|
37
|
+
* · UNDECLARED ⇒ untouched, which is #202's status quo.
|
|
38
|
+
*
|
|
39
|
+
* Why this matters beyond tidiness: `typeAgentInputSchemas` builds an agent's
|
|
40
|
+
* pydantic input model from these declarations, and card ga7's scored run r2 died
|
|
41
|
+
* at `AGENT_STARTUP.INPUT_VALIDATION_ERROR` because a Data Service list node said
|
|
42
|
+
* `object` and the runtime sent 8 rows. #202 made that property TYPELESS, which
|
|
43
|
+
* unblocked the agent without asserting anything; this says the true thing.
|
|
44
|
+
*
|
|
45
|
+
* ## And deliberately NO item schema
|
|
46
|
+
*
|
|
47
|
+
* #203 suggested emitting the item fields alongside. They are not emitted, and the
|
|
48
|
+
* reason is a measurement rather than caution: a list operation's declared fields
|
|
49
|
+
* are **neither a superset nor a subset** of the real response (card LT, three live
|
|
50
|
+
* responses — ServiceNow `incident` declares 0 of the 103 fields it returns; Entra
|
|
51
|
+
* declares 20, returns 15, misses 2; Jira declares 253, returns 116, misses 42).
|
|
52
|
+
* Asserting them into an agent's pydantic model would reject real rows — the exact
|
|
53
|
+
* fault class above, moved rather than fixed. The library lift stays a READ-checking
|
|
54
|
+
* aid, where it fails open by design (lesson 26). The emitted bytes claim only what
|
|
55
|
+
* is certain: this is an array.
|
|
56
|
+
*/
|
|
57
|
+
function listOutputs(rc, nodeId) {
|
|
58
|
+
if (!rc.listEnvelope || rc.listEnvelope.envelope !== null)
|
|
59
|
+
return undefined;
|
|
60
|
+
const out = rc.v1def?.outputDefinition?.output;
|
|
61
|
+
if (!out || typeof out !== 'object' || out.type !== 'object')
|
|
62
|
+
return undefined;
|
|
63
|
+
return { output: { ...subNodeId(jsonClone(out), nodeId), type: 'array' } };
|
|
64
|
+
}
|
|
65
|
+
function serializeConnector(spec, ctx) {
|
|
66
|
+
const step = ctx.step;
|
|
67
|
+
if (!ctx.library)
|
|
68
|
+
throw new Error(`connector "${step}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
|
|
69
|
+
let rc;
|
|
70
|
+
try {
|
|
71
|
+
rc = ctx.library.resolve(spec.key, spec.action, spec.version, spec.object);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (!ctx.drafting || !(error instanceof ConnectorResolveError) || error.code !== 'CONNECTOR_OBJECT_REQUIRED')
|
|
75
|
+
throw error;
|
|
76
|
+
// A generic operation saved before its object was picked has no definition
|
|
77
|
+
// to emit against. Draft tolerance emits a stable stand-in under the node's
|
|
78
|
+
// id; merge restores the original when it is untouched and refuses it
|
|
79
|
+
// otherwise. It leaves on the port every connector action leaves on, so the
|
|
80
|
+
// no-edit round trip rewires nothing. Its inputs carry what the author wrote,
|
|
81
|
+
// so an edit to the node (an input, a connection) shows up as a change and
|
|
82
|
+
// merge refuses it instead of restoring the original.
|
|
83
|
+
ctx.draft(error.code, error.message);
|
|
84
|
+
return {
|
|
85
|
+
inputs: {},
|
|
86
|
+
port: 'output',
|
|
87
|
+
standIn: {
|
|
88
|
+
type: `${CONNECTOR_PREFIX}${spec.key}.${spec.action}`,
|
|
89
|
+
typeVersion: spec.version ?? '1.0.0',
|
|
90
|
+
inputs: {
|
|
91
|
+
draft: true,
|
|
92
|
+
spec: ctx.renderDeep(spec.inputs ?? {}),
|
|
93
|
+
...(spec.connection !== undefined ? { connection: spec.connection } : {}),
|
|
94
|
+
...(spec.folder !== undefined ? { folder: spec.folder } : {}),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// An entry `prepare` synthesized from Integration Service compiles in BPMN only.
|
|
100
|
+
const noDefinition = synthesizedDefinitionRefusal(rc);
|
|
101
|
+
if (noDefinition)
|
|
102
|
+
throw noDefinition;
|
|
103
|
+
const where = `connector "${step}"`;
|
|
104
|
+
const target = { key: spec.key, action: spec.action };
|
|
105
|
+
// A connector-bound node needs both labels (#890); check says so first
|
|
106
|
+
// (BINDING_MISSING). A draft that names no connection gets no binding rows
|
|
107
|
+
// either: there is nothing to bind, and rows with an empty resourceKey would
|
|
108
|
+
// leak into the merged file through merge's bindings union.
|
|
109
|
+
let unbound = false;
|
|
110
|
+
const missing = missingBindingLabels(spec);
|
|
111
|
+
if (missing.length > 0) {
|
|
112
|
+
const refusal = new BindingRefusal('BINDING_MISSING', bindingMissingMessage(where, missing, target));
|
|
113
|
+
if (!ctx.drafting)
|
|
114
|
+
throw refusal;
|
|
115
|
+
ctx.draft(refusal.code, refusal.message);
|
|
116
|
+
unbound = true;
|
|
117
|
+
}
|
|
118
|
+
const conn = ctx.resolveBinding(spec.connection, 'connection', where, target);
|
|
119
|
+
const folder = ctx.resolveBinding(spec.folder, 'folder', where, target);
|
|
120
|
+
// The platform reaches the connection through the flow's `bindings[]`, never
|
|
121
|
+
// through `inputs.detail` — without this pair the run faults with `[102010]`
|
|
122
|
+
// (board G-19).
|
|
123
|
+
if (!unbound) {
|
|
124
|
+
ctx.bindConnection({
|
|
125
|
+
model: rc.v1def?.model,
|
|
126
|
+
connectorKey: rc.connectorKey, connectionId: conn,
|
|
127
|
+
...(folder !== undefined ? { folderKey: folder } : {}),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const library = ctx.library;
|
|
131
|
+
const connectorInputs = buildConnectorInputs(rc, spec.inputs, {
|
|
132
|
+
...(ctx.drafting
|
|
133
|
+
? { onMissingRequired: (m) => ctx.draft(CONNECTOR_INPUT_MISSING, `connector ${rc.nodeType}: ${m}`) }
|
|
134
|
+
: {}),
|
|
135
|
+
dialect: FLOW_EXPR_DIALECT,
|
|
136
|
+
render: (value) => ctx.renderDeep(value),
|
|
137
|
+
resolutions: library.lookupResolutions ?? {},
|
|
138
|
+
// Only for the missing-required hint; a test double that is not a full
|
|
139
|
+
// Library simply gets the plain message.
|
|
140
|
+
lookups: typeof library.lookupSpecs === 'function'
|
|
141
|
+
? library.lookupSpecs(spec.key, spec.action, spec.version, spec.object)
|
|
142
|
+
: {},
|
|
143
|
+
});
|
|
144
|
+
const replayInputs = {
|
|
145
|
+
...(connectorInputs.bodyParameters ?? {}),
|
|
146
|
+
...(connectorInputs.pathParameters ?? {}),
|
|
147
|
+
...(connectorInputs.queryParameters ?? {}),
|
|
148
|
+
};
|
|
149
|
+
const detail = {
|
|
150
|
+
connector: rc.connectorKey,
|
|
151
|
+
method: transportHttpMethod(rc.httpMethod),
|
|
152
|
+
endpoint: rc.endpoint,
|
|
153
|
+
// The op's object identity, as a SIBLING of the copy inside `configuration`
|
|
154
|
+
// — the designer emits both, and every connector node in a product-exported
|
|
155
|
+
// flow carries it. Conditional because the library defaults a missing
|
|
156
|
+
// `operation.objectName` to '' — an op that names no object gets no key.
|
|
157
|
+
...(rc.objectName ? { objectName: rc.objectName } : {}),
|
|
158
|
+
inputMetadata: {},
|
|
159
|
+
errorState: { hasError: false },
|
|
160
|
+
telemetryData: { connectorKey: rc.connectorKey, connectorName: rc.connectorName },
|
|
161
|
+
configuration: buildConfiguration(rc.entry, rc.version, replayInputs, ctx.nodeId),
|
|
162
|
+
...(rc.activityTypeId ? { uiPathActivityTypeId: rc.activityTypeId } : {}),
|
|
163
|
+
...(conn ? { connectionId: conn, connectionResourceId: conn } : {}),
|
|
164
|
+
...(folder ? { connectionFolderKey: folder } : {}),
|
|
165
|
+
};
|
|
166
|
+
// Each input in the slot its LOCATION names — body → detail.bodyParameters,
|
|
167
|
+
// query → queryParameters, path → pathParameters, multipart →
|
|
168
|
+
// multipartParameters — checked against the operation's field list.
|
|
169
|
+
// `bodyParameters` is emitted unconditionally (3 of 1,283 deployed connector
|
|
170
|
+
// nodes carry an empty one); the others only when the op has inputs there.
|
|
171
|
+
Object.assign(detail, connectorInputs);
|
|
172
|
+
// The node is the operation the author named (`rc.nodeType`); a definition
|
|
173
|
+
// naming another — a library entry that borrowed one, as a synthetic test
|
|
174
|
+
// library does — is written under that name, so the node and its
|
|
175
|
+
// definitions[] entry agree.
|
|
176
|
+
const v1def = rc.v1def;
|
|
177
|
+
const def = v1def.nodeType === rc.nodeType ? v1def : { ...v1def, nodeType: rc.nodeType };
|
|
178
|
+
const outputs = listOutputs(rc, ctx.nodeId);
|
|
179
|
+
return {
|
|
180
|
+
definition: { source: 'snapshot', nodeType: def.nodeType, version: def.version, manifest: def },
|
|
181
|
+
inputs: { detail },
|
|
182
|
+
// Every connector operation leaves on `output`, named: a definition with no
|
|
183
|
+
// handles has no default to fall back on.
|
|
184
|
+
port: 'output',
|
|
185
|
+
...(outputs !== undefined ? { outputs } : {}),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/** A connector input's value as decompile reads it: an expression back to an `Expr`, wire text as text, any other JSON as is. */
|
|
189
|
+
function liftValue(v, ctx) {
|
|
190
|
+
const raw = Array.isArray(v) ? undefined : ctx.sourceText(v);
|
|
191
|
+
if (raw === undefined)
|
|
192
|
+
return v;
|
|
193
|
+
return raw.startsWith('=js:') ? new Expr(raw.slice(4)) : raw;
|
|
194
|
+
}
|
|
195
|
+
/** Pull `objectName` out of a connector's `=jsonString:` configuration blob, if present. */
|
|
196
|
+
function configurationObjectName(configuration) {
|
|
197
|
+
if (typeof configuration !== 'string' || !configuration.startsWith('=jsonString:'))
|
|
198
|
+
return undefined;
|
|
199
|
+
try {
|
|
200
|
+
const cfg = JSON.parse(configuration.slice('=jsonString:'.length));
|
|
201
|
+
const name = cfg?.instanceParameters?.objectName ?? cfg?.objectName;
|
|
202
|
+
return typeof name === 'string' ? name : undefined;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function liftConnector(node, ctx) {
|
|
209
|
+
const n = node;
|
|
210
|
+
const split = splitOperation(n.type);
|
|
211
|
+
if (!split)
|
|
212
|
+
return undefined;
|
|
213
|
+
const detail = (n.inputs?.detail ?? {});
|
|
214
|
+
const inputs = [];
|
|
215
|
+
for (const slot of ['bodyParameters', 'queryParameters', 'pathParameters']) {
|
|
216
|
+
const m = detail[slot];
|
|
217
|
+
if (m && typeof m === 'object' && !Array.isArray(m)) {
|
|
218
|
+
for (const [k, v] of Object.entries(m))
|
|
219
|
+
inputs.push([k, liftValue(v, ctx)]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const multipart = detail.multipartParameters;
|
|
223
|
+
if (Array.isArray(multipart)) {
|
|
224
|
+
for (const part of multipart) {
|
|
225
|
+
if (typeof part?.name === 'string' && Object.prototype.hasOwnProperty.call(part, 'value')) {
|
|
226
|
+
inputs.push([part.name, liftValue(part.value, ctx)]);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const connection = typeof detail.connectionId === 'string' ? detail.connectionId : undefined;
|
|
231
|
+
const folder = typeof detail.connectionFolderKey === 'string' ? detail.connectionFolderKey : undefined;
|
|
232
|
+
// A connection the file never picked (`connectionId: null`): the source cannot
|
|
233
|
+
// invent labels for it, and compile refuses a node without both
|
|
234
|
+
// (BINDING_MISSING, flow-builder-sdk#890), so the source says so at the node
|
|
235
|
+
// and the run warns once.
|
|
236
|
+
const missing = missingBindingLabels({ connection, folder });
|
|
237
|
+
if (missing.length > 0) {
|
|
238
|
+
ctx.warn(`node ${tsLiteral(n.id)} (${n.type}) has no ${missing.join(' or ')} in the .flow (an unbound draft). `
|
|
239
|
+
+ `The source leaves ${missing.length > 1 ? 'them' : 'it'} out, and compile refuses the node (BINDING_MISSING) `
|
|
240
|
+
+ `until it names both labels: run \`${prepareCommand(split.key, split.action)}\`, then pass `
|
|
241
|
+
+ `\`connection\` and \`folder\`.`);
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
kind: 'connector', ...split, inputs, connection, folder,
|
|
245
|
+
object: configurationObjectName(detail.configuration),
|
|
246
|
+
missing,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function printConnector(l, ctx) {
|
|
250
|
+
// A present input whose value is `undefined` (the inputs check accepts one, and
|
|
251
|
+
// serialize keeps it in memory) prints as `undefined`, as it did before the port.
|
|
252
|
+
const value = (v) => (v === undefined ? 'undefined' : v instanceof Expr ? ctx.value(v) : ctx.json(v));
|
|
253
|
+
const optional = (v) => (v === undefined ? undefined : ctx.string(v));
|
|
254
|
+
return {
|
|
255
|
+
factory: 'connector',
|
|
256
|
+
positional: [ctx.string(l.key), ctx.string(l.action), tsObject(l.inputs.map(([k, v]) => [k, value(v)])) || '{}'],
|
|
257
|
+
options: [
|
|
258
|
+
['connection', optional(l.connection)],
|
|
259
|
+
['folder', optional(l.folder)],
|
|
260
|
+
['object', optional(l.object)],
|
|
261
|
+
],
|
|
262
|
+
...(l.missing.length > 0
|
|
263
|
+
? { comment: `TODO: bind ${l.missing.join(' and ')}; compile refuses this node until it has both labels (BINDING_MISSING)` }
|
|
264
|
+
: {}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const connector10 = {
|
|
268
|
+
kind: 'connector', typeName: `${CONNECTOR_PREFIX}*`, version: '1.0.0', factory: 'connector',
|
|
269
|
+
definition: 'per-tenant',
|
|
270
|
+
// The library's definition answers, in serialize and, given a library, in check
|
|
271
|
+
// (checkDefinition). Without one check assumes the port every operation the
|
|
272
|
+
// library serves declares.
|
|
273
|
+
errorPort: (_spec, definition) => definition === undefined || definitionHasErrorPort(definition),
|
|
274
|
+
checkDefinition: (spec, options) => {
|
|
275
|
+
const def = options.connectorDefinition?.(spec.key, spec.action, spec.version, spec.object);
|
|
276
|
+
// Named as serialize places it: under the operation the author named.
|
|
277
|
+
return def !== null && typeof def === 'object' ? { ...def, nodeType: `${CONNECTOR_PREFIX}${spec.key}.${spec.action}` } : undefined;
|
|
278
|
+
},
|
|
279
|
+
serialize: (spec, ctx) => serializeConnector(spec, ctx),
|
|
280
|
+
deserialize: (node, ctx) => liftConnector(node, ctx),
|
|
281
|
+
print: (spec, ctx) => printConnector(spec, ctx),
|
|
282
|
+
check: (spec, ctx) => ctx.connector(spec),
|
|
283
|
+
// Every Expr in the inputs, nested ones included, resolves against the flow's names.
|
|
284
|
+
expressions: (spec) => [{ value: spec.inputs }],
|
|
285
|
+
output: () => ({ kind: 'opaque' }),
|
|
286
|
+
listShape: (spec, options) => options.listEnvelope?.(spec.key, spec.action, spec.version, spec.object),
|
|
287
|
+
};
|
|
288
|
+
export const connectorNodeType = {
|
|
289
|
+
kind: 'connector', typeName: `${CONNECTOR_PREFIX}*`, typeNamePrefix: CONNECTOR_PREFIX, factory: 'connector',
|
|
290
|
+
typeNameMatches: (typeName) => splitOperation(typeName) !== undefined,
|
|
291
|
+
versionedByDefinition: true,
|
|
292
|
+
defaultVersion: connector10.version,
|
|
293
|
+
versions: { [connector10.version]: connector10 },
|
|
294
|
+
};
|
package/dist/serialize.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { BuiltFlow } from './flow-sdk.js';
|
|
|
3
3
|
import type { Library } from './library.js';
|
|
4
4
|
import { type BindingRefusalCode } from './core/binding-messages.js';
|
|
5
5
|
import type { Bindings } from './bindings.js';
|
|
6
|
-
import { connectorInputErrors as coreConnectorInputErrors } from './core/connector-inputs.js';
|
|
6
|
+
import { CONNECTOR_INPUT_MISSING, connectorInputErrors as coreConnectorInputErrors } from './core/connector-inputs.js';
|
|
7
7
|
/**
|
|
8
8
|
* What `compile` would refuse these connector inputs with, or `undefined` when it
|
|
9
9
|
* would accept them — Flow's binding of the shared validator.
|
|
@@ -23,8 +23,7 @@ export interface SerializeWarning {
|
|
|
23
23
|
code: string;
|
|
24
24
|
message: string;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
export declare const CONNECTOR_INPUT_MISSING = "CONNECTOR_INPUT_MISSING";
|
|
26
|
+
export { CONNECTOR_INPUT_MISSING } from './core/connector-inputs.js';
|
|
28
27
|
/** The refusals a draft-tolerant compile reports instead of throwing. */
|
|
29
28
|
export type DraftCode = BindingRefusalCode | typeof CONNECTOR_INPUT_MISSING | 'CONNECTOR_OBJECT_REQUIRED';
|
|
30
29
|
/** A refusal a draft-tolerant compile reported instead of throwing (see {@link SerializeOpts.draft}). */
|