@uipath/maestro-builder-sdk 6.69.1 → 6.70.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.
@@ -13,6 +13,8 @@ import { isUnrestorableSlotId, taskFingerprint } from './preserve.js';
13
13
  import { v31TaskSets } from './task-sets.js';
14
14
  import { camelizeName, defaultActionOutputVar } from './action-output-var.js';
15
15
  import { buildConnectorInputs, nestConnectorBody, replayInputsOf, transportHttpMethod } from '../core/connector-inputs.js';
16
+ import { carriedConnectorFieldsRefusal, withCarriedFields } from '../core/carried-connector-fields.js';
17
+ import { connectorTaskIdentity, sameConnectorOperation, withCurrentCaches, writeOptionalConfiguration } from './connector-saved-config.js';
16
18
  import { buildEssentialConfiguration } from '../core/connector-configuration.js';
17
19
  import { EQUALS_EXPR_DIALECT } from '../core/connector-checks.js';
18
20
  import { bindingLabelRefusal } from '../core/binding-messages.js';
@@ -612,6 +614,20 @@ export function applyPreservation(result, preservation, options = {}) {
612
614
  task[field] = value;
613
615
  }
614
616
  }
617
+ for (const entry of preservation.connectorConfigurations ?? []) {
618
+ const stage = stageNodes.find((candidate) => candidate.data?.label === entry.stage);
619
+ const task = stageTask(stage, entry.task);
620
+ // A deleted task, or one the edit pointed at another operation, has no use
621
+ // for the saved half: it gets the essential half only, as a new task does.
622
+ const now = task && connectorTaskIdentity(task);
623
+ if (!task || !now || !sameConnectorOperation(now, entry.connector))
624
+ continue;
625
+ // The Designer's caches describe input values as saved: an entry whose
626
+ // field the edit changed would show the old value, so it is dropped.
627
+ const written = writeOptionalConfiguration(task, withCurrentCaches(task, entry.optionalConfiguration, entry.cachedFieldValues));
628
+ if (written === 'different' && !options.overwrite)
629
+ throw new CompileRefusal('PRESERVATION_FIELD_COLLISION', `preserved connector configuration of task "${entry.stage}/${entry.task}" collides with serializer-owned output.`);
630
+ }
615
631
  for (const entry of preservation.omitTaskElementIds ?? []) {
616
632
  const stage = stageNodes.find((candidate) => candidate.data?.label === entry.stage);
617
633
  const task = stageTask(stage, entry.task);
@@ -796,6 +812,7 @@ export function followRenames(result, preservation) {
796
812
  stages,
797
813
  },
798
814
  ...(preservation.taskFields ? { taskFields: preservation.taskFields.map(ref) } : {}),
815
+ ...(preservation.connectorConfigurations ? { connectorConfigurations: preservation.connectorConfigurations.map(ref) } : {}),
799
816
  ...(preservation.omitTaskElementIds ? { omitTaskElementIds: preservation.omitTaskElementIds.map(ref) } : {}),
800
817
  ...(preservation.omitStagePositions ? { omitStagePositions: preservation.omitStagePositions.map(stageName) } : {}),
801
818
  };
