@uipath/maestro-builder-sdk 6.70.0 → 6.70.2
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/api-index.md +76 -73
- package/dist/api-members.md +231 -227
- package/dist/bpmn/decompile.js +12 -4
- package/dist/case/case-sdk.d.ts +37 -8
- package/dist/case/case-sdk.js +6 -1
- package/dist/case/connector-saved-config.d.ts +64 -0
- package/dist/case/connector-saved-config.js +168 -0
- package/dist/case/decompile.d.ts +9 -0
- package/dist/case/decompile.js +43 -1
- package/dist/case/preserve.d.ts +22 -0
- package/dist/case/preserve.js +14 -0
- package/dist/case/serialize.js +24 -1
- package/dist/core/carried-connector-fields.d.ts +71 -0
- package/dist/core/carried-connector-fields.js +80 -0
- package/package.json +2 -2
package/dist/bpmn/decompile.js
CHANGED
|
@@ -2071,7 +2071,7 @@ defaultScope = () => ownerId) {
|
|
|
2071
2071
|
if (skip.has(v.id))
|
|
2072
2072
|
continue;
|
|
2073
2073
|
const type = varType(v.type);
|
|
2074
|
-
const def = v.default === undefined ? undefined : decodeDefault(v.default, v.type);
|
|
2074
|
+
const def = v.default === undefined ? undefined : decodeDefault(v.default, v.type, v.id);
|
|
2075
2075
|
entries.push(`{ id: ${tsText(v.id)}, type: ${tsText(type)}${inner([
|
|
2076
2076
|
['direction', direction === 'inputOutput' ? undefined : tsText(direction)],
|
|
2077
2077
|
['name', v.name !== undefined && v.name !== v.id ? tsText(v.name) : undefined],
|
|
@@ -2118,11 +2118,19 @@ function bindingIsReferenced(xml, id) {
|
|
|
2118
2118
|
* anything non-finite, so it was right by accident; this makes the rule explicit for
|
|
2119
2119
|
* every type.
|
|
2120
2120
|
*/
|
|
2121
|
-
function decodeDefault(raw, wire) {
|
|
2121
|
+
function decodeDefault(raw, wire, id) {
|
|
2122
2122
|
if (raw.startsWith('='))
|
|
2123
2123
|
return tsText(raw); // an expression, not a value of `wire`
|
|
2124
|
-
if (wire === 'json')
|
|
2125
|
-
|
|
2124
|
+
if (wire === 'json') {
|
|
2125
|
+
// emitted unquoted, so it must be JSON
|
|
2126
|
+
try {
|
|
2127
|
+
JSON.parse(raw);
|
|
2128
|
+
}
|
|
2129
|
+
catch {
|
|
2130
|
+
throw new Error(`decompile: variable ${JSON.stringify(id)} has type "json" but its default ${JSON.stringify(raw)} is not JSON.`);
|
|
2131
|
+
}
|
|
2132
|
+
return raw;
|
|
2133
|
+
}
|
|
2126
2134
|
if (wire === 'number')
|
|
2127
2135
|
return Number.isFinite(Number(raw)) ? String(Number(raw)) : tsText(raw);
|
|
2128
2136
|
// Only the two literals are booleans. Anything else — an empty `default=""`, which
|
package/dist/case/case-sdk.d.ts
CHANGED
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
import type { TypeDesc } from '../core/expr.js';
|
|
21
21
|
import { type ActionSpec, type ConnectorOpts, type EventSubscription, type TriggerOptions } from '../core/actions.js';
|
|
22
22
|
import { type ConnectorDescriptor, type TriggerDescriptor } from '../core/connectors.js';
|
|
23
|
+
import type { CarriedConnectorField } from '../core/carried-connector-fields.js';
|
|
24
|
+
export type { CarriedConnectorField, CarriedFieldLocation } from '../core/carried-connector-fields.js';
|
|
23
25
|
export { preserveCaseJson } from './preserve.js';
|
|
24
|
-
export type { CaseJsonPreservation, PreservedCaseNode, PreservedTaskFields, PreservedVariableLane } from './preserve.js';
|
|
26
|
+
export type { CaseJsonPreservation, PreservedCaseNode, PreservedConnectorConfiguration, PreservedTaskFields, PreservedVariableLane } from './preserve.js';
|
|
25
27
|
export { CaseBuildError } from './case-expr-check.js';
|
|
26
28
|
export type { CaseLocatedDiagnostic } from './case-expr-check.js';
|
|
27
29
|
export type CaseRuleType = 'case-entered' | 'required-tasks-completed' | 'required-stages-completed' | 'selected-stage-completed' | 'selected-stage-exited' | 'selected-tasks-completed' | 'current-stage-entered' | 'adhoc' | 'runs-sequentially' | 'user-selected-stage' | 'wait-for-connector' | 'sla-status-change' | 'api-event';
|
|
@@ -424,7 +426,34 @@ export type WaitConnectorSpec = WaitConnectorPlaceholderSpec | EventSubscription
|
|
|
424
426
|
*/
|
|
425
427
|
export type ConnectorSpecData = Extract<ActionSpec, {
|
|
426
428
|
kind: 'connector';
|
|
427
|
-
}
|
|
429
|
+
}> & {
|
|
430
|
+
/** The plan's own declarations of fields the library lacks; see {@link CaseConnectorOpts.carriedFields}. */
|
|
431
|
+
carriedFields?: readonly CarriedConnectorField[];
|
|
432
|
+
};
|
|
433
|
+
/**
|
|
434
|
+
* {@link TaskBuilder.connector}'s options: the Flow `connector()` options, plus
|
|
435
|
+
* the fields a decompiled task carries.
|
|
436
|
+
*/
|
|
437
|
+
export interface CaseConnectorOpts extends ConnectorOpts {
|
|
438
|
+
/**
|
|
439
|
+
* Fields the plan this task was decompiled from declares and the connector
|
|
440
|
+
* library does not. Written by `decompile`; leave it in place, and do not add
|
|
441
|
+
* to it by hand.
|
|
442
|
+
*
|
|
443
|
+
* @remarks
|
|
444
|
+
* Studio Web saves a connector task against the live connector, which can be
|
|
445
|
+
* newer than the published library. Decompile copies the saved declaration of
|
|
446
|
+
* each such field (from the task's `fieldsContainer.inputFields`), so `check`
|
|
447
|
+
* and `compile` accept the value the plan already has instead of refusing it
|
|
448
|
+
* as an unknown input (#1362). For instance, an Outlook `get-email-list` task
|
|
449
|
+
* saved at connector 1.47.0 carries
|
|
450
|
+
* `[{ name: 'bodyFormat', location: 'query', type: 'string' }]`.
|
|
451
|
+
*
|
|
452
|
+
* A carried field only adds a name: an input the library declares is checked
|
|
453
|
+
* against the library, and a carried field is never required.
|
|
454
|
+
*/
|
|
455
|
+
carriedFields?: readonly CarriedConnectorField[];
|
|
456
|
+
}
|
|
428
457
|
/**
|
|
429
458
|
* The external agent's or automation's `executionType`, as Integration Service
|
|
430
459
|
* reports it for the connector object.
|
|
@@ -1088,22 +1117,22 @@ declare class TaskBuilder {
|
|
|
1088
1117
|
*
|
|
1089
1118
|
* @param descriptor - The generated operation, from a prepared connector module.
|
|
1090
1119
|
* @param inputs - The activity's inputs, typed by the descriptor.
|
|
1091
|
-
* @param opts - Symbolic `connection` / `folder`, an action `version`,
|
|
1092
|
-
* `object` a generic operation addresses.
|
|
1120
|
+
* @param opts - Symbolic `connection` / `folder`, an action `version`, the
|
|
1121
|
+
* `object` a generic operation addresses, and the `carriedFields` decompile writes.
|
|
1093
1122
|
* @returns This builder, so calls chain.
|
|
1094
1123
|
*/
|
|
1095
|
-
connector<I extends Record<string, unknown>, O>(descriptor: ConnectorDescriptor<I, O>, inputs: I, opts?:
|
|
1124
|
+
connector<I extends Record<string, unknown>, O>(descriptor: ConnectorDescriptor<I, O>, inputs: I, opts?: CaseConnectorOpts): this;
|
|
1096
1125
|
/**
|
|
1097
1126
|
* Stringly form, for a connector with no prepared module.
|
|
1098
1127
|
*
|
|
1099
1128
|
* @param key - The connector library key, e.g. `'uipath-salesforce-slack'`.
|
|
1100
1129
|
* @param action - The operation id, e.g. `'send-message-to-channel'`.
|
|
1101
1130
|
* @param inputs - The activity's inputs.
|
|
1102
|
-
* @param opts - Symbolic `connection` / `folder`, an action `version`,
|
|
1103
|
-
* `object` a generic operation addresses.
|
|
1131
|
+
* @param opts - Symbolic `connection` / `folder`, an action `version`, the
|
|
1132
|
+
* `object` a generic operation addresses, and the `carriedFields` decompile writes.
|
|
1104
1133
|
* @returns This builder, so calls chain.
|
|
1105
1134
|
*/
|
|
1106
|
-
connector(key: string, action: string, inputs?: Record<string, unknown>, opts?:
|
|
1135
|
+
connector(key: string, action: string, inputs?: Record<string, unknown>, opts?: CaseConnectorOpts): this;
|
|
1107
1136
|
/**
|
|
1108
1137
|
* A wait-for-connector task — suspend the stage until an Integration Service
|
|
1109
1138
|
* **event** fires. A generated descriptor supplies the connector/event identity;
|
package/dist/case/case-sdk.js
CHANGED
|
@@ -398,7 +398,12 @@ class TaskBuilder {
|
|
|
398
398
|
this.setKind('connector');
|
|
399
399
|
// Reuse the shared connector() so descriptor/nodeType splitting + version
|
|
400
400
|
// handling match the Flow surface exactly; store the connector ActionSpec.
|
|
401
|
-
|
|
401
|
+
const spec = makeConnector(a, b, c, d);
|
|
402
|
+
// The options are the 4th argument in the stringly form, the 3rd with a descriptor.
|
|
403
|
+
const carried = (typeof a === 'string' ? d : c)?.carriedFields;
|
|
404
|
+
if (carried !== undefined)
|
|
405
|
+
spec.carriedFields = carried;
|
|
406
|
+
this._connector = spec;
|
|
402
407
|
return this;
|
|
403
408
|
}
|
|
404
409
|
waitForConnector(descriptorOrSpec = {}, opts) {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Designer-saved connector task's `optionalConfiguration`, kept through a
|
|
3
|
+
* decompile -> compile round trip (#1362).
|
|
4
|
+
*
|
|
5
|
+
* Studio Web saves a connector task's configuration in two halves:
|
|
6
|
+
* `essentialConfiguration` (what the runtime needs, which `compile` rebuilds
|
|
7
|
+
* from the library) and `optionalConfiguration` (the Designer's own state,
|
|
8
|
+
* including `fieldsContainer.inputFields`, the live connector's field list).
|
|
9
|
+
* Compile writes the essential half only, so before this a plan lost its saved
|
|
10
|
+
* field list on its first SDK compile. The next `decompile` then had nothing to
|
|
11
|
+
* carry `carriedFields` from, and `check` refused a field such as Outlook
|
|
12
|
+
* `bodyFormat` again on the second edit.
|
|
13
|
+
*
|
|
14
|
+
* Decompile now records the saved optional half in `preserveCaseJson`, keyed
|
|
15
|
+
* by task, and compile writes it back. It is written back only while the task
|
|
16
|
+
* still calls the same connector operation (connector key, object, method and
|
|
17
|
+
* path): a task the edit pointed at another operation has no saved state for
|
|
18
|
+
* that operation, and gets the essential half only, as a new task does.
|
|
19
|
+
*
|
|
20
|
+
* Only a half with a `fieldsContainer` is kept. Studio Web renders a task's
|
|
21
|
+
* fields from that container when the optional half is non-empty, and shows
|
|
22
|
+
* them empty without it (the essential-only rule in `core/connector-configuration.ts`).
|
|
23
|
+
*
|
|
24
|
+
* The half also holds the Designer's display caches (`cachedBrowserItems`,
|
|
25
|
+
* `cachedLookupValues`, `cachedPropertyMappings`), which describe input VALUES
|
|
26
|
+
* as they were saved: `cachedBrowserItems` maps `parentFolderId` to the folder
|
|
27
|
+
* "Inbox" and its id. After an edit that changes the value, that entry would
|
|
28
|
+
* describe the old folder. So decompile also records each cached field's saved
|
|
29
|
+
* value, and compile keeps a cache entry only when the field still has that
|
|
30
|
+
* value. A cache entry not in the `[field, item]` pair form, the only form
|
|
31
|
+
* measured in Designer plans, cannot be matched to a field and is dropped.
|
|
32
|
+
*/
|
|
33
|
+
/** The connector operation a connector task calls, as its `context` rows name it. */
|
|
34
|
+
export interface ConnectorTaskIdentity {
|
|
35
|
+
connectorKey: string;
|
|
36
|
+
objectName: string;
|
|
37
|
+
method: string;
|
|
38
|
+
path: string;
|
|
39
|
+
}
|
|
40
|
+
/** The operation an `execute-connector-activity` task calls, or `undefined` for any other task. */
|
|
41
|
+
export declare function connectorTaskIdentity(task: Record<string, any>): ConnectorTaskIdentity | undefined;
|
|
42
|
+
/** Whether two operations are the same one. */
|
|
43
|
+
export declare function sameConnectorOperation(a: ConnectorTaskIdentity, b: ConnectorTaskIdentity): boolean;
|
|
44
|
+
/** The task's saved `optionalConfiguration`, when it is an object with a `fieldsContainer`. */
|
|
45
|
+
export declare function savedOptionalConfiguration(task: Record<string, any>): Record<string, unknown> | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Write `optional` into the task's configuration beside its essential half.
|
|
48
|
+
*
|
|
49
|
+
* @returns `'written'`, `'same'` when the task already has that exact half,
|
|
50
|
+
* `'different'` when it has another one, or `'no-configuration'` when the task
|
|
51
|
+
* has no configuration to write into.
|
|
52
|
+
*/
|
|
53
|
+
export declare function writeOptionalConfiguration(task: Record<string, any>, optional: Record<string, unknown>): 'written' | 'same' | 'different' | 'no-configuration';
|
|
54
|
+
/**
|
|
55
|
+
* The saved value of every field the half's caches name, as the task sends it.
|
|
56
|
+
* Decompile records it; compile compares against it (see the module comment).
|
|
57
|
+
*/
|
|
58
|
+
export declare function cachedFieldValues(task: Record<string, any>, optional: Record<string, unknown>): Record<string, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* `optional` with each cache entry kept only while its field still has its
|
|
61
|
+
* saved value in `task`. A cache that is not a list keeps its value; an entry
|
|
62
|
+
* not in the `[field, item]` form is dropped.
|
|
63
|
+
*/
|
|
64
|
+
export declare function withCurrentCaches(task: Record<string, any>, optional: Record<string, unknown>, saved?: Readonly<Record<string, unknown>>): Record<string, unknown>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Designer-saved connector task's `optionalConfiguration`, kept through a
|
|
3
|
+
* decompile -> compile round trip (#1362).
|
|
4
|
+
*
|
|
5
|
+
* Studio Web saves a connector task's configuration in two halves:
|
|
6
|
+
* `essentialConfiguration` (what the runtime needs, which `compile` rebuilds
|
|
7
|
+
* from the library) and `optionalConfiguration` (the Designer's own state,
|
|
8
|
+
* including `fieldsContainer.inputFields`, the live connector's field list).
|
|
9
|
+
* Compile writes the essential half only, so before this a plan lost its saved
|
|
10
|
+
* field list on its first SDK compile. The next `decompile` then had nothing to
|
|
11
|
+
* carry `carriedFields` from, and `check` refused a field such as Outlook
|
|
12
|
+
* `bodyFormat` again on the second edit.
|
|
13
|
+
*
|
|
14
|
+
* Decompile now records the saved optional half in `preserveCaseJson`, keyed
|
|
15
|
+
* by task, and compile writes it back. It is written back only while the task
|
|
16
|
+
* still calls the same connector operation (connector key, object, method and
|
|
17
|
+
* path): a task the edit pointed at another operation has no saved state for
|
|
18
|
+
* that operation, and gets the essential half only, as a new task does.
|
|
19
|
+
*
|
|
20
|
+
* Only a half with a `fieldsContainer` is kept. Studio Web renders a task's
|
|
21
|
+
* fields from that container when the optional half is non-empty, and shows
|
|
22
|
+
* them empty without it (the essential-only rule in `core/connector-configuration.ts`).
|
|
23
|
+
*
|
|
24
|
+
* The half also holds the Designer's display caches (`cachedBrowserItems`,
|
|
25
|
+
* `cachedLookupValues`, `cachedPropertyMappings`), which describe input VALUES
|
|
26
|
+
* as they were saved: `cachedBrowserItems` maps `parentFolderId` to the folder
|
|
27
|
+
* "Inbox" and its id. After an edit that changes the value, that entry would
|
|
28
|
+
* describe the old folder. So decompile also records each cached field's saved
|
|
29
|
+
* value, and compile keeps a cache entry only when the field still has that
|
|
30
|
+
* value. A cache entry not in the `[field, item]` pair form, the only form
|
|
31
|
+
* measured in Designer plans, cannot be matched to a field and is dropped.
|
|
32
|
+
*/
|
|
33
|
+
const JSON_STRING = '=jsonString:';
|
|
34
|
+
const IDENTITY_ROWS = ['connectorKey', 'objectName', 'method', 'path'];
|
|
35
|
+
/** The task's `metadata` context row's body, when it has one. */
|
|
36
|
+
function metadataBody(task) {
|
|
37
|
+
const rows = task?.data?.context;
|
|
38
|
+
if (!Array.isArray(rows))
|
|
39
|
+
return undefined;
|
|
40
|
+
const body = rows.find((row) => row?.name === 'metadata')?.body;
|
|
41
|
+
return body && typeof body === 'object' ? body : undefined;
|
|
42
|
+
}
|
|
43
|
+
/** The parsed `=jsonString:` configuration of a connector task, or `undefined`. */
|
|
44
|
+
function parsedConfiguration(task) {
|
|
45
|
+
const encoded = metadataBody(task)?.activityPropertyConfiguration?.configuration;
|
|
46
|
+
if (typeof encoded !== 'string' || !encoded.startsWith(JSON_STRING))
|
|
47
|
+
return undefined;
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(encoded.slice(JSON_STRING.length));
|
|
50
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : undefined;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The operation an `execute-connector-activity` task calls, or `undefined` for any other task. */
|
|
57
|
+
export function connectorTaskIdentity(task) {
|
|
58
|
+
if (task?.type !== 'execute-connector-activity' || !Array.isArray(task.data?.context))
|
|
59
|
+
return undefined;
|
|
60
|
+
const value = (name) => task.data.context.find((row) => row?.name === name)?.value;
|
|
61
|
+
const values = IDENTITY_ROWS.map(value);
|
|
62
|
+
if (values.some((v) => typeof v !== 'string'))
|
|
63
|
+
return undefined;
|
|
64
|
+
const [connectorKey, objectName, method, path] = values;
|
|
65
|
+
return { connectorKey, objectName, method, path };
|
|
66
|
+
}
|
|
67
|
+
/** Whether two operations are the same one. */
|
|
68
|
+
export function sameConnectorOperation(a, b) {
|
|
69
|
+
return IDENTITY_ROWS.every((name) => a[name] === b[name]);
|
|
70
|
+
}
|
|
71
|
+
/** The task's saved `optionalConfiguration`, when it is an object with a `fieldsContainer`. */
|
|
72
|
+
export function savedOptionalConfiguration(task) {
|
|
73
|
+
const optional = parsedConfiguration(task)?.optionalConfiguration;
|
|
74
|
+
if (!optional || typeof optional !== 'object' || Array.isArray(optional))
|
|
75
|
+
return undefined;
|
|
76
|
+
const container = optional.fieldsContainer;
|
|
77
|
+
return container && typeof container === 'object' ? optional : undefined;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Write `optional` into the task's configuration beside its essential half.
|
|
81
|
+
*
|
|
82
|
+
* @returns `'written'`, `'same'` when the task already has that exact half,
|
|
83
|
+
* `'different'` when it has another one, or `'no-configuration'` when the task
|
|
84
|
+
* has no configuration to write into.
|
|
85
|
+
*/
|
|
86
|
+
export function writeOptionalConfiguration(task, optional) {
|
|
87
|
+
const config = parsedConfiguration(task);
|
|
88
|
+
if (!config)
|
|
89
|
+
return 'no-configuration';
|
|
90
|
+
if (config.optionalConfiguration !== undefined)
|
|
91
|
+
return JSON.stringify(config.optionalConfiguration) === JSON.stringify(optional) ? 'same' : 'different';
|
|
92
|
+
metadataBody(task).activityPropertyConfiguration.configuration =
|
|
93
|
+
`${JSON_STRING}${JSON.stringify({ ...config, optionalConfiguration: optional })}`;
|
|
94
|
+
return 'written';
|
|
95
|
+
}
|
|
96
|
+
/** Whether a configuration key is one of the Designer's value caches (`cachedBrowserItems`, …). */
|
|
97
|
+
function isCacheKey(key) {
|
|
98
|
+
return /^cached[A-Z]/.test(key);
|
|
99
|
+
}
|
|
100
|
+
/** A cache entry's field, when it is in the `[field, item]` pair form. */
|
|
101
|
+
function cacheEntryField(entry) {
|
|
102
|
+
return Array.isArray(entry) && entry.length === 2 && typeof entry[0] === 'string' ? entry[0] : undefined;
|
|
103
|
+
}
|
|
104
|
+
function camel(name) {
|
|
105
|
+
return name.replace(/_([a-z0-9])/gi, (_m, c) => String(c).toUpperCase());
|
|
106
|
+
}
|
|
107
|
+
/** The input container `name` of a task (`pathParameters`, `queryParameters`, `body`). */
|
|
108
|
+
function container(task, name) {
|
|
109
|
+
const body = Array.isArray(task?.data?.inputs)
|
|
110
|
+
? task.data.inputs.find((row) => row?.name === name)?.body : undefined;
|
|
111
|
+
return body && typeof body === 'object' && !Array.isArray(body) ? body : undefined;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The wire value a task sends for `field`, or `null` when it sends none: a
|
|
115
|
+
* query or path parameter (by its name or the camelCased wire name), else a
|
|
116
|
+
* body field (a dotted name reads the nested body).
|
|
117
|
+
*/
|
|
118
|
+
function fieldValue(task, field) {
|
|
119
|
+
for (const name of ['queryParameters', 'pathParameters']) {
|
|
120
|
+
const slot = container(task, name);
|
|
121
|
+
for (const key of [field, camel(field)])
|
|
122
|
+
if (slot && Object.prototype.hasOwnProperty.call(slot, key))
|
|
123
|
+
return slot[key];
|
|
124
|
+
}
|
|
125
|
+
let at = container(task, 'body');
|
|
126
|
+
if (at && Object.prototype.hasOwnProperty.call(at, field))
|
|
127
|
+
return at[field];
|
|
128
|
+
for (const part of field.split('.')) {
|
|
129
|
+
if (!at || typeof at !== 'object' || !Object.prototype.hasOwnProperty.call(at, part))
|
|
130
|
+
return null;
|
|
131
|
+
at = at[part];
|
|
132
|
+
}
|
|
133
|
+
return at ?? null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The saved value of every field the half's caches name, as the task sends it.
|
|
137
|
+
* Decompile records it; compile compares against it (see the module comment).
|
|
138
|
+
*/
|
|
139
|
+
export function cachedFieldValues(task, optional) {
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const [key, cache] of Object.entries(optional)) {
|
|
142
|
+
if (!isCacheKey(key) || !Array.isArray(cache))
|
|
143
|
+
continue;
|
|
144
|
+
for (const entry of cache) {
|
|
145
|
+
const field = cacheEntryField(entry);
|
|
146
|
+
if (field !== undefined && !Object.prototype.hasOwnProperty.call(out, field))
|
|
147
|
+
out[field] = fieldValue(task, field);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* `optional` with each cache entry kept only while its field still has its
|
|
154
|
+
* saved value in `task`. A cache that is not a list keeps its value; an entry
|
|
155
|
+
* not in the `[field, item]` form is dropped.
|
|
156
|
+
*/
|
|
157
|
+
export function withCurrentCaches(task, optional, saved = {}) {
|
|
158
|
+
const same = (field) => Object.prototype.hasOwnProperty.call(saved, field)
|
|
159
|
+
&& JSON.stringify(saved[field]) === JSON.stringify(fieldValue(task, field));
|
|
160
|
+
return Object.fromEntries(Object.entries(optional).map(([key, cache]) => {
|
|
161
|
+
if (!isCacheKey(key) || !Array.isArray(cache))
|
|
162
|
+
return [key, cache];
|
|
163
|
+
return [key, cache.filter((entry) => {
|
|
164
|
+
const field = cacheEntryField(entry);
|
|
165
|
+
return field !== undefined && same(field);
|
|
166
|
+
})];
|
|
167
|
+
}));
|
|
168
|
+
}
|
package/dist/case/decompile.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* input bytes; platform-owned preservation lanes are handled separately.
|
|
5
5
|
*/
|
|
6
6
|
import type { Library } from '../core/library.js';
|
|
7
|
+
import { type ConnectorTaskIdentity } from './connector-saved-config.js';
|
|
7
8
|
interface IoRow {
|
|
8
9
|
id?: string;
|
|
9
10
|
name?: string;
|
|
@@ -217,6 +218,14 @@ type Preservation = {
|
|
|
217
218
|
task: string;
|
|
218
219
|
fields: Record<string, unknown>;
|
|
219
220
|
}>;
|
|
221
|
+
connectorConfigurations?: Array<{
|
|
222
|
+
stage: string;
|
|
223
|
+
lane: number;
|
|
224
|
+
task: string;
|
|
225
|
+
connector: ConnectorTaskIdentity;
|
|
226
|
+
optionalConfiguration: Record<string, unknown>;
|
|
227
|
+
cachedFieldValues: Record<string, unknown>;
|
|
228
|
+
}>;
|
|
220
229
|
triggerDataFields?: Array<Record<string, unknown>>;
|
|
221
230
|
omitStagePositions?: string[];
|
|
222
231
|
omitTaskElementIds?: Array<{
|
package/dist/case/decompile.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { savedFieldsMissingFromLibrary, withCarriedFields } from '../core/carried-connector-fields.js';
|
|
2
|
+
import { cachedFieldValues, connectorTaskIdentity, savedOptionalConfiguration } from './connector-saved-config.js';
|
|
1
3
|
import { commentText } from '../core/comment-text.js';
|
|
2
4
|
import { implicitInArgTrigger } from './in-arg-binding.js';
|
|
3
5
|
import { tsLiteral, tsNumber } from '../core/ts-literal.js';
|
|
@@ -511,6 +513,21 @@ function selectNextStageByExit(plan, refs) {
|
|
|
511
513
|
['connector', connectorSource],
|
|
512
514
|
])]]);
|
|
513
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* A connector task's parsed `=jsonString:` activity configuration, or
|
|
518
|
+
* `undefined` when it has none. Malformed JSON is `connectorVersion`'s to refuse.
|
|
519
|
+
*/
|
|
520
|
+
function savedConfiguration(context) {
|
|
521
|
+
const encoded = context.get('metadata')?.body?.activityPropertyConfiguration?.configuration;
|
|
522
|
+
if (typeof encoded !== 'string' || !encoded.startsWith('=jsonString:'))
|
|
523
|
+
return undefined;
|
|
524
|
+
try {
|
|
525
|
+
return JSON.parse(encoded.slice('=jsonString:'.length));
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
514
531
|
function connectorVersion(context) {
|
|
515
532
|
const metadata = context.get('metadata')?.body;
|
|
516
533
|
const encoded = metadata?.activityPropertyConfiguration?.configuration;
|
|
@@ -555,6 +572,11 @@ function connectorInputs(slots, resolved, owner) {
|
|
|
555
572
|
const parameterByWireName = new Map();
|
|
556
573
|
for (const parameter of resolved.entry.operation?.parameters ?? [])
|
|
557
574
|
parameterByWireName.set(toCamel(String(parameter.name)), String(parameter.name));
|
|
575
|
+
// A carried query or path field is a parameter too, which the library's
|
|
576
|
+
// `operation.parameters` does not list.
|
|
577
|
+
for (const field of resolved.fields)
|
|
578
|
+
if ((field.location === 'query' || field.location === 'path') && !parameterByWireName.has(toCamel(field.name)))
|
|
579
|
+
parameterByWireName.set(toCamel(field.name), field.name);
|
|
558
580
|
const inputs = {};
|
|
559
581
|
const fileParts = new Set(resolved.multipartParts
|
|
560
582
|
?.filter((part) => part.dataType.toLowerCase() === 'file').map((part) => part.name));
|
|
@@ -604,7 +626,13 @@ function connectorCall(task, refs, library) {
|
|
|
604
626
|
objectName: String(objectName),
|
|
605
627
|
version: connectorVersion(context),
|
|
606
628
|
});
|
|
607
|
-
const
|
|
629
|
+
const fromLibrary = library.resolve(identity.key, identity.action, identity.version, identity.object);
|
|
630
|
+
// The fields the plan's saved field list declares and the library lacks: a
|
|
631
|
+
// Designer plan saved against a newer connector than the library uses them,
|
|
632
|
+
// and the source carries their declarations so check and compile accept the
|
|
633
|
+
// values instead of refusing them as unknown inputs (#1362).
|
|
634
|
+
const carried = savedFieldsMissingFromLibrary(savedConfiguration(context), fromLibrary);
|
|
635
|
+
const resolved = withCarriedFields(fromLibrary, carried);
|
|
608
636
|
const inputs = connectorInputs(Array.isArray(data.inputs) ? data.inputs : [], resolved);
|
|
609
637
|
const connection = bindingName(context.get('connection')?.value, refs);
|
|
610
638
|
const folder = bindingName(context.get('folderKey')?.value, refs);
|
|
@@ -613,6 +641,7 @@ function connectorCall(task, refs, library) {
|
|
|
613
641
|
['folder', folder !== undefined ? tsLiteral(folder) : undefined],
|
|
614
642
|
['version', identity.version !== '1.0.0' ? tsLiteral(identity.version) : undefined],
|
|
615
643
|
['object', identity.object !== undefined ? tsLiteral(identity.object) : undefined],
|
|
644
|
+
['carriedFields', carried.length ? tsLiteral(carried) : undefined],
|
|
616
645
|
]);
|
|
617
646
|
return `.connector(${tsLiteral(identity.key)}, ${tsLiteral(identity.action)}, ${tsLiteral(inputs)}${options ? `, ${options}` : ''})`;
|
|
618
647
|
}
|
|
@@ -1473,6 +1502,7 @@ export function collectPreservation(plan) {
|
|
|
1473
1502
|
'entryConditions', 'conditions', 'nonSequentialRunConditions',
|
|
1474
1503
|
]);
|
|
1475
1504
|
const taskFields = [];
|
|
1505
|
+
const connectorConfigurations = [];
|
|
1476
1506
|
const omitTaskElementIds = [];
|
|
1477
1507
|
const omitStagePositions = [];
|
|
1478
1508
|
for (const stage of (plan.nodes ?? []).filter(isStage)) {
|
|
@@ -1494,10 +1524,22 @@ export function collectPreservation(plan) {
|
|
|
1494
1524
|
throw new Error(`task "${task.displayName ?? task.id ?? '?'}" has unsupported field "${field}", which is not allowed in the collision-refusing preservation channel.`);
|
|
1495
1525
|
if (Object.keys(fields).length)
|
|
1496
1526
|
taskFields.push({ stage: label, lane: laneIndex, task: task.displayName ?? '', fields });
|
|
1527
|
+
// The Designer's half of a connector task's configuration, which compile
|
|
1528
|
+
// does not rebuild: its saved field list is what the next decompile
|
|
1529
|
+
// carries `carriedFields` from (#1362).
|
|
1530
|
+
const connector = connectorTaskIdentity(task);
|
|
1531
|
+
const optionalConfiguration = connector && savedOptionalConfiguration(task);
|
|
1532
|
+
if (connector && optionalConfiguration)
|
|
1533
|
+
connectorConfigurations.push({
|
|
1534
|
+
stage: label, lane: laneIndex, task: task.displayName ?? '', connector, optionalConfiguration,
|
|
1535
|
+
cachedFieldValues: cachedFieldValues(task, optionalConfiguration),
|
|
1536
|
+
});
|
|
1497
1537
|
}));
|
|
1498
1538
|
}
|
|
1499
1539
|
if (taskFields.length)
|
|
1500
1540
|
preservation.taskFields = taskFields;
|
|
1541
|
+
if (connectorConfigurations.length)
|
|
1542
|
+
preservation.connectorConfigurations = connectorConfigurations;
|
|
1501
1543
|
if (omitTaskElementIds.length)
|
|
1502
1544
|
preservation.omitTaskElementIds = omitTaskElementIds;
|
|
1503
1545
|
if (omitStagePositions.length)
|
package/dist/case/preserve.d.ts
CHANGED
|
@@ -27,6 +27,26 @@ export interface PreservedTaskRef {
|
|
|
27
27
|
/** Task display name used to find the rebuilt task. */
|
|
28
28
|
task: string;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* A Designer-saved connector task's `optionalConfiguration`, written back while
|
|
32
|
+
* the task calls the same operation (`case/connector-saved-config.ts`, #1362).
|
|
33
|
+
*/
|
|
34
|
+
export interface PreservedConnectorConfiguration extends PreservedTaskRef {
|
|
35
|
+
/** The operation the half was saved for: the task's `connectorKey`, `objectName`, `method` and `path`. */
|
|
36
|
+
connector: {
|
|
37
|
+
connectorKey: string;
|
|
38
|
+
objectName: string;
|
|
39
|
+
method: string;
|
|
40
|
+
path: string;
|
|
41
|
+
};
|
|
42
|
+
/** The saved half, verbatim. It always has a `fieldsContainer`. */
|
|
43
|
+
optionalConfiguration: Record<string, unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* The saved value of each field the half's caches name. Compile keeps a cache
|
|
46
|
+
* entry only while its field still has this value.
|
|
47
|
+
*/
|
|
48
|
+
cachedFieldValues?: Record<string, unknown>;
|
|
49
|
+
}
|
|
30
50
|
export interface PreservedTaskFields extends PreservedTaskRef {
|
|
31
51
|
/** Wire-only fields; the preservation contract permits `elementId` only. */
|
|
32
52
|
fields: Record<string, unknown>;
|
|
@@ -129,6 +149,8 @@ export interface CaseJsonPreservation {
|
|
|
129
149
|
variableOrder?: Partial<Record<PreservedVariableLane, string[]>>;
|
|
130
150
|
/** Wire-only task identity fields aligned without authoring them. */
|
|
131
151
|
taskFields?: PreservedTaskFields[];
|
|
152
|
+
/** Designer-saved connector `optionalConfiguration`s, by task. */
|
|
153
|
+
connectorConfigurations?: PreservedConnectorConfiguration[];
|
|
132
154
|
/** Platform-owned trigger data fields aligned by trigger order. */
|
|
133
155
|
triggerDataFields?: Array<Record<string, unknown>>;
|
|
134
156
|
/** Stage labels whose source JSON intentionally omitted inline positions. */
|
package/dist/case/preserve.js
CHANGED
|
@@ -164,6 +164,20 @@ export function caseJsonPreservationErrors(payload) {
|
|
|
164
164
|
errors.push(`preserved binding row "${r.id}" carries no resource key other than its own id`);
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
|
+
for (const entry of payload.connectorConfigurations ?? []) {
|
|
168
|
+
if (!Number.isInteger(entry.lane) || entry.lane < 0)
|
|
169
|
+
errors.push(`preserved connector configuration lane "${entry.lane}" is not a non-negative integer`);
|
|
170
|
+
const connector = entry.connector;
|
|
171
|
+
if (!connector || ['connectorKey', 'objectName', 'method', 'path'].some((name) => typeof connector[name] !== 'string'))
|
|
172
|
+
errors.push(`preserved connector configuration of task "${entry.task}" must name its connectorKey, objectName, method and path`);
|
|
173
|
+
const optional = entry.optionalConfiguration;
|
|
174
|
+
if (!optional || typeof optional !== 'object' || Array.isArray(optional)
|
|
175
|
+
|| !optional.fieldsContainer || typeof optional.fieldsContainer !== 'object')
|
|
176
|
+
errors.push(`preserved connector configuration of task "${entry.task}" must be an object with a fieldsContainer`);
|
|
177
|
+
if (entry.cachedFieldValues !== undefined
|
|
178
|
+
&& (!entry.cachedFieldValues || typeof entry.cachedFieldValues !== 'object' || Array.isArray(entry.cachedFieldValues)))
|
|
179
|
+
errors.push(`preserved connector configuration of task "${entry.task}" has cachedFieldValues that is not an object`);
|
|
180
|
+
}
|
|
167
181
|
for (const [index, fields] of (payload.triggerDataFields ?? []).entries())
|
|
168
182
|
for (const field of Object.keys(fields))
|
|
169
183
|
if (!PRESERVED_TRIGGER_DATA_KEYS.has(field))
|
package/dist/case/serialize.js
CHANGED
|
@@ -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
|
-
|
|
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 }),
|