@uipath/maestro-builder-sdk 6.54.1 → 6.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +101 -84
- package/dist/api-members.md +398 -371
- package/dist/bpmn/bpmn-sdk.d.ts +110 -27
- package/dist/bpmn/bpmn-sdk.js +84 -6
- package/dist/bpmn/declared-vars.js +3 -2
- package/dist/bpmn/decompile.js +66 -0
- package/dist/bpmn/human-task-form.d.ts +170 -0
- package/dist/bpmn/human-task-form.js +321 -0
- package/dist/bpmn/serialize.js +8 -0
- package/dist/bpmn/typed-node.d.ts +6 -0
- package/dist/bpmn/typed-node.js +6 -0
- package/dist/case/case-sdk.d.ts +7 -2
- package/dist/case/case-sdk.js +22 -7
- package/dist/case/check.js +14 -0
- package/dist/case/decompile.js +34 -2
- package/dist/case/serialize.js +26 -2
- package/package.json +2 -2
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { FORM_FIELD_TYPES, formFieldJsonSchema, formFieldOptionsProblem, formFieldOptionsWhy, formFieldSettingProblems, formFieldSettingsWire, formFieldTypeHint, liftFormFieldSettings, validFormFieldTypesText, wireFormFieldType, } from '../core/form-field-type.js';
|
|
2
|
+
import { outcomeIdOf } from '../core/hitl-routing.js';
|
|
3
|
+
import { schemaId as stableSchemaId } from '../core/stable-id.js';
|
|
4
|
+
/** The `uipath:type` version both the converter and Studio Web write for `Actions.HITL`. */
|
|
5
|
+
const QUICK_TYPE_VERSION = 'v2';
|
|
6
|
+
/** The response row the app variant derives from the registry; the quick form keeps it. */
|
|
7
|
+
const RESPONSE_ROW = 'Process response';
|
|
8
|
+
const DRAFT_07 = 'http://json-schema.org/draft-07/schema#';
|
|
9
|
+
/**
|
|
10
|
+
* A field id has to be a plain, unreserved identifier, for two reasons.
|
|
11
|
+
*
|
|
12
|
+
* An answer is read back as the bare expression `=<id>`, and `=review-note` is a
|
|
13
|
+
* subtraction. And the local evaluator makes every key of the response a PARAMETER of
|
|
14
|
+
* one `new Function(...keys, body)`, so a single key that cannot be a parameter name —
|
|
15
|
+
* `review-note`, `default`, `true` — throws for every output row of the task, the
|
|
16
|
+
* outcome mapping included, and `applyOutputs` writes `undefined` for all of them.
|
|
17
|
+
*/
|
|
18
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
19
|
+
/** Words a parameter cannot be named: ECMAScript's reserved words, strict-mode ones, and the literal-like globals. */
|
|
20
|
+
const RESERVED_WORDS = new Set([
|
|
21
|
+
'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'else',
|
|
22
|
+
'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'new',
|
|
23
|
+
'null', 'return', 'super', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'var', 'void', 'while', 'with',
|
|
24
|
+
'yield', 'let', 'static', 'implements', 'interface', 'package', 'private', 'protected', 'public',
|
|
25
|
+
'arguments', 'eval', 'undefined', 'NaN', 'Infinity',
|
|
26
|
+
]);
|
|
27
|
+
/** The response's own key for the outcome picked — an answer cannot claim it. */
|
|
28
|
+
const OUTCOME_KEY = 'Action';
|
|
29
|
+
/** Whether a field is shown to the reviewer (its value is sent). */
|
|
30
|
+
function isShown(f) {
|
|
31
|
+
return f.direction === 'input' || f.direction === 'inOut';
|
|
32
|
+
}
|
|
33
|
+
/** Whether a field comes back as an answer. */
|
|
34
|
+
function isAnswer(f) {
|
|
35
|
+
return f.direction === 'output' || f.direction === 'inOut';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A shown value as the form schema's `binding`: the variable PATH the designer reads
|
|
39
|
+
* (`vars.amount`), when the expression is one; otherwise the expression as written.
|
|
40
|
+
*/
|
|
41
|
+
function bindingOf(value) {
|
|
42
|
+
const path = /^=(?:js:)?(vars\.[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)$/.exec(value);
|
|
43
|
+
return path ? path[1] : value;
|
|
44
|
+
}
|
|
45
|
+
/** An output row's type for a field: its JSON Schema type, with structures as `json`. */
|
|
46
|
+
function rowTypeOf(type) {
|
|
47
|
+
const json = formFieldJsonSchema(type)?.type ?? 'string';
|
|
48
|
+
return json === 'object' || json === 'array' ? 'json' : json;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Refuse a form the engine could not render, at the call that wrote it.
|
|
52
|
+
*
|
|
53
|
+
* @throws Error naming the task and what is wrong.
|
|
54
|
+
*/
|
|
55
|
+
function validate(id, input) {
|
|
56
|
+
const { form } = input;
|
|
57
|
+
const where = `humanTask "${id}"`;
|
|
58
|
+
if (!Array.isArray(form.fields))
|
|
59
|
+
throw new Error(`${where}: form.fields must be an array.`);
|
|
60
|
+
if (!Array.isArray(form.outcomes) || form.outcomes.length === 0) {
|
|
61
|
+
throw new Error(`${where}: the form needs at least one outcome, e.g. outcomes: ['Approve', 'Reject'].`);
|
|
62
|
+
}
|
|
63
|
+
const ids = new Set();
|
|
64
|
+
for (const f of form.fields) {
|
|
65
|
+
if (typeof f.id !== 'string' || f.id === '')
|
|
66
|
+
throw new Error(`${where}: every form field needs an id.`);
|
|
67
|
+
if (ids.has(f.id))
|
|
68
|
+
throw new Error(`${where}: form field id "${f.id}" is used twice.`);
|
|
69
|
+
ids.add(f.id);
|
|
70
|
+
if (f.id === OUTCOME_KEY) {
|
|
71
|
+
throw new Error(`${where}: form field "${OUTCOME_KEY}" collides with the response's own "${OUTCOME_KEY}" — the outcome picked. Rename it, e.g. id: '${OUTCOME_KEY}Value'.`);
|
|
72
|
+
}
|
|
73
|
+
if (!IDENTIFIER.test(f.id)) {
|
|
74
|
+
throw new Error(`${where}: form field "${f.id}" (${f.direction}) is not a name the runtime can read back as =${f.id}. ` +
|
|
75
|
+
`Use an identifier (letters, digits, _ or $), e.g. id: '${f.id.replace(/\W+(\w)?/g, (_m, c) => (c ?? '').toUpperCase())}'.`);
|
|
76
|
+
}
|
|
77
|
+
if (RESERVED_WORDS.has(f.id)) {
|
|
78
|
+
throw new Error(`${where}: form field "${f.id}" is a reserved word, which the runtime cannot bind as a name — it would lose every answer and the outcome. Rename it, e.g. id: '${f.id}Value'.`);
|
|
79
|
+
}
|
|
80
|
+
// The SDK's own spellings only. The product also resolves platform aliases such as
|
|
81
|
+
// `System.String`, but those are not `FormFieldType`s: accepting one here would let a
|
|
82
|
+
// decompile lift write source that does not type-check, so such an import stays verbatim.
|
|
83
|
+
if (!FORM_FIELD_TYPES.includes(String(f.type))) {
|
|
84
|
+
throw new Error(`${where}: form field "${f.id}" has type "${String(f.type)}". Valid: ${validFormFieldTypesText()}. ${formFieldTypeHint(f.type)}`.trimEnd());
|
|
85
|
+
}
|
|
86
|
+
// The checks Flow's form checker reports as errors: a picker the reviewer cannot
|
|
87
|
+
// pick from, and a setting the platform's schema refuses. (Options on a field that
|
|
88
|
+
// renders no picker is a warning there, and is not refused here.)
|
|
89
|
+
if (formFieldOptionsProblem(f) === 'missing') {
|
|
90
|
+
throw new Error(`${where}: form field "${f.id}" ${formFieldOptionsWhy('missing', f.type)}. Add options: [{ id: 'o1', value: 'a', label: 'A' }, …].`);
|
|
91
|
+
}
|
|
92
|
+
const settings = formFieldSettingProblems(f);
|
|
93
|
+
if (settings.length)
|
|
94
|
+
throw new Error(`${where}: form field "${f.id}" sets ${settings.join('; ')}.`);
|
|
95
|
+
if (isShown(f) && (typeof f.value !== 'string' || !f.value.startsWith('='))) {
|
|
96
|
+
throw new Error(`${where}: form field "${f.id}" (${f.direction}) needs a value — a =-expression such as '=vars.${f.id}' — or the form shows it empty.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// One variable, one writer. Two answers landing in one variable, or an `outputs`
|
|
100
|
+
// mapping onto an answer's variable, both evaluate and the LAST row wins, so an answer
|
|
101
|
+
// is lost with no signal (`outputs: { decision: '=Action' }` beside a field named
|
|
102
|
+
// `decision` replaces the reviewer's text with the outcome name). The product's own
|
|
103
|
+
// converter never writes that shape (`applyHitlExtension` dedupes with `seenVars`).
|
|
104
|
+
const writers = new Map();
|
|
105
|
+
const claim = (variable, writer) => {
|
|
106
|
+
const first = writers.get(variable);
|
|
107
|
+
if (first !== undefined) {
|
|
108
|
+
throw new Error(`${where}: ${first} and ${writer} both write vars.${variable}, so one would overwrite the other. Give one a different variable.`);
|
|
109
|
+
}
|
|
110
|
+
writers.set(variable, writer);
|
|
111
|
+
};
|
|
112
|
+
claim(input.outputVar ?? `${id}_processResponse`, 'the task response (outputVar)');
|
|
113
|
+
for (const f of form.fields)
|
|
114
|
+
if (isAnswer(f))
|
|
115
|
+
claim(f.variable ?? f.id, `form field "${f.id}"`);
|
|
116
|
+
for (const v of Object.keys(input.outputs ?? {}))
|
|
117
|
+
claim(v, `outputs.${v}`);
|
|
118
|
+
const names = new Set();
|
|
119
|
+
const outcomeIds = new Map();
|
|
120
|
+
form.outcomes.forEach((o, i) => {
|
|
121
|
+
const name = typeof o === 'string' ? o : o?.name;
|
|
122
|
+
if (typeof name !== 'string' || name.trim() === '')
|
|
123
|
+
throw new Error(`${where}: every outcome needs a name.`);
|
|
124
|
+
if (names.has(name))
|
|
125
|
+
throw new Error(`${where}: outcome "${name}" is listed twice.`);
|
|
126
|
+
names.add(name);
|
|
127
|
+
// The schema's outcome ids must be unique too, and two names can reach one: an
|
|
128
|
+
// explicit `id` repeated, or `Needs review` and `Needs-review`, which both derive
|
|
129
|
+
// `needs-review`.
|
|
130
|
+
const outcomeId = outcomeIdOf(o, i);
|
|
131
|
+
const clash = outcomeIds.get(outcomeId);
|
|
132
|
+
if (clash !== undefined) {
|
|
133
|
+
throw new Error(`${where}: outcomes "${clash}" and "${name}" both have the id "${outcomeId}". Give one an explicit, different id: { name: '${name}', id: '…' }.`);
|
|
134
|
+
}
|
|
135
|
+
outcomeIds.set(outcomeId, name);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The rows a quick human task is written as.
|
|
140
|
+
*
|
|
141
|
+
* @param id - The task's element id.
|
|
142
|
+
* @param processId - The process id, which the default `hitlSchemaId` is derived from.
|
|
143
|
+
* @param input - The form and the task options.
|
|
144
|
+
* @returns The context, payload and output rows, the input schema and the type version.
|
|
145
|
+
* @throws Error when the form is malformed — see {@link validate}.
|
|
146
|
+
*/
|
|
147
|
+
export function quickHumanTaskRows(id, processId, input) {
|
|
148
|
+
const { form } = input;
|
|
149
|
+
validate(id, input);
|
|
150
|
+
const schemaId = form.schemaId ?? stableSchemaId(processId, id);
|
|
151
|
+
const fields = form.fields.map((f) => {
|
|
152
|
+
const row = { id: f.id, label: f.label ?? f.id, type: wireFormFieldType(f.type), direction: f.direction };
|
|
153
|
+
if (isShown(f))
|
|
154
|
+
row.binding = bindingOf(f.value);
|
|
155
|
+
if (isAnswer(f)) {
|
|
156
|
+
row.variable = `vars.${f.variable ?? f.id}`;
|
|
157
|
+
if (typeof f.required === 'boolean')
|
|
158
|
+
row.required = f.required;
|
|
159
|
+
}
|
|
160
|
+
return { ...row, ...formFieldSettingsWire(f) };
|
|
161
|
+
});
|
|
162
|
+
const outcomes = form.outcomes.map((o, i) => ({
|
|
163
|
+
id: outcomeIdOf(o, i),
|
|
164
|
+
name: typeof o === 'string' ? o : o.name,
|
|
165
|
+
type: 'string',
|
|
166
|
+
isPrimary: i === 0,
|
|
167
|
+
action: 'Continue',
|
|
168
|
+
}));
|
|
169
|
+
const shown = form.fields.filter(isShown);
|
|
170
|
+
const bindings = Object.fromEntries(shown.map((f) => [f.id, bindingOf(f.value)]));
|
|
171
|
+
const contextRows = [
|
|
172
|
+
{ name: 'hitlType', type: 'string', value: 'quick' },
|
|
173
|
+
{ name: '__hitlFormSchema', type: 'json', body: { id: schemaId, fields, outcomes } },
|
|
174
|
+
{ name: 'hitlSchemaId', type: 'string', value: schemaId },
|
|
175
|
+
...(shown.length ? [{ name: '__hitlInputBindings', type: 'json', body: bindings }] : []),
|
|
176
|
+
{ name: 'taskTitle', type: 'string', value: input.title ?? input.name ?? id },
|
|
177
|
+
...(input.priority !== undefined ? [{ name: 'priority', type: 'string', value: input.priority }] : []),
|
|
178
|
+
...(input.labels !== undefined ? [{ name: 'labels', type: 'string', value: input.labels }] : []),
|
|
179
|
+
];
|
|
180
|
+
// What the form shows, as JSON Schema — the converter's `inputSchema`.
|
|
181
|
+
const properties = {};
|
|
182
|
+
const required = [];
|
|
183
|
+
for (const f of shown) {
|
|
184
|
+
properties[f.id] = { ...formFieldJsonSchema(f.type), title: f.label ?? f.id };
|
|
185
|
+
if (f.direction === 'inOut' && f.required === true)
|
|
186
|
+
required.push(f.id);
|
|
187
|
+
}
|
|
188
|
+
const inputSchema = { $schema: DRAFT_07, type: 'object', properties, ...(required.length ? { required } : {}) };
|
|
189
|
+
const inputRows = [
|
|
190
|
+
{ name: 'HitlTaskArguments', type: 'json', target: 'bodyField', body: Object.fromEntries(shown.map((f) => [f.id, f.value])) },
|
|
191
|
+
];
|
|
192
|
+
const fieldById = new Map(form.fields.map((f) => [f.id, f]));
|
|
193
|
+
const outputRows = [
|
|
194
|
+
{ name: RESPONSE_ROW, type: 'Actions.HITL', var: input.outputVar ?? `${id}_processResponse` },
|
|
195
|
+
...form.fields.filter(isAnswer).map((f) => {
|
|
196
|
+
const v = f.variable ?? f.id;
|
|
197
|
+
return { name: v, type: rowTypeOf(f.type), source: `=${f.id}`, var: v, custom: true };
|
|
198
|
+
}),
|
|
199
|
+
];
|
|
200
|
+
const mappedOutputRows = Object.entries(input.outputs ?? {}).map(([v, source]) => {
|
|
201
|
+
// The fallback type: an answer's field type for `=<fieldId>`, `string` otherwise
|
|
202
|
+
// (`=Action`, the outcome NAME, is a string). A declared variable overrides it.
|
|
203
|
+
const field = /^=([A-Za-z_$][\w$]*)$/.exec(source)?.[1];
|
|
204
|
+
const f = field === undefined ? undefined : fieldById.get(field);
|
|
205
|
+
return { name: v, type: f ? rowTypeOf(f.type) : 'string', source, var: v };
|
|
206
|
+
});
|
|
207
|
+
return { typeVersion: QUICK_TYPE_VERSION, contextRows, inputSchema, inputRows, outputRows, mappedOutputRows };
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Rows as comparable text: each row's own keys sorted (a row read from XML and a row
|
|
211
|
+
* built here need not list them in the same order), but every body as its exact TEXT.
|
|
212
|
+
* A body that parses to the same value with other whitespace or key order would compile
|
|
213
|
+
* back to different bytes, so it must not count as a match.
|
|
214
|
+
*/
|
|
215
|
+
function rowsText(rows) {
|
|
216
|
+
return JSON.stringify(rows.map((r) => {
|
|
217
|
+
const entries = Object.entries(r)
|
|
218
|
+
.filter(([, v]) => v !== undefined)
|
|
219
|
+
.map(([k, v]) => [k, k === 'body' || k === 'schema' ? textOf(v) : v]);
|
|
220
|
+
return Object.fromEntries(entries.sort(([a], [b]) => a.localeCompare(b)));
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
/** A body as the text it is written as — a value is JSON-stringified, as the serializer does. */
|
|
224
|
+
function textOf(body) {
|
|
225
|
+
return typeof body === 'string' ? body : JSON.stringify(body);
|
|
226
|
+
}
|
|
227
|
+
/** Parse a json body read from XML, or `undefined` when it is not JSON. */
|
|
228
|
+
function bodyValue(body) {
|
|
229
|
+
if (typeof body !== 'string')
|
|
230
|
+
return body;
|
|
231
|
+
try {
|
|
232
|
+
return JSON.parse(body);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Read a quick task back as `.humanTask({ form })` options — or `undefined` when its
|
|
240
|
+
* rows are anything but what those options compile to.
|
|
241
|
+
*
|
|
242
|
+
* Best effort by construction, and safe because of the last step: the lifted options
|
|
243
|
+
* are run through {@link quickHumanTaskRows} and the result must equal the node's rows,
|
|
244
|
+
* order and all. A Studio Web task (sidecar schema, `_schemaFileId`, no response row)
|
|
245
|
+
* fails that and stays the verbatim `.activity()` it was.
|
|
246
|
+
*
|
|
247
|
+
* @param node - The node's rows, as decompile read them.
|
|
248
|
+
* @param processId - The id the decompiled `bpmn(…)` call will carry.
|
|
249
|
+
* @param authoredVarType - The wire type of each variable the decompiled source will
|
|
250
|
+
* DECLARE with `.var()` — what the serializer types a mapped `outputs` row from on the
|
|
251
|
+
* way back. A variable decompile folds away is not one: its row is re-typed from the
|
|
252
|
+
* fallback, so it has to already carry that type to match.
|
|
253
|
+
* @returns The options, or `undefined` to keep the node verbatim.
|
|
254
|
+
*/
|
|
255
|
+
export function liftQuickHumanTask(node, processId, authoredVarType = () => undefined) {
|
|
256
|
+
const ctx = new Map(node.contextRows.map((r) => [r.name, r]));
|
|
257
|
+
if (ctx.get('hitlType')?.value !== 'quick')
|
|
258
|
+
return undefined;
|
|
259
|
+
const schema = bodyValue(ctx.get('__hitlFormSchema')?.body);
|
|
260
|
+
if (!schema || !Array.isArray(schema.fields) || !Array.isArray(schema.outcomes))
|
|
261
|
+
return undefined;
|
|
262
|
+
const args = bodyValue(node.inputRows.find((r) => r.name === 'HitlTaskArguments')?.body);
|
|
263
|
+
let lifted;
|
|
264
|
+
try {
|
|
265
|
+
const fields = schema.fields.map((row) => {
|
|
266
|
+
const id = String(row.id);
|
|
267
|
+
const base = {
|
|
268
|
+
id,
|
|
269
|
+
...(row.label !== undefined && row.label !== id ? { label: String(row.label) } : {}),
|
|
270
|
+
type: row.type,
|
|
271
|
+
...liftFormFieldSettings(row),
|
|
272
|
+
};
|
|
273
|
+
const variable = typeof row.variable === 'string' ? row.variable.replace(/^vars\./, '') : undefined;
|
|
274
|
+
const answerVar = variable !== undefined && variable !== id ? { variable } : {};
|
|
275
|
+
const required = typeof row.required === 'boolean' ? { required: row.required } : {};
|
|
276
|
+
if (row.direction === 'input')
|
|
277
|
+
return { ...base, direction: 'input', value: String(args?.[id]) };
|
|
278
|
+
if (row.direction === 'inOut')
|
|
279
|
+
return { ...base, direction: 'inOut', value: String(args?.[id]), ...required, ...answerVar };
|
|
280
|
+
return { ...base, direction: 'output', ...required, ...answerVar };
|
|
281
|
+
});
|
|
282
|
+
const outcomes = schema.outcomes.map((o, i) => {
|
|
283
|
+
const name = String(o.name);
|
|
284
|
+
return outcomeIdOf(name, i) === o.id ? name : { name, id: String(o.id) };
|
|
285
|
+
});
|
|
286
|
+
const schemaId = String(schema.id);
|
|
287
|
+
const response = node.outputRows[0];
|
|
288
|
+
const answerVars = new Set(fields.filter((f) => f.direction !== 'input').map((f) => f.variable ?? f.id));
|
|
289
|
+
const extra = node.outputRows.slice(1).filter((r) => r.var !== undefined && !answerVars.has(r.var) && !r.custom);
|
|
290
|
+
lifted = {
|
|
291
|
+
form: { fields, outcomes, ...(schemaId !== stableSchemaId(processId, node.id) ? { schemaId } : {}) },
|
|
292
|
+
...(node.name !== undefined ? { name: node.name } : {}),
|
|
293
|
+
...(ctx.get('taskTitle')?.value !== (node.name ?? node.id) ? { title: ctx.get('taskTitle')?.value } : {}),
|
|
294
|
+
...(ctx.has('priority') ? { priority: ctx.get('priority').value } : {}),
|
|
295
|
+
...(ctx.has('labels') ? { labels: ctx.get('labels').value } : {}),
|
|
296
|
+
...(extra.length ? { outputs: Object.fromEntries(extra.map((r) => [r.var, r.source ?? ''])) } : {}),
|
|
297
|
+
...(response?.var !== undefined && response.var !== `${node.id}_processResponse` ? { outputVar: response.var } : {}),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
let rebuilt;
|
|
304
|
+
try {
|
|
305
|
+
rebuilt = quickHumanTaskRows(node.id, processId, lifted);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
const same = (node.typeVersion ?? 'v1') === rebuilt.typeVersion &&
|
|
311
|
+
rowsText(node.contextRows) === rowsText(rebuilt.contextRows) &&
|
|
312
|
+
node.inputSchema !== undefined &&
|
|
313
|
+
textOf(node.inputSchema) === textOf(rebuilt.inputSchema) &&
|
|
314
|
+
rowsText(node.inputRows) === rowsText(rebuilt.inputRows) &&
|
|
315
|
+
rowsText(node.outputRows) ===
|
|
316
|
+
rowsText([
|
|
317
|
+
...rebuilt.outputRows,
|
|
318
|
+
...rebuilt.mappedOutputRows.map((r) => ({ ...r, type: (r.var !== undefined ? authoredVarType(r.var) : undefined) ?? r.type })),
|
|
319
|
+
]);
|
|
320
|
+
return same ? lifted : undefined;
|
|
321
|
+
}
|
package/dist/bpmn/serialize.js
CHANGED
|
@@ -470,6 +470,7 @@ function createNode(ctx, n) {
|
|
|
470
470
|
skipCondition: n.skipCondition,
|
|
471
471
|
contextVerbatim: n.contextVerbatim,
|
|
472
472
|
outputRows: n.outputRows,
|
|
473
|
+
mappedOutputRows: n.mappedOutputRows,
|
|
473
474
|
}, (varId) => ctx.authoredVarType.get(varId)), n);
|
|
474
475
|
case 'subProcess':
|
|
475
476
|
return applyActivityFields(ctx, createSubProcess(ctx, n), n);
|
|
@@ -822,6 +823,13 @@ export function implicitNodeVars(nodes, declared) {
|
|
|
822
823
|
// is what keeps a node's own outputs inside the node: 83 of the 112 declarations
|
|
823
824
|
// in `claims-intake-hitl` are this shape, and every one of them had to be
|
|
824
825
|
// restated as a `.var()` because the derivation could not reach the row.
|
|
826
|
+
// A mapped row's own type is its fallback, so an undeclared variable is declared
|
|
827
|
+
// as the row is written; a declared one keeps its own (`claim` skips it). First, so
|
|
828
|
+
// `typedNodeDeclaredVars` below, which lists them too, cannot claim them untyped.
|
|
829
|
+
for (const row of n.mappedOutputRows ?? []) {
|
|
830
|
+
const d = rowDeclaration(row);
|
|
831
|
+
claim(row.var, n.id, d.type, d);
|
|
832
|
+
}
|
|
825
833
|
if (n.outputRows) {
|
|
826
834
|
for (const row of n.outputRows) {
|
|
827
835
|
const d = rowDeclaration(row);
|
|
@@ -376,6 +376,12 @@ export interface TypedNodeInput {
|
|
|
376
376
|
* for a caller that already knows the exact rows.
|
|
377
377
|
*/
|
|
378
378
|
outputRows?: TypedOutputRow[];
|
|
379
|
+
/**
|
|
380
|
+
* Rows written after everything else, each typed from the author-declared variable it
|
|
381
|
+
* writes (the `varType` callback), its own `type` otherwise. Unlike `outputs` these
|
|
382
|
+
* survive `outputRows`; `.humanTask({ form })` uses them for its `outputs`.
|
|
383
|
+
*/
|
|
384
|
+
mappedOutputRows?: TypedOutputRow[];
|
|
379
385
|
/** `=`-expression that skips the node when truthy (activity/event tags only). */
|
|
380
386
|
skipCondition?: string;
|
|
381
387
|
/**
|
package/dist/bpmn/typed-node.js
CHANGED
|
@@ -556,6 +556,7 @@ varType) {
|
|
|
556
556
|
// TypedNodeInput.outputRows — this is what lets a connector's response schema
|
|
557
557
|
// survive a round trip.
|
|
558
558
|
rows.push(...verbatimOutputRows(m, input.outputRows));
|
|
559
|
+
rows.push(...mappedRows(m, input.mappedOutputRows, varType));
|
|
559
560
|
if (rows.length)
|
|
560
561
|
payload.output = rows;
|
|
561
562
|
return finish(m, spec, id, input.name, payload);
|
|
@@ -588,10 +589,15 @@ varType) {
|
|
|
588
589
|
// which is what every row did before.
|
|
589
590
|
rows.push(m.create('uipath:Output', { name: varId, type: varType?.(varId) ?? 'string', source, var: varId }));
|
|
590
591
|
}
|
|
592
|
+
rows.push(...mappedRows(m, input.mappedOutputRows, varType));
|
|
591
593
|
if (rows.length)
|
|
592
594
|
payload.output = rows;
|
|
593
595
|
return finish(m, spec, id, input.name, payload);
|
|
594
596
|
}
|
|
597
|
+
/** `TypedNodeInput.mappedOutputRows`, each typed from its declared variable when there is one. */
|
|
598
|
+
function mappedRows(m, rows, varType) {
|
|
599
|
+
return verbatimOutputRows(m, (rows ?? []).map((r) => ({ ...r, type: (r.var !== undefined ? varType?.(r.var) : undefined) ?? r.type })));
|
|
600
|
+
}
|
|
595
601
|
/** `uipath:Output` rows exactly as the caller spelled them, deriving nothing. */
|
|
596
602
|
export function verbatimOutputRows(m, rows) {
|
|
597
603
|
return rows.map((row) => {
|
package/dist/case/case-sdk.d.ts
CHANGED
|
@@ -1123,8 +1123,8 @@ declare class TaskBuilder {
|
|
|
1123
1123
|
type?: TypeDesc;
|
|
1124
1124
|
}>): this;
|
|
1125
1125
|
/**
|
|
1126
|
-
* Extract fields of the task's **result** into case variables (reference-mode
|
|
1127
|
-
* tasks). Each key is the case-variable name a later task/condition reads as
|
|
1126
|
+
* Extract fields of the task's **result** into case variables (reference-mode,
|
|
1127
|
+
* connector and wait-for-connector tasks). Each key is the case-variable name a later task/condition reads as
|
|
1128
1128
|
* `=vars.<name>`; each value is the source field expression (e.g. `=response`,
|
|
1129
1129
|
* `=Error.Message`). Pass `{ source, type }` to set a non-string type. Emits a
|
|
1130
1130
|
* `data.outputs[]` row plus a root `inputOutputs` companion so the name resolves.
|
|
@@ -1150,6 +1150,11 @@ declare class TaskBuilder {
|
|
|
1150
1150
|
* later output's (`check` reports `OUTPUT_SELF_REFERENCE` /
|
|
1151
1151
|
* `OUTPUT_FORWARD_READ`).
|
|
1152
1152
|
*
|
|
1153
|
+
* On a connector or wait-for-connector task, a field is read from the
|
|
1154
|
+
* operation's (or event's) `response`: an SDD row `app_id -> postAppId` is
|
|
1155
|
+
* `.outputs({ postAppId: '=response.app_id' })`. Each extract is written after
|
|
1156
|
+
* the task's own `response` and `Error` rows.
|
|
1157
|
+
*
|
|
1153
1158
|
* @example
|
|
1154
1159
|
* **Extract a result field and assign two custom outputs**
|
|
1155
1160
|
* ```ts
|
package/dist/case/case-sdk.js
CHANGED
|
@@ -474,8 +474,8 @@ class TaskBuilder {
|
|
|
474
474
|
return this;
|
|
475
475
|
}
|
|
476
476
|
/**
|
|
477
|
-
* Extract fields of the task's **result** into case variables (reference-mode
|
|
478
|
-
* tasks). Each key is the case-variable name a later task/condition reads as
|
|
477
|
+
* Extract fields of the task's **result** into case variables (reference-mode,
|
|
478
|
+
* connector and wait-for-connector tasks). Each key is the case-variable name a later task/condition reads as
|
|
479
479
|
* `=vars.<name>`; each value is the source field expression (e.g. `=response`,
|
|
480
480
|
* `=Error.Message`). Pass `{ source, type }` to set a non-string type. Emits a
|
|
481
481
|
* `data.outputs[]` row plus a root `inputOutputs` companion so the name resolves.
|
|
@@ -501,6 +501,11 @@ class TaskBuilder {
|
|
|
501
501
|
* later output's (`check` reports `OUTPUT_SELF_REFERENCE` /
|
|
502
502
|
* `OUTPUT_FORWARD_READ`).
|
|
503
503
|
*
|
|
504
|
+
* On a connector or wait-for-connector task, a field is read from the
|
|
505
|
+
* operation's (or event's) `response`: an SDD row `app_id -> postAppId` is
|
|
506
|
+
* `.outputs({ postAppId: '=response.app_id' })`. Each extract is written after
|
|
507
|
+
* the task's own `response` and `Error` rows.
|
|
508
|
+
*
|
|
504
509
|
* @example
|
|
505
510
|
* **Extract a result field and assign two custom outputs**
|
|
506
511
|
* ```ts
|
|
@@ -549,13 +554,23 @@ class TaskBuilder {
|
|
|
549
554
|
_build() {
|
|
550
555
|
if (!this._kind)
|
|
551
556
|
throw new Error(`task "${this._name}" has no kind — call .process()/.agent()/.rpa()/.action()/.connector()/.waitForTimer()/… once.`);
|
|
552
|
-
// Task
|
|
553
|
-
//
|
|
557
|
+
// Task input binding is reference-mode only (REF_IO): a connector task takes
|
|
558
|
+
// its inputs in `.connector()` / `.waitForConnector()`. Output extracts also
|
|
559
|
+
// work on connector and wait-for-connector tasks: the task writes its own
|
|
560
|
+
// `response` and `Error` rows, and each extract (`=response.<field>`) is one
|
|
561
|
+
// more row after them, as `uip maestro case sdd convert` and `case splice`
|
|
562
|
+
// write an SDD row `app_id -> postAppId`.
|
|
554
563
|
const REF_IO = new Set(['process', 'agent', 'rpa', 'api-workflow', 'case-management', 'flow-process', 'function', 'business-rule']);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
564
|
+
const CONNECTOR_OUTPUTS = new Set(['connector', 'wait-for-connector']);
|
|
565
|
+
if (this._inputs && !REF_IO.has(this._kind))
|
|
566
|
+
throw new Error(`task "${this._name}": .inputs()/.outputs() are only supported on reference-mode tasks (process/agent/rpa/api-workflow/case-management/flow-process/function/business-rule), not "${this._kind}". A connector task takes its inputs in .connector() / .waitForConnector().`);
|
|
567
|
+
if (this._outputs && !REF_IO.has(this._kind) && !CONNECTOR_OUTPUTS.has(this._kind))
|
|
568
|
+
throw new Error(`task "${this._name}": .outputs() is only supported on reference-mode tasks (process/agent/rpa/api-workflow/case-management/flow-process/function/business-rule) and on connector / wait-for-connector tasks, not "${this._kind}".`);
|
|
569
|
+
if (REF_IO.has(this._kind) && !this._ref && (this._inputs || this._outputs))
|
|
558
570
|
throw new Error(`task "${this._name}": an unresolved reference task cannot declare inputs or outputs.`);
|
|
571
|
+
if (this._kind === 'wait-for-connector' && this._outputs
|
|
572
|
+
&& !(this._waitConnector && 'connector' in this._waitConnector && 'event' in this._waitConnector))
|
|
573
|
+
throw new Error(`task "${this._name}": a placeholder wait-for-connector task has no event response to extract outputs from — name the event (.waitForConnector(<trigger descriptor>) or { connector, event }).`);
|
|
559
574
|
return {
|
|
560
575
|
name: this._name,
|
|
561
576
|
kind: this._kind,
|
package/dist/case/check.js
CHANGED
|
@@ -597,6 +597,20 @@ function checkStage(stage, diags, refStage, refPlacement, bindings) {
|
|
|
597
597
|
});
|
|
598
598
|
}
|
|
599
599
|
}
|
|
600
|
+
// A connector task's result is its `response` (and `Error`): `=app_id`
|
|
601
|
+
// names nothing at run time. The field is `=response.app_id`, the source
|
|
602
|
+
// `uip maestro case sdd convert` writes for the SDD row `app_id -> postAppId`.
|
|
603
|
+
if (t.kind === 'connector' || t.kind === 'wait-for-connector')
|
|
604
|
+
for (const o of t.outputs ?? [])
|
|
605
|
+
// A custom row (`{ assign }`, builder-sdk#1438) sets a variable from an
|
|
606
|
+
// expression, not from the response, so it is not an extract.
|
|
607
|
+
if (!o.custom && !/^=\s*(response|Error)(?=$|[.[])/.test(o.source))
|
|
608
|
+
diags.push({
|
|
609
|
+
level: 'error',
|
|
610
|
+
code: 'CONNECTOR_OUTPUT_SOURCE',
|
|
611
|
+
message: `${t.kind === 'connector' ? 'Connector' : 'Wait-for-connector'} task "${t.name}" output "${o.var}" reads "${o.source}". A connector task's result is its response: write "=response.${o.source.replace(/^=\s*/, '')}" (or "=Error.<field>" for the error).`,
|
|
612
|
+
where: t.name,
|
|
613
|
+
});
|
|
600
614
|
if (t.kind === 'flow-process' && (!t.ref || !t.ref.name.trim()))
|
|
601
615
|
diags.push({
|
|
602
616
|
level: 'error',
|
package/dist/case/decompile.js
CHANGED
|
@@ -782,6 +782,28 @@ function actionApp(data, refs, task) {
|
|
|
782
782
|
function isAuthorableBusinessRule(task) {
|
|
783
783
|
return task.type === 'business-rule' && task.data?.version === 'v3';
|
|
784
784
|
}
|
|
785
|
+
/** Task kinds whose own `response` / `Error` rows can be followed by `.outputs()` extracts. */
|
|
786
|
+
const CONNECTOR_EXTRACT_KINDS = new Set(['execute-connector-activity', 'wait-for-connector']);
|
|
787
|
+
/**
|
|
788
|
+
* A connector or wait-for-connector task's extract rows: every output but the
|
|
789
|
+
* `response` and `Error` rows `.connector()` / `.waitForConnector()` re-emit
|
|
790
|
+
* themselves. Dropping them lost every SDD extract on a round trip.
|
|
791
|
+
*/
|
|
792
|
+
function connectorExtracts(task) {
|
|
793
|
+
const rows = Array.isArray(task.data?.outputs) ? task.data.outputs : [];
|
|
794
|
+
return rows.filter((row) => !isConnectorOwnOutput(row));
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* A connector's own `response` / `Error` row, which the builder re-emits
|
|
798
|
+
* itself. Its id and source may carry a plan-wide suffix (`=response2`,
|
|
799
|
+
* builder-sdk#518), so the source is matched with the suffix, the same test
|
|
800
|
+
* the trigger path uses. An exact match made a suffixed own row look like an
|
|
801
|
+
* extract, which `check` then refused (CONNECTOR_OUTPUT_SOURCE).
|
|
802
|
+
*/
|
|
803
|
+
function isConnectorOwnOutput(row) {
|
|
804
|
+
return (row.name === 'response' && /^=response\d*$/.test(String(row.source ?? '')))
|
|
805
|
+
|| (row.name === 'Error' && /^=Error\d*$/.test(String(row.source ?? '')));
|
|
806
|
+
}
|
|
785
807
|
/** The two outputs every business-rule task carries, which `.businessRule()` re-emits itself. */
|
|
786
808
|
function isBusinessRuleOwnOutput(row) {
|
|
787
809
|
return (row.name === 'output' && row.source === '=result') || (row.name === 'Error' && row.source === '=Error');
|
|
@@ -837,6 +859,11 @@ function emitTask(out, depth, task, lane, refs, library) {
|
|
|
837
859
|
if (extracts.length)
|
|
838
860
|
out.line(inner, `.outputs(${ioArg(extracts, 'outputs')})`);
|
|
839
861
|
}
|
|
862
|
+
if (CONNECTOR_EXTRACT_KINDS.has(String(task.type))) {
|
|
863
|
+
const extracts = connectorExtracts(task);
|
|
864
|
+
if (extracts.length)
|
|
865
|
+
out.line(inner, `.outputs(${ioArg(extracts, 'outputs')})`);
|
|
866
|
+
}
|
|
840
867
|
if (task.isRequired !== undefined)
|
|
841
868
|
out.line(inner, task.isRequired ? '.required()' : '.required(false)');
|
|
842
869
|
if (task.shouldRunOnlyOnce !== undefined)
|
|
@@ -921,8 +948,7 @@ function varSpec(entry) {
|
|
|
921
948
|
function isResolvedConnectorOutput(row, triggerId, resolved) {
|
|
922
949
|
if (!resolved || row.elementId !== triggerId)
|
|
923
950
|
return false;
|
|
924
|
-
return (row
|
|
925
|
-
|| (row.name === 'Error' && /^=Error\d*$/.test(String(row.source ?? '')));
|
|
951
|
+
return isConnectorOwnOutput(row);
|
|
926
952
|
}
|
|
927
953
|
function producedVariableNames(plan) {
|
|
928
954
|
const produced = new Set();
|
|
@@ -955,6 +981,12 @@ function producedVariableNames(plan) {
|
|
|
955
981
|
if (row.var && !isBusinessRuleOwnOutput(row))
|
|
956
982
|
produced.add(row.var);
|
|
957
983
|
}
|
|
984
|
+
else if (CONNECTOR_EXTRACT_KINDS.has(String(task.type))) {
|
|
985
|
+
// A connector extract registers its case variable on recompile too.
|
|
986
|
+
for (const row of connectorExtracts(task))
|
|
987
|
+
if (row.var)
|
|
988
|
+
produced.add(row.var);
|
|
989
|
+
}
|
|
958
990
|
}
|
|
959
991
|
}
|
|
960
992
|
// An own-name row that carries `originalVar` (`{ id: 'estimatedAge', var:
|
package/dist/case/serialize.js
CHANGED
|
@@ -1002,7 +1002,7 @@ function taskData(task, resolver, taskId, stageId, ids) {
|
|
|
1002
1002
|
// `task.elementId === output.elementId` filter matched none of them and the
|
|
1003
1003
|
// task's `response` / `Error` never reached a later task (#1419).
|
|
1004
1004
|
case 'connector':
|
|
1005
|
-
return connectorTaskData(task.connector, resolver, `${stageId}-${taskId}`, `Connector task "${task.name}"`);
|
|
1005
|
+
return withOutputExtracts(connectorTaskData(task.connector, resolver, `${stageId}-${taskId}`, `Connector task "${task.name}"`), task, ids, resolver);
|
|
1006
1006
|
case 'external-agent':
|
|
1007
1007
|
case 'external-workflow':
|
|
1008
1008
|
return externalTaskData(task.kind, task.external, resolver, `${stageId}-${taskId}`, `${task.kind === 'external-agent' ? 'External Agent' : 'External Workflow'} task "${task.name}"`);
|
|
@@ -1018,9 +1018,33 @@ function taskData(task, resolver, taskId, stageId, ids) {
|
|
|
1018
1018
|
case 'wait-for-connector':
|
|
1019
1019
|
// Floor placeholder (no connector named) = `serviceType` only; naming the
|
|
1020
1020
|
// connector/operation emits the `context` subscription.
|
|
1021
|
-
return waitConnectorBag(task.waitConnector, { alwaysContext: false }, resolver, `${stageId}-${taskId}`, `Wait-for-connector task "${task.name}"`);
|
|
1021
|
+
return withOutputExtracts(waitConnectorBag(task.waitConnector, { alwaysContext: false }, resolver, `${stageId}-${taskId}`, `Wait-for-connector task "${task.name}"`), task, ids, resolver);
|
|
1022
1022
|
}
|
|
1023
1023
|
}
|
|
1024
|
+
/**
|
|
1025
|
+
* A connector or wait-for-connector task's `.outputs()` extracts, appended to
|
|
1026
|
+
* the `response` / `Error` rows the task writes itself. Each is an ordinary task
|
|
1027
|
+
* output (`emitTaskOutput`) on the same `elementId` as those rows, e.g. for
|
|
1028
|
+
* `.outputs({ postAppId: '=response.app_id' })`:
|
|
1029
|
+
* { name: 'app_id', type: 'string', id: 'o…', var: 'postAppId', originalVar: 'o…',
|
|
1030
|
+
* value: 'postAppId', source: '=response.app_id', target: '=o…', elementId }
|
|
1031
|
+
* That is the row `uip maestro case sdd convert` writes for the SDD row
|
|
1032
|
+
* `app_id -> postAppId`, and `validate --strict --sdd` matches it on
|
|
1033
|
+
* `source` + `var` (STRICT_SDD_EXTRACT_MISSING).
|
|
1034
|
+
*/
|
|
1035
|
+
function withOutputExtracts(data, task, ids, resolver) {
|
|
1036
|
+
if (!task.outputs?.length)
|
|
1037
|
+
return data;
|
|
1038
|
+
const own = Array.isArray(data.outputs) ? data.outputs : [];
|
|
1039
|
+
const elementId = own.find((row) => typeof row.elementId === 'string')?.elementId;
|
|
1040
|
+
if (!elementId)
|
|
1041
|
+
throw new Error(`task "${task.name}": output extracts need the task's resolved response output.`);
|
|
1042
|
+
data.outputs = [
|
|
1043
|
+
...own,
|
|
1044
|
+
...task.outputs.map((o, i) => emitTaskOutput(o, elementId, ids, `${elementId}:out:${i}:${o.var}`, resolver)),
|
|
1045
|
+
];
|
|
1046
|
+
return data;
|
|
1047
|
+
}
|
|
1024
1048
|
/**
|
|
1025
1049
|
* The `wait-for-connector` subscription bag (`data` on the task, `uipath` on the
|
|
1026
1050
|
* rule). Verified against a real `uip maestro case` caseplan: a placeholder
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/maestro-builder-sdk",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.56.0",
|
|
4
4
|
"description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.uipath.com/maestro",
|
|
@@ -96,5 +96,5 @@
|
|
|
96
96
|
"@types/node": "^22.7.0",
|
|
97
97
|
"esbuild": "^0.28.1"
|
|
98
98
|
},
|
|
99
|
-
"gitref": "
|
|
99
|
+
"gitref": "31230281aaf4fa5e44d2844c08747ead2814e94d"
|
|
100
100
|
}
|