@@ -1901,7 +1918,13 @@ function connectorTaskData(spec, resolver, elementId, where, executionType, even
1901
1918
  if (!resolver.library)
1902
1919
  throw new CompileRefusal('LIBRARY_REQUIRED', `connector task needs the connector library — pass { library } to serialize or use uip maestro case compile --library.`);
1903
1920
  const library = resolver.library;
1904
- const rc = onTask(where, () => resolveOperation(library, spec));
1921
+ // A decompiled task's carried fields widen the library's field list with the
1922
+ // plan's own declarations, so a field the Designer saved against a newer
1923
+ // connector than the library compiles instead of being refused (#1362).
1924
+ const carriedRefusal = carriedConnectorFieldsRefusal(spec.carriedFields, where);
1925
+ if (carriedRefusal)
1926
+ throw new CompileRefusal('CARRIED_DEFINITION_INVALID', carriedRefusal);
1927
+ const rc = withCarriedFields(onTask(where, () => resolveOperation(library, spec)), spec.carriedFields);
1905
1928
  const op = rc.entry.operation ?? {};
1906
1929
  const context = [
1907
1930
  io('connectorKey', 'string', { value: rc.connectorKey }),
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Connector input fields a decompiled task CARRIES — the plan's own field
3
+ * declarations for the ones the connector library does not list (#1362).
4
+ *
5
+ * The published library is a snapshot, and a connector keeps shipping after it
6
+ * is taken. Studio Web saves a connector activity against the LIVE connector,
7
+ * and writes that connector's field list into the activity's configuration
8
+ * (`optionalConfiguration.fieldsContainer.inputFields`). So a Designer plan can
9
+ * use a field the library has never heard of: Outlook `get-email-list` saved at
10
+ * connector 1.47.0 carries `bodyFormat: "html"`, and the library's entry has no
11
+ * `bodyFormat`. With the library as the only field authority, `check` and
12
+ * `compile` refused that untouched field as `unknown input`, and the easy way
13
+ * past the refusal was to delete it: a silent change to the task.
14
+ *
15
+ * Decompile now copies the saved declaration of each field the library lacks
16
+ * into the task's source (`carriedFields`), and `check` and `compile` read the
17
+ * operation's fields as the library's plus the carried ones. This is the same
18
+ * idea as Flow's carried node definitions (`core/carried-definitions.ts`): the
19
+ * file's own definition is the third source, after the SDK and the library.
20
+ *
21
+ * It only ever ADDS a name. A field the library declares keeps the library's
22
+ * type, location and `required`, whatever the carried copy says, so a carried
23
+ * list cannot relax a check the library makes. And a carried field is accepted,
24
+ * never demanded: the library's own fields are the required contract.
25
+ *
26
+ * For instance, with `carriedFields: [{ name: 'bodyFormat', type: 'string',
27
+ * location: 'query' }]`, the input `{ bodyFormat: 'html' }` compiles to
28
+ * `queryParameters.bodyFormat = "html"`; without it, compile refuses it.
29
+ */
30
+ import type { FieldLocation, ResolvedConnector } from './library.js';
31
+ /** Where a carried field rides on the wire: the request body, the query string or the path. */
32
+ export type CarriedFieldLocation = Extract<FieldLocation, 'body' | 'query' | 'path'>;
33
+ /**
34
+ * One input field a connector task carries from the plan it was decompiled from.
35
+ *
36
+ * Written by decompile, copied from the activity's saved field list. It is not an
37
+ * authoring surface: an input the connector library already declares is checked
38
+ * against the library, whatever is carried.
39
+ */
40
+ export interface CarriedConnectorField {
41
+ /** The field's name, as the saved field list names it, e.g. `bodyFormat`. */
42
+ name: string;
43
+ /** The wire slot the saved field list names (`fieldLocation`). */
44
+ location: CarriedFieldLocation;
45
+ /** `string`, `number`, `boolean`, `array` or `object`, when the saved field declares one. */
46
+ type?: string;
47
+ }
48
+ /**
49
+ * Why `fields` cannot be carried, or `undefined` when it can.
50
+ *
51
+ * For instance `[{ name: 'bodyFormat', location: 'header' }]` is refused: a
52
+ * header is no slot the connector wire has.
53
+ */
54
+ export declare function carriedConnectorFieldsRefusal(fields: unknown, where: string): string | undefined;
55
+ /**
56
+ * `rc` with each carried field the library does not declare appended to its
57
+ * fields, as an optional input. A name the library declares keeps the library's
58
+ * declaration. `fields` must have passed {@link carriedConnectorFieldsRefusal}.
59
+ */
60
+ export declare function withCarriedFields(rc: ResolvedConnector, fields: readonly CarriedConnectorField[] | undefined): ResolvedConnector;
61
+ /**
62
+ * The fields a saved activity configuration declares that `rc` does not, as
63
+ * decompile carries them: from `optionalConfiguration.fieldsContainer.inputFields`,
64
+ * request fields only, in the saved order. Empty when the configuration has no
65
+ * field list (an SDK-compiled plan writes the essential half only), or every
66
+ * saved field is in the library. A saved field on a slot the connector wire has
67
+ * no place for (a `header`) is not carried.
68
+ *
69
+ * @param configuration - The parsed `=jsonString:` configuration blob.
70
+ */
71
+ export declare function savedFieldsMissingFromLibrary(configuration: unknown, rc: ResolvedConnector): CarriedConnectorField[];
@@ -0,0 +1,80 @@
1
+ import { ownEntry } from './own-entry.js';
2
+ const LOCATIONS = { body: 'body', query: 'query', path: 'path' };
3
+ const TYPES = {
4
+ string: 'string', boolean: 'boolean', number: 'number', integer: 'number', array: 'array', object: 'object',
5
+ };
6
+ /**
7
+ * Why `fields` cannot be carried, or `undefined` when it can.
8
+ *
9
+ * For instance `[{ name: 'bodyFormat', location: 'header' }]` is refused: a
10
+ * header is no slot the connector wire has.
11
+ */
12
+ export function carriedConnectorFieldsRefusal(fields, where) {
13
+ if (fields === undefined)
14
+ return undefined;
15
+ if (!Array.isArray(fields))
16
+ return `${where}: carriedFields must be an array of { name, location, type? } fields.`;
17
+ const seen = new Set();
18
+ for (const field of fields) {
19
+ const f = field;
20
+ if (!f || typeof f !== 'object' || typeof f.name !== 'string' || !f.name)
21
+ return `${where}: a carried field must name itself with a non-empty string.`;
22
+ if (typeof f.location !== 'string' || ownEntry(LOCATIONS, f.location) === undefined)
23
+ return `${where}: carried field "${f.name}" has location ${JSON.stringify(f.location)}; expected "body", "query" or "path".`;
24
+ if (f.type !== undefined && (typeof f.type !== 'string' || !Object.values(TYPES).includes(f.type)))
25
+ return `${where}: carried field "${f.name}" has type ${JSON.stringify(f.type)}; expected "string", "number", "boolean", "array" or "object".`;
26
+ const extra = Object.keys(f).find((key) => key !== 'name' && key !== 'location' && key !== 'type');
27
+ if (extra !== undefined)
28
+ return `${where}: carried field "${f.name}" has unsupported key "${extra}".`;
29
+ if (seen.has(f.name))
30
+ return `${where}: carried field "${f.name}" is listed twice.`;
31
+ seen.add(f.name);
32
+ }
33
+ return undefined;
34
+ }
35
+ /**
36
+ * `rc` with each carried field the library does not declare appended to its
37
+ * fields, as an optional input. A name the library declares keeps the library's
38
+ * declaration. `fields` must have passed {@link carriedConnectorFieldsRefusal}.
39
+ */
40
+ export function withCarriedFields(rc, fields) {
41
+ if (!fields?.length)
42
+ return rc;
43
+ const known = new Set(rc.fields.map((f) => f.name));
44
+ const added = fields
45
+ .filter((f) => !known.has(f.name))
46
+ .map((f) => ({ name: f.name, location: f.location, required: false, ...(f.type !== undefined ? { type: f.type } : {}) }));
47
+ return added.length ? { ...rc, fields: [...rc.fields, ...added] } : rc;
48
+ }
49
+ /**
50
+ * The fields a saved activity configuration declares that `rc` does not, as
51
+ * decompile carries them: from `optionalConfiguration.fieldsContainer.inputFields`,
52
+ * request fields only, in the saved order. Empty when the configuration has no
53
+ * field list (an SDK-compiled plan writes the essential half only), or every
54
+ * saved field is in the library. A saved field on a slot the connector wire has
55
+ * no place for (a `header`) is not carried.
56
+ *
57
+ * @param configuration - The parsed `=jsonString:` configuration blob.
58
+ */
59
+ export function savedFieldsMissingFromLibrary(configuration, rc) {
60
+ const inputFields = configuration
61
+ ?.optionalConfiguration?.fieldsContainer?.inputFields;
62
+ if (!Array.isArray(inputFields))
63
+ return [];
64
+ const known = new Set(rc.fields.map((f) => f.name));
65
+ const out = [];
66
+ for (const raw of inputFields) {
67
+ if (!raw || typeof raw !== 'object')
68
+ continue;
69
+ const f = raw;
70
+ if (typeof f.name !== 'string' || !f.name || known.has(f.name) || f.request === false)
71
+ continue;
72
+ const location = typeof f.fieldLocation === 'string' ? ownEntry(LOCATIONS, f.fieldLocation) : undefined;
73
+ if (!location)
74
+ continue;
75
+ known.add(f.name);
76
+ const type = typeof f.type === 'string' ? ownEntry(TYPES, f.type) : undefined;
77
+ out.push({ name: f.name, location, ...(type !== undefined ? { type } : {}) });
78
+ }
79
+ return out;
80
+ }
@@ -21,10 +21,10 @@
21
21
  * they get different codes, so one code never both stops compile and lets it
22
22
  * write.
23
23
  *
24
- * Since `compile` no longer runs `check` (#1497), a `lint` code is only right
24
+ * Since `compile` no longer runs `check` (Flow #1497, Case #1446), a `lint` code is only right
25
25
  * when the serializer still writes what the source says while the rule fires:
26
- * otherwise `compile` writes a wrong `.flow` without a word, and the code is
27
- * `lowering` (the serializer must refuse it). Every Flow `lint` code has a row
26
+ * otherwise `compile` writes a wrong `.flow` (or caseplan) without a word, and the code is
27
+ * `lowering` (the serializer must refuse it). Every Flow, Case and BPMN `lint` code has a row
28
28
  * in `tests/lint-lossless/` (#1518): a round-trip fixture, a fixture the
29
29
  * serializer refuses, or a written reason. The round trip shows the rule's
30
30
  * condition survives compile -> decompile -> compile and that compile is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.69.1",
3
+ "version": "6.70.1",
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",
@@ -100,5 +100,5 @@
100
100
  "@types/node": "^22.7.0",
101
101
  "esbuild": "^0.28.1"
102
102
  },
103
- "gitref": "f2e643fd7aeb11ab748bb281be50d24fe339bf52"
103
+ "gitref": "d1531fbce30cd3d2a88997dd8c65be6ef046e5ed"
104
104
  }