@uipath/maestro-builder-sdk 6.70.6 → 6.70.7
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 +71 -70
- package/dist/api-members.md +216 -216
- package/dist/case/case-expr-check.js +81 -11
- package/dist/case/case-sdk.d.ts +44 -3
- package/dist/case/case-sdk.js +47 -3
- package/dist/case/decompile.js +31 -0
- package/dist/case/serialize.js +117 -7
- package/dist/case/task-output-ref.d.ts +111 -0
- package/dist/case/task-output-ref.js +212 -0
- package/dist/core/refusals.d.ts +1 -0
- package/dist/core/refusals.js +1 -0
- package/package.json +2 -2
|
@@ -18,20 +18,79 @@
|
|
|
18
18
|
import { checkExpression } from '../core/expr-check.js';
|
|
19
19
|
import { CompileRefusal } from '../core/compile-refusal.js';
|
|
20
20
|
import { defaultActionOutputVar } from './action-output-var.js';
|
|
21
|
+
import { lowerTaskOutputRefs, pickByName, pickOutputRow, taskOutputRefs, unresolvedTaskOutputRef } from './task-output-ref.js';
|
|
21
22
|
const NOUN = 'case variable';
|
|
22
23
|
const NAMESPACE = 'vars';
|
|
23
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The root a task-output reference stands in for while the rest of the
|
|
26
|
+
* expression is checked: `serialize` lowers the reference to the output's id,
|
|
27
|
+
* which only it knows, so the check reads it as a declared root.
|
|
28
|
+
*/
|
|
29
|
+
const TASK_OUTPUT_ROOT = '$taskOutput';
|
|
30
|
+
/**
|
|
31
|
+
* The task kinds whose output rows are all authored in `.outputs()`: a
|
|
32
|
+
* reference to any other name on them names nothing. Every other kind also
|
|
33
|
+
* writes rows of its own (a connector's `response` / `Error`, a business
|
|
34
|
+
* rule's `output`, an action's form fields), so for those `build()` resolves
|
|
35
|
+
* the stage and the task, and `serialize` resolves the output against the rows
|
|
36
|
+
* it wrote.
|
|
37
|
+
*/
|
|
38
|
+
const AUTHORED_OUTPUT_KINDS = new Set(['process', 'agent', 'rpa', 'api-workflow', 'case-management', 'flow-process', 'function']);
|
|
39
|
+
/** Why `ref` names no output in `built`, or `undefined` when it may name one. */
|
|
40
|
+
function taskOutputRefProblem(built, ref) {
|
|
41
|
+
const stage = pickByName([...new Set(built.stages.map((s) => s.label))], ref.stage, (label) => [label]);
|
|
42
|
+
if (stage === undefined)
|
|
43
|
+
return `names no stage labelled ${JSON.stringify(ref.stage)}`;
|
|
44
|
+
if (stage === 'ambiguous')
|
|
45
|
+
return 'names a stage label that more than one stage answers to';
|
|
46
|
+
const tasks = built.stages.filter((s) => s.label === stage).flatMap((s) => s.lanes.flat());
|
|
47
|
+
const task = pickByName(tasks, ref.task, (t) => [t.name]);
|
|
48
|
+
if (task === undefined)
|
|
49
|
+
return `names no task ${JSON.stringify(ref.task)} in stage ${JSON.stringify(stage)}`;
|
|
50
|
+
if (task === 'ambiguous')
|
|
51
|
+
return `names a task that more than one task in stage ${JSON.stringify(stage)} answers to`;
|
|
52
|
+
if (!AUTHORED_OUTPUT_KINDS.has(task.kind))
|
|
53
|
+
return undefined;
|
|
54
|
+
// The rows `emitTaskOutput` writes: named after the source's last segment
|
|
55
|
+
// (a custom row after its variable), writing `var`.
|
|
56
|
+
const rows = (task.outputs ?? []).map((o) => ({
|
|
57
|
+
name: o.custom ? o.var : o.source.replace(/^=/, '').split('.').pop() || o.source,
|
|
58
|
+
var: o.var,
|
|
59
|
+
}));
|
|
60
|
+
const row = pickOutputRow(rows, ref.output);
|
|
61
|
+
if (row === undefined) {
|
|
62
|
+
const offered = rows.map((r) => JSON.stringify(r.name));
|
|
63
|
+
return `names no output of task ${JSON.stringify(task.name)}, whose outputs are ${offered.length ? offered.join(', ') : 'none'}`;
|
|
64
|
+
}
|
|
65
|
+
if (row === 'ambiguous')
|
|
66
|
+
return `names more than one output of task ${JSON.stringify(task.name)}; name it by the variable it writes instead`;
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/** Every task-output reference in `value` that names nothing in `built`. */
|
|
70
|
+
function checkTaskOutputRefs(built, value, where, out) {
|
|
71
|
+
if (typeof value !== 'string')
|
|
72
|
+
return;
|
|
73
|
+
for (const ref of taskOutputRefs(value)) {
|
|
74
|
+
const why = taskOutputRefProblem(built, ref);
|
|
75
|
+
if (why)
|
|
76
|
+
out.push({ level: 'error', code: 'TASK_OUTPUT_REF_UNRESOLVED', message: unresolvedTaskOutputRef(ref, why), where });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function checkExpr(built, expr, where, roots, out) {
|
|
24
80
|
if (!expr)
|
|
25
81
|
return;
|
|
26
|
-
|
|
82
|
+
checkTaskOutputRefs(built, expr, where, out);
|
|
83
|
+
const lowered = lowerTaskOutputRefs(expr, () => TASK_OUTPUT_ROOT);
|
|
84
|
+
const scope = lowered === expr ? roots : new Set([...roots, TASK_OUTPUT_ROOT]);
|
|
85
|
+
for (const d of checkExpression(lowered, { roots: scope, noun: NOUN, namespace: NAMESPACE })) {
|
|
27
86
|
out.push({ ...d, where });
|
|
28
87
|
}
|
|
29
88
|
}
|
|
30
89
|
/** Every rule's `=js:` gate across an AND/OR rule matrix. */
|
|
31
|
-
function checkRules(rules, where, roots, out) {
|
|
90
|
+
function checkRules(built, rules, where, roots, out) {
|
|
32
91
|
for (const group of rules ?? [])
|
|
33
92
|
for (const r of group)
|
|
34
|
-
checkExpr(r.expression, where, roots, out);
|
|
93
|
+
checkExpr(built, r.expression, where, roots, out);
|
|
35
94
|
}
|
|
36
95
|
/** Every first-level expression problem in a built case, each stamped with its location. */
|
|
37
96
|
export function checkCaseExpressions(built) {
|
|
@@ -58,21 +117,32 @@ export function checkCaseExpressions(built) {
|
|
|
58
117
|
for (const o of t.eventOutputs ?? [])
|
|
59
118
|
roots.add(o.var);
|
|
60
119
|
const out = [];
|
|
61
|
-
built.slaRules.forEach((sla, i) => checkExpr(sla.expression, `case SLA #${i + 1}`, roots, out));
|
|
62
|
-
built.caseExit.forEach((ce, i) => checkRules(ce.rules, `case completion #${i + 1}`, roots, out));
|
|
120
|
+
built.slaRules.forEach((sla, i) => checkExpr(built, sla.expression, `case SLA #${i + 1}`, roots, out));
|
|
121
|
+
built.caseExit.forEach((ce, i) => checkRules(built, ce.rules, `case completion #${i + 1}`, roots, out));
|
|
63
122
|
for (const stage of built.stages) {
|
|
64
123
|
const at = `stage "${stage.label}"`;
|
|
65
124
|
for (const ec of stage.entry)
|
|
66
|
-
checkRules(ec.rules, `${at} entry`, roots, out);
|
|
125
|
+
checkRules(built, ec.rules, `${at} entry`, roots, out);
|
|
67
126
|
for (const xc of stage.exit)
|
|
68
|
-
checkRules(xc.rules, `${at} exit`, roots, out);
|
|
69
|
-
stage.slaRules.forEach((sla, i) => checkExpr(sla.expression, `${at} SLA #${i + 1}`, roots, out));
|
|
127
|
+
checkRules(built, xc.rules, `${at} exit`, roots, out);
|
|
128
|
+
stage.slaRules.forEach((sla, i) => checkExpr(built, sla.expression, `${at} SLA #${i + 1}`, roots, out));
|
|
70
129
|
for (const lane of stage.lanes) {
|
|
71
130
|
for (const task of lane) {
|
|
72
131
|
const tat = `task "${task.name}" in ${at}`;
|
|
73
|
-
checkExpr(task.skip, `${tat} skipWhen`, roots, out);
|
|
132
|
+
checkExpr(built, task.skip, `${tat} skipWhen`, roots, out);
|
|
133
|
+
// A value that is not a gate can carry a reference too: a task input, an
|
|
134
|
+
// action form input, a custom output. Only the reference is checked
|
|
135
|
+
// here (these values are not gates, so their other reads are not), so a
|
|
136
|
+
// bad one fails `build()` as it would in a gate, not only at compile.
|
|
137
|
+
for (const inp of task.inputs ?? [])
|
|
138
|
+
checkTaskOutputRefs(built, inp.value, `${tat} input "${inp.name}"`, out);
|
|
139
|
+
for (const f of task.action?.inputs ?? [])
|
|
140
|
+
checkTaskOutputRefs(built, f.value, `${tat} action input "${f.name}"`, out);
|
|
141
|
+
for (const o of task.outputs ?? [])
|
|
142
|
+
if (o.custom)
|
|
143
|
+
checkTaskOutputRefs(built, o.source, `${tat} output "${o.var}"`, out);
|
|
74
144
|
for (const ec of task.entry)
|
|
75
|
-
checkRules(ec.rules, `${tat} entry`, roots, out);
|
|
145
|
+
checkRules(built, ec.rules, `${tat} entry`, roots, out);
|
|
76
146
|
}
|
|
77
147
|
}
|
|
78
148
|
}
|
package/dist/case/case-sdk.d.ts
CHANGED
|
@@ -99,6 +99,35 @@ export interface CaseRule extends RuleOpts {
|
|
|
99
99
|
* @returns A rule to pass to `entryWhen` / `exitWhen` / `completeWhen`.
|
|
100
100
|
*/
|
|
101
101
|
export declare function rule(type: CaseRuleType, opts?: RuleOpts): CaseRule;
|
|
102
|
+
/**
|
|
103
|
+
* Read another task's output in place, in a `=js:` expression or a task input:
|
|
104
|
+
* `=vars.<id>` once compiled, where `<id>` is the id the plan gives that output.
|
|
105
|
+
* No case variable is needed, so a plan built from an SDD carries only the
|
|
106
|
+
* variables the SDD declares.
|
|
107
|
+
*
|
|
108
|
+
* It returns the SDD's own `$xref` marker, so an SDD cell that carries one
|
|
109
|
+
* can also be copied as written. The output is named by the
|
|
110
|
+
* result field it reads or by its key in `.outputs()`.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* **Pass one task's result to the next, and gate on it**
|
|
114
|
+
* ```ts
|
|
115
|
+
* casePlan('expense').stage('Stage 1', (s) => s
|
|
116
|
+
* .task('Analyze', (t) => t.agent('Analyzer', { folder: 'Shared' })
|
|
117
|
+
* .outputs({ AgentExpenseRequest: '=AgentExpenseRequest' }))
|
|
118
|
+
* .task('Process', (t) => t.process('Processor', { folder: 'Shared' })
|
|
119
|
+
* .inputs({ request: `=${taskOutput('Stage 1', 'Analyze', 'AgentExpenseRequest')}` })
|
|
120
|
+
* .skipWhen(`=js:${taskOutput('Stage 1', 'Analyze', 'AgentExpenseRequest')}.amount > 1000`)))
|
|
121
|
+
* ```
|
|
122
|
+
* compiles the input to `=vars.AgentExpenseRequest` and the gate to
|
|
123
|
+
* `=js:vars.AgentExpenseRequest?.amount > 1000`.
|
|
124
|
+
*
|
|
125
|
+
* @param stage - The producing task's stage label.
|
|
126
|
+
* @param task - The producing task's name.
|
|
127
|
+
* @param output - The output: the result field it reads (`AgentExpenseRequest`, `response`) or its `.outputs()` key.
|
|
128
|
+
* @returns The reference, to place inside an expression string.
|
|
129
|
+
*/
|
|
130
|
+
export declare function taskOutput(stage: string, task: string, output: string): string;
|
|
102
131
|
/**
|
|
103
132
|
* A pure expression gate. The receiving condition slot supplies its canonical
|
|
104
133
|
* event: `case-entered` for stage entry, `selected-tasks-completed` for stage
|
|
@@ -1219,9 +1248,21 @@ declare class TaskBuilder {
|
|
|
1219
1248
|
* name equals the field — an SDD row `estimatedAge -> estimatedAge` is
|
|
1220
1249
|
* `.var('estimatedAge', 'number')` plus
|
|
1221
1250
|
* `.outputs({ estimatedAge: { source: '=estimatedAge', type: 'number' } })`.
|
|
1222
|
-
*
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1251
|
+
*
|
|
1252
|
+
* An output that keeps its field's name (the key is the source's last
|
|
1253
|
+
* segment: `AgentExpenseRequest: '=AgentExpenseRequest'`, or
|
|
1254
|
+
* `app_id: '=response.app_id'` on a connector task) with no such declaration
|
|
1255
|
+
* is the task's own (bare) output: it stays on the task (`id == var`) and
|
|
1256
|
+
* writes no case variable. Two exceptions: a `.var()` of that name (the row
|
|
1257
|
+
* then feeds it), and a second task with the same key, which makes one shared
|
|
1258
|
+
* case variable that both write (reassigned rows plus a root entry). To keep
|
|
1259
|
+
* a second task's output of the same field on its task, give it the
|
|
1260
|
+
* Designer's suffixed key: `Result2: '=Result'` is its own bare output. This
|
|
1261
|
+
* is the output an SDD lists with no `->` arrow. Read it elsewhere with
|
|
1262
|
+
* {@link taskOutput | taskOutput('<stage>', '<task>', '<output>')} (or the
|
|
1263
|
+
* SDD's `$xref` marker copied as written), which compiles to `vars.<id>`
|
|
1264
|
+
* of that output, so a plan built from an SDD needs no case variable the SDD
|
|
1265
|
+
* does not declare.
|
|
1225
1266
|
*
|
|
1226
1267
|
* Pass `{ assign, type }` for a **custom** output: the SDD's `x = expr` row.
|
|
1227
1268
|
* When the task completes, the case variable is set to `assign` — a literal,
|
package/dist/case/case-sdk.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { connector as makeConnector, subscriptionFrom, } from '../core/actions.js';
|
|
2
2
|
import { isTriggerDescriptor } from '../core/connectors.js';
|
|
3
3
|
import { checkCaseExpressions, CaseBuildError } from './case-expr-check.js';
|
|
4
|
+
import { taskOutputMarker } from './task-output-ref.js';
|
|
4
5
|
import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS, recordAuthoredCaseVersion } from './format-profile.js';
|
|
5
6
|
import { schemaVersionRefusal } from '../schema-version.js';
|
|
6
7
|
import { CompileRefusal } from '../core/compile-refusal.js';
|
|
@@ -19,6 +20,37 @@ export { CaseBuildError } from './case-expr-check.js';
|
|
|
19
20
|
export function rule(type, opts = {}) {
|
|
20
21
|
return { rule: type, ...opts };
|
|
21
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Read another task's output in place, in a `=js:` expression or a task input:
|
|
25
|
+
* `=vars.<id>` once compiled, where `<id>` is the id the plan gives that output.
|
|
26
|
+
* No case variable is needed, so a plan built from an SDD carries only the
|
|
27
|
+
* variables the SDD declares.
|
|
28
|
+
*
|
|
29
|
+
* It returns the SDD's own `$xref` marker, so an SDD cell that carries one
|
|
30
|
+
* can also be copied as written. The output is named by the
|
|
31
|
+
* result field it reads or by its key in `.outputs()`.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* **Pass one task's result to the next, and gate on it**
|
|
35
|
+
* ```ts
|
|
36
|
+
* casePlan('expense').stage('Stage 1', (s) => s
|
|
37
|
+
* .task('Analyze', (t) => t.agent('Analyzer', { folder: 'Shared' })
|
|
38
|
+
* .outputs({ AgentExpenseRequest: '=AgentExpenseRequest' }))
|
|
39
|
+
* .task('Process', (t) => t.process('Processor', { folder: 'Shared' })
|
|
40
|
+
* .inputs({ request: `=${taskOutput('Stage 1', 'Analyze', 'AgentExpenseRequest')}` })
|
|
41
|
+
* .skipWhen(`=js:${taskOutput('Stage 1', 'Analyze', 'AgentExpenseRequest')}.amount > 1000`)))
|
|
42
|
+
* ```
|
|
43
|
+
* compiles the input to `=vars.AgentExpenseRequest` and the gate to
|
|
44
|
+
* `=js:vars.AgentExpenseRequest?.amount > 1000`.
|
|
45
|
+
*
|
|
46
|
+
* @param stage - The producing task's stage label.
|
|
47
|
+
* @param task - The producing task's name.
|
|
48
|
+
* @param output - The output: the result field it reads (`AgentExpenseRequest`, `response`) or its `.outputs()` key.
|
|
49
|
+
* @returns The reference, to place inside an expression string.
|
|
50
|
+
*/
|
|
51
|
+
export function taskOutput(stage, task, output) {
|
|
52
|
+
return taskOutputMarker(stage, task, output);
|
|
53
|
+
}
|
|
22
54
|
/**
|
|
23
55
|
* Gate a condition slot only on case data without naming its default event.
|
|
24
56
|
*
|
|
@@ -505,9 +537,21 @@ class TaskBuilder {
|
|
|
505
537
|
* name equals the field — an SDD row `estimatedAge -> estimatedAge` is
|
|
506
538
|
* `.var('estimatedAge', 'number')` plus
|
|
507
539
|
* `.outputs({ estimatedAge: { source: '=estimatedAge', type: 'number' } })`.
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
540
|
+
*
|
|
541
|
+
* An output that keeps its field's name (the key is the source's last
|
|
542
|
+
* segment: `AgentExpenseRequest: '=AgentExpenseRequest'`, or
|
|
543
|
+
* `app_id: '=response.app_id'` on a connector task) with no such declaration
|
|
544
|
+
* is the task's own (bare) output: it stays on the task (`id == var`) and
|
|
545
|
+
* writes no case variable. Two exceptions: a `.var()` of that name (the row
|
|
546
|
+
* then feeds it), and a second task with the same key, which makes one shared
|
|
547
|
+
* case variable that both write (reassigned rows plus a root entry). To keep
|
|
548
|
+
* a second task's output of the same field on its task, give it the
|
|
549
|
+
* Designer's suffixed key: `Result2: '=Result'` is its own bare output. This
|
|
550
|
+
* is the output an SDD lists with no `->` arrow. Read it elsewhere with
|
|
551
|
+
* {@link taskOutput | taskOutput('<stage>', '<task>', '<output>')} (or the
|
|
552
|
+
* SDD's `$xref` marker copied as written), which compiles to `vars.<id>`
|
|
553
|
+
* of that output, so a plan built from an SDD needs no case variable the SDD
|
|
554
|
+
* does not declare.
|
|
511
555
|
*
|
|
512
556
|
* Pass `{ assign, type }` for a **custom** output: the SDD's `x = expr` row.
|
|
513
557
|
* When the task completes, the case variable is set to `assign` — a literal,
|
package/dist/case/decompile.js
CHANGED
|
@@ -11,6 +11,7 @@ import { isUnrestorableSlotId, taskFingerprint } from './preserve.js';
|
|
|
11
11
|
import { isInSolutionKey, SOLUTION_FOLDER } from './resource-identity.js';
|
|
12
12
|
import { offlineBindingLabel } from './ids.js';
|
|
13
13
|
import { connectorPlaceholderKindOf } from './connector-placeholder.js';
|
|
14
|
+
import { raiseTaskOutputReads, referableOutputIds } from './task-output-ref.js';
|
|
14
15
|
const TASK_KIND_METHOD = {
|
|
15
16
|
process: 'process',
|
|
16
17
|
agent: 'agent',
|
|
@@ -1599,6 +1600,7 @@ function slugId(plan) {
|
|
|
1599
1600
|
export function decompileWithDiagnostics(plan, options = {}) {
|
|
1600
1601
|
const diagnostics = [];
|
|
1601
1602
|
refuseUnsupportedAuthoring(plan);
|
|
1603
|
+
plan = raiseTaskOutputRefs(plan);
|
|
1602
1604
|
const preservation = collectPreservation(plan);
|
|
1603
1605
|
const refs = buildRefs(plan, options.library, options.bestEffort ? diagnostics : undefined);
|
|
1604
1606
|
const selectNextStage = selectNextStageByExit(plan, refs);
|
|
@@ -1677,6 +1679,35 @@ export function decompileWithDiagnostics(plan, options = {}) {
|
|
|
1677
1679
|
const source = `import { ${imports.join(', ')} } from ${tsLiteral(options.importSpecifier ?? './case-sdk.js')};\n\n${diagnosticHeader}${out.toString()}\n`;
|
|
1678
1680
|
return { source, diagnostics };
|
|
1679
1681
|
}
|
|
1682
|
+
/**
|
|
1683
|
+
* The plan with each read of a task output that recompiling could not keep,
|
|
1684
|
+
* `vars.<output id>`, written as the task-output reference that names the same
|
|
1685
|
+
* row (`vars.$xref('<stage>','<task>','<output>')`, what `taskOutput()` writes;
|
|
1686
|
+
* builder-sdk#1241). Recompiling lowers it back to `vars.<id>` of that row,
|
|
1687
|
+
* whatever id the row then gets. See `referableOutputIds` for which reads.
|
|
1688
|
+
*/
|
|
1689
|
+
function raiseTaskOutputRefs(plan) {
|
|
1690
|
+
const variables = plan.variables ?? {};
|
|
1691
|
+
const roots = new Set();
|
|
1692
|
+
for (const lane of [variables.inputs, variables.outputs, variables.inputOutputs])
|
|
1693
|
+
for (const entry of lane ?? [])
|
|
1694
|
+
for (const name of [entry?.name, entry?.id])
|
|
1695
|
+
if (typeof name === 'string')
|
|
1696
|
+
roots.add(name);
|
|
1697
|
+
const byId = referableOutputIds(plan, roots);
|
|
1698
|
+
if (!byId.size)
|
|
1699
|
+
return plan;
|
|
1700
|
+
const raise = (value) => {
|
|
1701
|
+
if (typeof value === 'string')
|
|
1702
|
+
return raiseTaskOutputReads(value, byId);
|
|
1703
|
+
if (Array.isArray(value))
|
|
1704
|
+
return value.map(raise);
|
|
1705
|
+
if (value && typeof value === 'object')
|
|
1706
|
+
return Object.fromEntries(Object.entries(value).map(([name, field]) => [name, raise(field)]));
|
|
1707
|
+
return value;
|
|
1708
|
+
};
|
|
1709
|
+
return raise(plan);
|
|
1710
|
+
}
|
|
1680
1711
|
export function decompile(plan, options = {}) {
|
|
1681
1712
|
return decompileWithDiagnostics(plan, options).source;
|
|
1682
1713
|
}
|
package/dist/case/serialize.js
CHANGED
|
@@ -37,6 +37,7 @@ import { implicitInArgBuiltTrigger, unboundInArgMessage } from './in-arg-binding
|
|
|
37
37
|
import { jsonClone } from '../core/json-clone.js';
|
|
38
38
|
import { taskShapeProblems } from './task-shape.js';
|
|
39
39
|
import { connectorPlaceholderData, isConnectorPlaceholder } from './connector-placeholder.js';
|
|
40
|
+
import { lowerTaskOutputRefs, planTaskOutputs, resolveInPlan, unresolvedTaskOutputRef } from './task-output-ref.js';
|
|
40
41
|
/** The ids a serialize pass hashed from element ids, by the document it returned. */
|
|
41
42
|
const ELEMENT_SEEDED_IDS = new WeakMap();
|
|
42
43
|
function elementSeededId(resolver, elementId, suffix) {
|
|
@@ -404,8 +405,46 @@ function serializeCase(built, opts) {
|
|
|
404
405
|
// repeats the way the platform's V31/V32.0.1 migrations would, so the file is
|
|
405
406
|
// already what the platform reads (see rule-names.ts).
|
|
406
407
|
dedupeConditionNames(plan);
|
|
408
|
+
// Over the finished document too: a task-output reference resolves to the id
|
|
409
|
+
// of the row it names, and the ids are only final here.
|
|
410
|
+
lowerPlanTaskOutputRefs(plan);
|
|
407
411
|
return plan;
|
|
408
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* Lower every `vars.$xref('<stage>','<task>','<output>')` marker (what
|
|
415
|
+
* `taskOutput()` writes, and what an SDD cell carries) in the written plan to
|
|
416
|
+
* `vars.<id>` of the output row it names, as `uip maestro case sdd convert`
|
|
417
|
+
* does (builder-sdk#1241). A marker that names no output is refused: left in
|
|
418
|
+
* place it reads nothing at run time, and the default `validate` profile
|
|
419
|
+
* reports only that the marker survived.
|
|
420
|
+
*/
|
|
421
|
+
function lowerPlanTaskOutputRefs(plan) {
|
|
422
|
+
let tasks;
|
|
423
|
+
const lower = (text) => lowerTaskOutputRefs(text, (ref) => {
|
|
424
|
+
tasks ??= planTaskOutputs(plan);
|
|
425
|
+
const resolved = resolveInPlan(tasks, ref);
|
|
426
|
+
if ('why' in resolved)
|
|
427
|
+
throw new CompileRefusal('TASK_OUTPUT_REF_UNRESOLVED', unresolvedTaskOutputRef(ref, resolved.why));
|
|
428
|
+
// A stand-in exists only in collect mode, which writes no plan: the marker is
|
|
429
|
+
// left, and the stood-in task's own refusal is what `check` reports.
|
|
430
|
+
return 'standIn' in resolved ? undefined : resolved.variable;
|
|
431
|
+
});
|
|
432
|
+
const walk = (value) => {
|
|
433
|
+
if (typeof value === 'string')
|
|
434
|
+
return value.includes('$xref') ? lower(value) : value;
|
|
435
|
+
if (Array.isArray(value)) {
|
|
436
|
+
for (let i = 0; i < value.length; i++)
|
|
437
|
+
value[i] = walk(value[i]);
|
|
438
|
+
}
|
|
439
|
+
else if (value && typeof value === 'object') {
|
|
440
|
+
const record = value;
|
|
441
|
+
for (const key of Object.keys(record))
|
|
442
|
+
record[key] = walk(record[key]);
|
|
443
|
+
}
|
|
444
|
+
return value;
|
|
445
|
+
};
|
|
446
|
+
walk(plan);
|
|
447
|
+
}
|
|
409
448
|
/** The task types whose `data.context[]` carries an Integration Service activity configuration. */
|
|
410
449
|
const CONNECTOR_CONFIG_TASK_TYPES = new Set([
|
|
411
450
|
'execute-connector-activity',
|
|
@@ -866,6 +905,17 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
866
905
|
row.value = wanted;
|
|
867
906
|
});
|
|
868
907
|
};
|
|
908
|
+
// Rows `reassignCarried` turned back into reassignments, and the variables
|
|
909
|
+
// they write: each needs its root companion (added below, before the root
|
|
910
|
+
// variable ids are restored).
|
|
911
|
+
const carriedCompanions = new Map();
|
|
912
|
+
const reassignCarried = (row, id) => {
|
|
913
|
+
row.id = id;
|
|
914
|
+
row.originalVar = id;
|
|
915
|
+
row.target = `=${id}`;
|
|
916
|
+
if (!carriedCompanions.has(row.var))
|
|
917
|
+
carriedCompanions.set(row.var, { id: row.var, name: row.var, type: row.type, elementId: 'root', custom: true });
|
|
918
|
+
};
|
|
869
919
|
const nodes = result.nodes;
|
|
870
920
|
const stages = nodes.filter((node) => String(node.type).endsWith('Stage'));
|
|
871
921
|
const bagField = CASE_FORMAT_PROFILE.waitConnectorRuleBagField;
|
|
@@ -902,6 +952,19 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
902
952
|
// A positional match never takes a row a name match owns.
|
|
903
953
|
: named.has(outputRows[index]?.name) ? undefined : outputRows[index];
|
|
904
954
|
const currentId = target?.id;
|
|
955
|
+
// A nested own-name row (`app_id: '=response.app_id'`, written bare since
|
|
956
|
+
// builder-sdk#1241) whose carried id is not its variable is a row an
|
|
957
|
+
// SDK 6.67.1-era plan wrote reassigned, with a root companion that
|
|
958
|
+
// readers spell (`=vars.app_id`). It keeps that shape: the carried slot id
|
|
959
|
+
// goes on the row, `var` stays, and the companion comes back. Decided
|
|
960
|
+
// here, on identities `followRenames` has already paired, so a renamed
|
|
961
|
+
// task or stage keeps it too.
|
|
962
|
+
if (target && typeof row.id === 'string' && isCarriedNestedReassignment(target, row.id) && !heldOutputIds.has(row.id)) {
|
|
963
|
+
reassignCarried(target, row.id);
|
|
964
|
+
heldOutputIds.delete(currentId);
|
|
965
|
+
heldOutputIds.add(row.id);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
905
968
|
// An own-name row's id IS its variable's name (`emitTaskOutput`). A
|
|
906
969
|
// preserved id that differs — an `o<hash>` slot from a plan an older
|
|
907
970
|
// SDK wrote — would rename the variable out from under every
|
|
@@ -957,6 +1020,20 @@ function applyPreservedIdentities(result, preservation) {
|
|
|
957
1020
|
takeConditions(metadata.caseExitRules, identities.caseExitRules);
|
|
958
1021
|
takeSlas(metadata.slaRules, identities.slaRules);
|
|
959
1022
|
const variables = result.variables;
|
|
1023
|
+
if (carriedCompanions.size) {
|
|
1024
|
+
const inputOutputs = (variables.inputOutputs ??= []);
|
|
1025
|
+
const rootIds = new Set([...(variables.inputs ?? []), ...(variables.outputs ?? []), ...inputOutputs].map((entry) => entry?.id));
|
|
1026
|
+
for (const [name, companion] of carriedCompanions)
|
|
1027
|
+
if (!rootIds.has(name))
|
|
1028
|
+
inputOutputs.push(companion);
|
|
1029
|
+
// Any other row that writes the variable under its own name now aliases the
|
|
1030
|
+
// root entry, the same rule `serialize` applies (`originalVar == id`).
|
|
1031
|
+
for (const stage of stages)
|
|
1032
|
+
for (const task of (stage.data?.tasks ?? []).flat())
|
|
1033
|
+
for (const row of task?.data?.outputs ?? [])
|
|
1034
|
+
if (carriedCompanions.has(row?.var) && row.id === row.var && row.originalVar === undefined)
|
|
1035
|
+
row.originalVar = row.id;
|
|
1036
|
+
}
|
|
960
1037
|
for (const [lane, rows] of Object.entries(identities.variables ?? {}))
|
|
961
1038
|
(rows ?? []).forEach((row) => {
|
|
962
1039
|
if (isUnrestorableSlotId(lane, row))
|
|
@@ -1722,13 +1799,16 @@ const OWN_NAME_OUTPUT_ROWS = new WeakSet();
|
|
|
1722
1799
|
* One `data.outputs[]` row: `source` field → the `var` case variable. Two shapes,
|
|
1723
1800
|
* as the Case Designer writes them:
|
|
1724
1801
|
*
|
|
1725
|
-
* - **own name** — the output keeps its field's name (
|
|
1726
|
-
*
|
|
1802
|
+
* - **own name** — the output keeps its field's name (the `var` is the source's
|
|
1803
|
+
* last segment, or the Designer's camelCase of it, either one optionally
|
|
1727
1804
|
* with a numeric suffix: `=APIOutput1` into `APIOutput1`, `aPIOutput1` or
|
|
1728
|
-
* `aPIOutput12`; see `keepsOwnName`).
|
|
1729
|
-
*
|
|
1805
|
+
* `aPIOutput12`, and `=response.app_id` into `app_id`; see `keepsOwnName`).
|
|
1806
|
+
* The row's `id` IS the `var`, with no `originalVar`:
|
|
1807
|
+
* `{ id: 'APIOutput1', var: 'APIOutput1', target: '=APIOutput1' }`. This is
|
|
1808
|
+
* the task-scoped output: it writes no case variable, and a later task reads
|
|
1809
|
+
* it in place, `=vars.<id>`, or by `taskOutput(stage, task, output)`.
|
|
1730
1810
|
* - **reassigned** — the output feeds a differently named variable (`=age` into
|
|
1731
|
-
* `estimatedAge`,
|
|
1811
|
+
* `estimatedAge`, `=Error.Message` into `errorMsg`). The row keeps its own minted
|
|
1732
1812
|
* slot `id`, points `var` at the case variable, and repeats the slot in
|
|
1733
1813
|
* `originalVar`, which is how the Designer recognises a reassignment.
|
|
1734
1814
|
*
|
|
@@ -2277,12 +2357,42 @@ function claimOutputId(resolver, base) {
|
|
|
2277
2357
|
resolver.outputIds.add(id);
|
|
2278
2358
|
return id;
|
|
2279
2359
|
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Whether `row` is a nested own-name output (a dotted `source` whose last
|
|
2362
|
+
* segment keeps its name, written with `id == var`) and the carried plan gave
|
|
2363
|
+
* it a different id, i.e. wrote it reassigned. See `applyPreservedIdentities`.
|
|
2364
|
+
*
|
|
2365
|
+
* An own-name row has two shapes, and both count: bare (no `originalVar`), and
|
|
2366
|
+
* the alias of a root variable of the same name (`originalVar == id`), which is
|
|
2367
|
+
* how it is written when a `.var()` declares that variable. Example: the
|
|
2368
|
+
* phase5-1 business-rule fixture declares `riskBand` and carries
|
|
2369
|
+
* `{ id: 'oMvpql9VW', var: 'riskBand', originalVar: 'oMvpql9VW' }`; it compiles
|
|
2370
|
+
* as the alias `{ id: 'riskBand', originalVar: 'riskBand' }` and is a carried
|
|
2371
|
+
* reassignment all the same. A row whose `originalVar` names another id is a
|
|
2372
|
+
* reassignment already, and `id == var` rules it out above.
|
|
2373
|
+
*/
|
|
2374
|
+
function isCarriedNestedReassignment(row, carriedId) {
|
|
2375
|
+
if (!row || row.custom === true || typeof row.id !== 'string' || row.id !== row.var)
|
|
2376
|
+
return false;
|
|
2377
|
+
if (row.originalVar !== undefined && row.originalVar !== row.id)
|
|
2378
|
+
return false;
|
|
2379
|
+
if (carriedId === row.id || typeof row.source !== 'string')
|
|
2380
|
+
return false;
|
|
2381
|
+
const path = row.source.replace(/^=/, '');
|
|
2382
|
+
return path.includes('.') && keepsOwnName(row.var, path.split('.').pop() ?? '');
|
|
2383
|
+
}
|
|
2280
2384
|
/** Whether a task output is written in the own-name shape (see `emitTaskOutput`). */
|
|
2281
2385
|
function isOwnNameOutput(o) {
|
|
2282
2386
|
if (o.custom)
|
|
2283
2387
|
return false;
|
|
2284
|
-
|
|
2285
|
-
|
|
2388
|
+
// The field is the path's last segment, as the row's `name` is: a connector
|
|
2389
|
+
// task's `app_id: '=response.app_id'` keeps its field's name exactly as
|
|
2390
|
+
// `=APIOutput1` into `APIOutput1` does. `uip maestro case sdd convert` writes
|
|
2391
|
+
// that bare row (`outputFor`: `{ id: 'app_id', var: 'app_id', source:
|
|
2392
|
+
// '=response.app_id' }`), so it stays on the task with no root companion
|
|
2393
|
+
// (builder-sdk#1241).
|
|
2394
|
+
const field = o.source.replace(/^=/, '').split('.').pop() ?? '';
|
|
2395
|
+
return keepsOwnName(o.var, field);
|
|
2286
2396
|
}
|
|
2287
2397
|
/**
|
|
2288
2398
|
* Every variable a task in the plan writes under its own name: own-name task
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading another task's output in place: the SDD's `vars.$xref('<stage>',
|
|
3
|
+
* '<task>', '<output>')` marker, and `taskOutput()`, the typed way to write it
|
|
4
|
+
* (builder-sdk#1241).
|
|
5
|
+
*
|
|
6
|
+
* A task's own output is a variable of its own: the Case Designer saves it as
|
|
7
|
+
* `{ name: 'AgentExpenseRequest', id: 'agentExpenseRequest', var:
|
|
8
|
+
* 'agentExpenseRequest' }`, with no root `variables` entry, and a later task
|
|
9
|
+
* reads it as `=vars.agentExpenseRequest`. The id is the plan's, not the
|
|
10
|
+
* author's: it is unique across the plan, so a second task's `Action` output
|
|
11
|
+
* is `action2`. An SDD therefore names an output by where it is produced, and
|
|
12
|
+
* `uip maestro case sdd convert` lowers each marker to `vars.<output id>`
|
|
13
|
+
* (`sdd-convert/xrefs.ts`). Without the lowering here, an author had to copy
|
|
14
|
+
* the value into a case variable the SDD does not declare, and `validate
|
|
15
|
+
* --strict --sdd` refuses each one (`STRICT_SDD_VARIABLE_EXTRA`).
|
|
16
|
+
*
|
|
17
|
+
* The rule lives here so `check` (at `build()`) and `serialize` read a marker
|
|
18
|
+
* the same way. `build()` cannot see the ids, which `serialize` mints, so it
|
|
19
|
+
* resolves the stage and the task, and the output where the task's rows are
|
|
20
|
+
* all authored (a reference-mode task). `serialize` resolves every marker
|
|
21
|
+
* against the rows it wrote and refuses one that names no output.
|
|
22
|
+
*/
|
|
23
|
+
/** The marker's three arguments. */
|
|
24
|
+
export interface TaskOutputRef {
|
|
25
|
+
stage: string;
|
|
26
|
+
task: string;
|
|
27
|
+
output: string;
|
|
28
|
+
}
|
|
29
|
+
/** An output row, as far as a reference to it is concerned. */
|
|
30
|
+
export interface OutputRowKey {
|
|
31
|
+
/** The row's `name`: the result field it reads (`AgentExpenseRequest`, `response`). */
|
|
32
|
+
name?: string;
|
|
33
|
+
/** The variable the row writes. */
|
|
34
|
+
var?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Each marker in `text`, in order, with where it sits. */
|
|
37
|
+
export declare function taskOutputRefs(text: string): Array<TaskOutputRef & {
|
|
38
|
+
whole: string;
|
|
39
|
+
index: number;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Replace every marker in `text` with what `lower` returns for it. In a `=js:`
|
|
43
|
+
* expression, a marker followed by a property read gets the optional chain
|
|
44
|
+
* (`vars.<id>?.field`); any other value keeps the plain read (`vars.<id>.field`).
|
|
45
|
+
*
|
|
46
|
+
* @param text - The expression.
|
|
47
|
+
* @param lower - The variable a marker reads (`agentExpenseRequest`), or `undefined` to keep the marker.
|
|
48
|
+
*/
|
|
49
|
+
export declare function lowerTaskOutputRefs(text: string, lower: (ref: TaskOutputRef) => string | undefined): string;
|
|
50
|
+
/**
|
|
51
|
+
* The one candidate `name` picks out: an exact match first, then a
|
|
52
|
+
* case-insensitive one (an SDD addresses a stage or an output in any case).
|
|
53
|
+
* `undefined` when none does; `'ambiguous'` when more than one does.
|
|
54
|
+
*/
|
|
55
|
+
export declare function pickByName<T>(items: readonly T[], name: string, names: (item: T) => Array<string | undefined>): T | 'ambiguous' | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* The row an output reference names among one task's rows: by the field it
|
|
58
|
+
* reads (`name`) or by the variable it writes (`var`).
|
|
59
|
+
*/
|
|
60
|
+
export declare function pickOutputRow<R extends OutputRowKey>(rows: readonly R[], output: string): R | 'ambiguous' | undefined;
|
|
61
|
+
/** The refusal message for a marker that resolves to nothing. */
|
|
62
|
+
export declare function unresolvedTaskOutputRef(ref: TaskOutputRef, why: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* The marker for a reference: `vars.$xref('<stage>','<task>','<output>')`,
|
|
65
|
+
* each argument single-quoted with its quotes and backslashes escaped. What
|
|
66
|
+
* `taskOutput()` returns, and what decompile writes back.
|
|
67
|
+
*/
|
|
68
|
+
export declare function taskOutputMarker(stage: string, task: string, output: string): string;
|
|
69
|
+
/** One task's output rows in a written plan, under the names a reference uses. */
|
|
70
|
+
export interface PlanTaskOutputs {
|
|
71
|
+
stage: string;
|
|
72
|
+
task: string;
|
|
73
|
+
rows: Array<OutputRowKey & {
|
|
74
|
+
id?: string;
|
|
75
|
+
}>;
|
|
76
|
+
/**
|
|
77
|
+
* A refused task's stand-in, in `check`'s collect mode (`data.standIn`): it
|
|
78
|
+
* wrote no rows, so a reference to it cannot be resolved against them.
|
|
79
|
+
*/
|
|
80
|
+
standIn?: boolean;
|
|
81
|
+
}
|
|
82
|
+
/** Every stage task's output rows in a written caseplan, in document order. */
|
|
83
|
+
export declare function planTaskOutputs(plan: unknown): PlanTaskOutputs[];
|
|
84
|
+
/**
|
|
85
|
+
* What a reference reads in a written plan: the variable of the row it names
|
|
86
|
+
* (`vars.<id>`, or a custom row's `var`, which has no id), or why it names none.
|
|
87
|
+
*/
|
|
88
|
+
export declare function resolveInPlan(tasks: readonly PlanTaskOutputs[], ref: TaskOutputRef): {
|
|
89
|
+
variable: string;
|
|
90
|
+
} | {
|
|
91
|
+
why: string;
|
|
92
|
+
} | {
|
|
93
|
+
standIn: true;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Turn each `vars.<output id>` read in `text` that only a task-output reference
|
|
97
|
+
* can say back into `vars.$xref(…)`, for decompile. `byId` maps such an id to
|
|
98
|
+
* the reference that names it.
|
|
99
|
+
*/
|
|
100
|
+
export declare function raiseTaskOutputReads(text: string, byId: ReadonlyMap<string, TaskOutputRef>): string;
|
|
101
|
+
/**
|
|
102
|
+
* The output ids a decompiled source can only read back by reference: a row
|
|
103
|
+
* whose id is not the variable it writes (a reassigned extract, a Designer row
|
|
104
|
+
* such as `{ id: 'oA1b2C3d4', var: 'AgentExpenseRequest' }`). Recompiling mints
|
|
105
|
+
* that id afresh, and no declaration makes `vars.<id>` readable, so a
|
|
106
|
+
* `vars.<id>` read in the plan goes back to the source as the reference that
|
|
107
|
+
* resolves to the same row. A read of an id that is also a variable (an
|
|
108
|
+
* own-name row, a root variable) is left as written. A row the reference
|
|
109
|
+
* could not name uniquely is left as written too.
|
|
110
|
+
*/
|
|
111
|
+
export declare function referableOutputIds(plan: unknown, rootNames: ReadonlySet<string>): Map<string, TaskOutputRef>;
|