@uipath/maestro-builder-sdk 6.31.8 → 6.32.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 +92 -90
- package/dist/api-members.md +327 -321
- package/dist/check.js +47 -10
- package/dist/core/actions.d.ts +50 -4
- package/dist/core/form-field-type.d.ts +80 -14
- package/dist/core/form-field-type.js +132 -28
- package/dist/decompile.js +19 -6
- package/dist/flow-sdk.d.ts +19 -2
- package/dist/flow-sdk.js +1 -1
- package/dist/nodes/hitl/flow/check.js +7 -1
- package/dist/nodes/hitl/flow/node-type.js +13 -1
- package/dist/serialize.js +23 -9
- package/package.json +2 -2
package/dist/check.js
CHANGED
|
@@ -10,7 +10,7 @@ import { FlowNode } from './core/node-classes.js';
|
|
|
10
10
|
import { planContributionBindings } from './core/contribution-bindings.js';
|
|
11
11
|
import { definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
|
|
12
12
|
import { actionVersion, bundledActionNodeType, bundledContainerDefinition, bundledDefinition, perCallDefinition, resolveContributionDefinition } from './serialize.js';
|
|
13
|
-
import { FORM_FIELD_TYPES, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText } from './core/form-field-type.js';
|
|
13
|
+
import { FORM_FIELD_TYPES, formFieldOptionsProblem, formFieldOptionsWhy, formFieldTypeHint, formFieldTypeWhy, validFormFieldTypesText } from './core/form-field-type.js';
|
|
14
14
|
import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
|
|
15
15
|
import { checkEventResolution, checkEventSubscription, eventParamsFor, } from './core/event-checks.js';
|
|
16
16
|
import { checkCookedEscapes } from './flow-expr-check.js';
|
|
@@ -3418,6 +3418,16 @@ function checkInlineAgent(step, inputs, diags, opts = {}) {
|
|
|
3418
3418
|
+ `${JSON.stringify(f?.type)}, ${formFieldTypeWhy(f?.type, `which the form does not recognize — it would `
|
|
3419
3419
|
+ `be published as a plain string`)}. Valid: ${validFormFieldTypesText()}.`, formFieldTypeHint(f?.type));
|
|
3420
3420
|
}
|
|
3421
|
+
const optionsProblem = formFieldOptionsProblem(f?.type, f?.options);
|
|
3422
|
+
if (optionsProblem !== undefined) {
|
|
3423
|
+
const message = `Inline agent step "${step}"'s quick-form escalation field "${String(f?.id ?? '?')}" `
|
|
3424
|
+
+ `${formFieldOptionsWhy(optionsProblem, f?.type)}.`;
|
|
3425
|
+
if (optionsProblem === 'ignored')
|
|
3426
|
+
warn('INLINE_AGENT_ESCALATION_FIELD_OPTIONS_IGNORED', message);
|
|
3427
|
+
else {
|
|
3428
|
+
at('INLINE_AGENT_ESCALATION_FIELD_OPTIONS', message, `options: [{ id: 'opt-1', value: 'high', label: 'High' }, { id: 'opt-2', value: 'low', label: 'Low' }]`);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3421
3431
|
}
|
|
3422
3432
|
continue;
|
|
3423
3433
|
}
|
|
@@ -4548,17 +4558,36 @@ function checkStepRefs(built, diags) {
|
|
|
4548
4558
|
const scopeOf = new Map();
|
|
4549
4559
|
/** Step name → the parallel arm it lives in, as `<parallel>#<index>`. */
|
|
4550
4560
|
const armOf = new Map();
|
|
4561
|
+
/**
|
|
4562
|
+
* How many nodes answer to each NAMED End's name (`.return({}, { name })`), and
|
|
4563
|
+
* how many of those are steps. A named End is a ref target too (#1223): a side
|
|
4564
|
+
* path that runs into the main path's End is one End with two incoming edges,
|
|
4565
|
+
* which the product accepts and the canvas draws. Counted so a ref to a name
|
|
4566
|
+
* two nodes share is refused rather than wired to whichever came first.
|
|
4567
|
+
*/
|
|
4568
|
+
const endNames = new Map();
|
|
4569
|
+
const stepNamesSeen = new Set();
|
|
4551
4570
|
let seq = 0;
|
|
4552
4571
|
const index = (steps, portList, scope, arm) => {
|
|
4553
4572
|
for (const s of steps) {
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
if (
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4573
|
+
// A `return`'s own `name` is synthetic; its End is named by `options.name`.
|
|
4574
|
+
const name = s.kind === 'return' ? s.options?.name : 'name' in s ? s.name : undefined;
|
|
4575
|
+
if (name !== undefined) {
|
|
4576
|
+
if (s.kind === 'return')
|
|
4577
|
+
endNames.set(name, (endNames.get(name) ?? 0) + 1);
|
|
4578
|
+
else
|
|
4579
|
+
stepNamesSeen.add(name);
|
|
4580
|
+
// Exactly the step rule from before, for steps. A named End only fills a
|
|
4581
|
+
// name nothing has claimed; a clash is REF_AMBIGUOUS_TARGET below.
|
|
4582
|
+
if (s.kind !== 'return' || !order.has(name)) {
|
|
4583
|
+
if (!order.has(name))
|
|
4584
|
+
order.set(name, seq);
|
|
4585
|
+
if (portList)
|
|
4586
|
+
inPortList.add(name);
|
|
4587
|
+
scopeOf.set(name, scope);
|
|
4588
|
+
if (arm)
|
|
4589
|
+
armOf.set(name, arm);
|
|
4590
|
+
}
|
|
4562
4591
|
}
|
|
4563
4592
|
seq++;
|
|
4564
4593
|
if (s.kind === 'stepToList')
|
|
@@ -4620,7 +4649,15 @@ function checkStepRefs(built, diags) {
|
|
|
4620
4649
|
if (!order.has(target)) {
|
|
4621
4650
|
const near = [...order.keys()].find((n) => n.toLowerCase() === target.toLowerCase());
|
|
4622
4651
|
say('error', 'REF_UNKNOWN_TARGET', `.stepToRef('${target}') ${where} names a step this flow does not have`
|
|
4623
|
-
+ `${near ? ` (did you mean "${near}"?)` : ''}. It takes the same name you passed to .step()
|
|
4652
|
+
+ `${near ? ` (did you mean "${near}"?)` : ''}. It takes the same name you passed to .step(), or the `
|
|
4653
|
+
+ `\`name\` of a \`.return({}, { name })\` End.`, near ? `.stepToRef('${near}')` : undefined);
|
|
4654
|
+
continue;
|
|
4655
|
+
}
|
|
4656
|
+
const ends = endNames.get(target) ?? 0;
|
|
4657
|
+
if (ends > 0 && ends + (stepNamesSeen.has(target) ? 1 : 0) > 1) {
|
|
4658
|
+
say('error', 'REF_AMBIGUOUS_TARGET', `.stepToRef('${target}') ${where} names ${stepNamesSeen.has(target) ? 'both a step and an End' : `${ends} Ends`} `
|
|
4659
|
+
+ `called "${target}", so it is not clear which one the path should go to. Give each \`.return({}, { name })\` `
|
|
4660
|
+
+ `a name no other step or End uses.`);
|
|
4624
4661
|
continue;
|
|
4625
4662
|
}
|
|
4626
4663
|
// A ref to a container the path is INSIDE names it as a step, so the edge
|
package/dist/core/actions.d.ts
CHANGED
|
@@ -62,15 +62,46 @@ export interface ConnectorOpts {
|
|
|
62
62
|
*/
|
|
63
63
|
export type CanonicalFormFieldType = 'string' | 'number' | 'integer' | 'float' | 'double' | 'boolean' | 'date' | 'datetime' | 'file' | 'object' | 'array';
|
|
64
64
|
/**
|
|
65
|
-
*
|
|
65
|
+
* The platform's choice-control spellings of a field type. Each names the data
|
|
66
|
+
* the field holds AND how it renders (`@uipath/hitl-schema-types`
|
|
67
|
+
* `resolveFieldType` + `controlForDeclaredType`): `singleSelect` is a `string`
|
|
68
|
+
* shown as a dropdown, `multiSelect` an `array` shown as a multi-select, and
|
|
69
|
+
* `enum` (what the designer's variable picker writes for a closed set) a
|
|
70
|
+
* `string` shown as a dropdown. The choices are the field's `options`.
|
|
71
|
+
*/
|
|
72
|
+
export type ControlFormFieldType = 'singleSelect' | 'multiSelect' | 'enum';
|
|
73
|
+
/**
|
|
74
|
+
* A form field's type: one of the platform's canonical types; `'text'` /
|
|
66
75
|
* `'date-time'`, the SDK's older spellings of `'string'` / `'datetime'` (the
|
|
67
|
-
* platform resolves both)
|
|
68
|
-
*
|
|
76
|
+
* platform resolves both); or a choice-control spelling (`'singleSelect'`,
|
|
77
|
+
* `'multiSelect'`, `'enum'`), which the form renders as a picker over the
|
|
78
|
+
* field's `options`. A quick form with any other type fails the platform's
|
|
79
|
+
* validation, which is why `check` rejects it.
|
|
69
80
|
*
|
|
70
81
|
* @example
|
|
71
82
|
* hitl({ fields: [{ id: 'dueOn', type: 'date', direction: 'output' }], outcomes: ['Done'] })
|
|
83
|
+
* hitl({
|
|
84
|
+
* fields: [{
|
|
85
|
+
* id: 'priority', type: 'singleSelect', direction: 'output',
|
|
86
|
+
* options: [{ id: 'opt-1', value: 'high', label: 'High' }, { id: 'opt-2', value: 'low', label: 'Low' }],
|
|
87
|
+
* }],
|
|
88
|
+
* outcomes: ['Done'],
|
|
89
|
+
* })
|
|
90
|
+
*/
|
|
91
|
+
export type FormFieldType = CanonicalFormFieldType | 'text' | 'date-time' | ControlFormFieldType;
|
|
92
|
+
/**
|
|
93
|
+
* One choice of a picker field (`@uipath/hitl-schema-types` `optionShape`):
|
|
94
|
+
* `value` is what the answer holds, `label` what the reviewer sees, and `id`
|
|
95
|
+
* the option's own stable id. All three are required by the platform.
|
|
72
96
|
*/
|
|
73
|
-
export
|
|
97
|
+
export interface FormFieldOption {
|
|
98
|
+
/** The option's own stable id, e.g. `'opt-1'`. */
|
|
99
|
+
id: string;
|
|
100
|
+
/** What the answer holds when the reviewer picks it, e.g. `'high'`. */
|
|
101
|
+
value: string;
|
|
102
|
+
/** What the reviewer sees, e.g. `'High'`. */
|
|
103
|
+
label: string;
|
|
104
|
+
}
|
|
74
105
|
/** A value the reviewer READS: shown on the form, filled in from the flow. */
|
|
75
106
|
export interface ShownField {
|
|
76
107
|
/** Field id — the key the answer object uses. Must be unique on the form. */
|
|
@@ -79,6 +110,11 @@ export interface ShownField {
|
|
|
79
110
|
label?: string;
|
|
80
111
|
type: FormFieldType;
|
|
81
112
|
direction: 'input';
|
|
113
|
+
/**
|
|
114
|
+
* The choices of a picker field — a `singleSelect` / `enum` dropdown or a
|
|
115
|
+
* `multiSelect`. Written to the form verbatim, in order.
|
|
116
|
+
*/
|
|
117
|
+
options?: FormFieldOption[];
|
|
82
118
|
/**
|
|
83
119
|
* What to show — a reference, e.g. `input('amount')` or
|
|
84
120
|
* `out('fetchInvoice', 'body.total')`. Required: a shown field with nothing
|
|
@@ -95,6 +131,11 @@ export interface AskedField {
|
|
|
95
131
|
label?: string;
|
|
96
132
|
type: FormFieldType;
|
|
97
133
|
direction: 'output';
|
|
134
|
+
/**
|
|
135
|
+
* The choices of a picker field — a `singleSelect` / `enum` dropdown or a
|
|
136
|
+
* `multiSelect`. Written to the form verbatim, in order.
|
|
137
|
+
*/
|
|
138
|
+
options?: FormFieldOption[];
|
|
98
139
|
/** Reviewer must fill it in before the task can be completed. */
|
|
99
140
|
required?: boolean;
|
|
100
141
|
}
|
|
@@ -111,6 +152,11 @@ export interface InOutField {
|
|
|
111
152
|
label?: string;
|
|
112
153
|
type: FormFieldType;
|
|
113
154
|
direction: 'inOut';
|
|
155
|
+
/**
|
|
156
|
+
* The choices of a picker field — a `singleSelect` / `enum` dropdown or a
|
|
157
|
+
* `multiSelect`. Written to the form verbatim, in order.
|
|
158
|
+
*/
|
|
159
|
+
options?: FormFieldOption[];
|
|
114
160
|
/** What to pre-fill — a reference, e.g. `out('fetchInvoice', 'body.total')`. */
|
|
115
161
|
value: Expr;
|
|
116
162
|
/** Reviewer must confirm/fill it before the task can be completed. */
|
|
@@ -14,35 +14,59 @@
|
|
|
14
14
|
* on it at runtime. This module is a transcription, and
|
|
15
15
|
* `tests/hitl-field-type-contract.test.ts` runs the real package against it:
|
|
16
16
|
* the canonical set, each type's JSON Schema, and every alias below must agree
|
|
17
|
-
* with the pinned `@uipath/hitl-schema-types`. A canonical-set
|
|
18
|
-
* change upstream fails there; a new alias does not.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* with the pinned `@uipath/hitl-schema-types`. A canonical-set, JSON Schema or
|
|
18
|
+
* control-spelling change upstream fails there; a new data alias does not.
|
|
19
|
+
*
|
|
20
|
+
* Besides data types, the product accepts three CHOICE-CONTROL spellings —
|
|
21
|
+
* `singleSelect`, `multiSelect`, `enum` — which name both the data the field
|
|
22
|
+
* holds and the control it renders with (#1235). The SDK accepts them in
|
|
23
|
+
* source and writes them as written, so a canvas dropdown stays a dropdown.
|
|
21
24
|
*
|
|
22
25
|
* Every SDK path reads field types from here: `check` (human tasks and inline
|
|
23
26
|
* agent quick-form escalations), decompile, and the serializers' output
|
|
24
27
|
* schemas.
|
|
25
28
|
*/
|
|
26
|
-
import type { CanonicalFormFieldType } from './actions.js';
|
|
29
|
+
import type { CanonicalFormFieldType, ControlFormFieldType, FormFieldOption } from './actions.js';
|
|
27
30
|
/** The product's 11 canonical quick-form field types, in its own order. */
|
|
28
31
|
export declare const CANONICAL_FORM_FIELD_TYPES: CanonicalFormFieldType[];
|
|
29
32
|
/** The SDK's legacy spellings (`text`, `date-time`), for diagnostics that list them. */
|
|
30
33
|
export declare const LEGACY_FORM_FIELD_TYPES: string[];
|
|
31
|
-
/**
|
|
34
|
+
/** The choice-control spellings, for `check`'s messages and the contract test. */
|
|
35
|
+
export declare const CONTROL_FORM_FIELD_TYPES: ControlFormFieldType[];
|
|
36
|
+
/**
|
|
37
|
+
* Every form-field type the SDK accepts in source: the 11 canonical ones, the
|
|
38
|
+
* SDK's legacy aliases, and the three choice-control spellings.
|
|
39
|
+
*/
|
|
32
40
|
export declare const FORM_FIELD_TYPES: string[];
|
|
33
41
|
/**
|
|
34
42
|
* The tail of a "Valid: …" diagnostic: the canonical types, then the legacy
|
|
35
|
-
* spellings in parentheses.
|
|
43
|
+
* spellings and the choice controls in parentheses.
|
|
36
44
|
*
|
|
37
45
|
* @example
|
|
38
|
-
* validFormFieldTypesText() // 'string,
|
|
46
|
+
* validFormFieldTypesText() // 'string, …, array (or the older text, date-time; or a choice control: singleSelect, …)'
|
|
39
47
|
*/
|
|
40
48
|
export declare function validFormFieldTypesText(): string;
|
|
49
|
+
/**
|
|
50
|
+
* The data type and control a choice-control spelling names, or `undefined`
|
|
51
|
+
* for any other type.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* controlFormFieldType('singleSelect') // { data: 'string', control: 'dropdown' }
|
|
55
|
+
* controlFormFieldType('multiSelect') // { data: 'array', control: 'multiSelect' }
|
|
56
|
+
* controlFormFieldType('string') // undefined
|
|
57
|
+
*/
|
|
58
|
+
export declare function controlFormFieldType(type: unknown): {
|
|
59
|
+
data: CanonicalFormFieldType;
|
|
60
|
+
control: string;
|
|
61
|
+
} | undefined;
|
|
41
62
|
/** The product aliases, for the contract test. */
|
|
42
63
|
export declare const FORM_FIELD_TYPE_ALIASES: Readonly<Record<string, CanonicalFormFieldType>>;
|
|
43
64
|
/**
|
|
44
65
|
* The canonical type a wire or source spelling names, or `undefined` when it
|
|
45
|
-
* names none the SDK models.
|
|
66
|
+
* names none the SDK models. A choice-control spelling is its own wire
|
|
67
|
+
* spelling, so it is `undefined` here too — its data type is
|
|
68
|
+
* `controlFormFieldType(t).data`; resolving it here would let a caller write
|
|
69
|
+
* `string` for a dropdown.
|
|
46
70
|
*
|
|
47
71
|
* @example
|
|
48
72
|
* canonicalFormFieldType('integer') // 'integer'
|
|
@@ -71,11 +95,9 @@ export declare function canonicalFormFieldType(type: unknown): CanonicalFormFiel
|
|
|
71
95
|
export declare function sourceFormFieldType(wire: unknown): string;
|
|
72
96
|
/**
|
|
73
97
|
* Why a form-field type is refused: a product alias the SDK does not accept in
|
|
74
|
-
* source (`"System.Int32"` is the platform's .NET name for `integer`), a
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* `unknownWhy` is the caller's reason for the last case; the default is a human
|
|
78
|
-
* task's.
|
|
98
|
+
* source (`"System.Int32"` is the platform's .NET name for `integer`), or a type
|
|
99
|
+
* the product does not recognize at all. `unknownWhy` is the caller's reason for
|
|
100
|
+
* the second case; the default is a human task's.
|
|
79
101
|
*/
|
|
80
102
|
export declare function formFieldTypeWhy(type: unknown, unknownWhy?: string): string;
|
|
81
103
|
/** The `suggestion` for an unknown form-field type: its canonical type, the nearest valid one, else `type: 'string'`. */
|
|
@@ -87,8 +109,52 @@ export declare function formFieldTypeHint(type: unknown): string;
|
|
|
87
109
|
* @example
|
|
88
110
|
* formFieldJsonSchema('date') // { type: 'string', format: 'date' }
|
|
89
111
|
* formFieldJsonSchema('text') // { type: 'string' }
|
|
112
|
+
* formFieldJsonSchema('multiSelect') // { type: 'array' }
|
|
90
113
|
*/
|
|
91
114
|
export declare function formFieldJsonSchema(type: unknown): {
|
|
92
115
|
type: string;
|
|
93
116
|
format?: string;
|
|
94
117
|
} | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* A picker field's choices as the form row carries them (`options`, each
|
|
120
|
+
* `{ id, value, label }` — the product's `optionShape`), copied so the wire
|
|
121
|
+
* never aliases the author's array. `undefined` when the field has none.
|
|
122
|
+
* Both serializers (human task, quick-form escalation) write through this.
|
|
123
|
+
*/
|
|
124
|
+
export declare function formFieldOptionsWire(options: readonly FormFieldOption[] | undefined): FormFieldOption[] | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* A form row's `options` read back for source: the well-formed choices
|
|
127
|
+
* (string `id`, `value`, `label` — all three the product requires), or
|
|
128
|
+
* `undefined` when the row has no `options` array. A malformed entry is left
|
|
129
|
+
* out, not coerced: `{ id: 'o1', value: 1 }` is not rewritten to
|
|
130
|
+
* `value: '1', label: ''`. Decompile (human task and quick-form escalation)
|
|
131
|
+
* reads through this, so a canvas dropdown's choices survive an edit.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* liftFormFieldOptions([{ id: 'o1', value: 'a', label: 'A' }, { id: 'o2', value: 2 }])
|
|
135
|
+
* // [{ id: 'o1', value: 'a', label: 'A' }]
|
|
136
|
+
*/
|
|
137
|
+
export declare function liftFormFieldOptions(raw: unknown): FormFieldOption[] | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* What is wrong with a form field's `options`, or `undefined` when nothing is.
|
|
140
|
+
* `check` reports each (human task and quick-form escalation):
|
|
141
|
+
*
|
|
142
|
+
* - `'malformed'`: `options` is there but is not an array of `{ id, value,
|
|
143
|
+
* label }` strings. Serialize would throw or write rows the platform refuses.
|
|
144
|
+
* - `'missing'`: a choice-control type (`singleSelect`, `multiSelect`, `enum`)
|
|
145
|
+
* with no choices. The form publishes an empty picker.
|
|
146
|
+
* - `'ignored'`: choices on a type that is not a choice control. The form
|
|
147
|
+
* renders a plain input and never shows them.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* formFieldOptionsProblem('singleSelect', undefined) // 'missing'
|
|
151
|
+
* formFieldOptionsProblem('singleSelect', {}) // 'malformed'
|
|
152
|
+
* formFieldOptionsProblem('string', [{ id: 'o1', value: 'a', label: 'A' }]) // 'ignored'
|
|
153
|
+
* formFieldOptionsProblem('enum', [{ id: 'o1', value: 'a', label: 'A' }]) // undefined
|
|
154
|
+
*/
|
|
155
|
+
export declare function formFieldOptionsProblem(type: unknown, options: unknown): 'malformed' | 'missing' | 'ignored' | undefined;
|
|
156
|
+
/**
|
|
157
|
+
* The reason text for a `formFieldOptionsProblem`, worded for a diagnostic
|
|
158
|
+
* that starts "… the field "<id>" …".
|
|
159
|
+
*/
|
|
160
|
+
export declare function formFieldOptionsWhy(problem: 'malformed' | 'missing' | 'ignored', type: unknown): string;
|
|
@@ -32,17 +32,53 @@ const SDK_ALIASES = {
|
|
|
32
32
|
};
|
|
33
33
|
/** The SDK's legacy spellings (`text`, `date-time`), for diagnostics that list them. */
|
|
34
34
|
export const LEGACY_FORM_FIELD_TYPES = Object.keys(SDK_ALIASES);
|
|
35
|
-
/**
|
|
36
|
-
|
|
35
|
+
/**
|
|
36
|
+
* The product's choice-control spellings (#1235): the data type each holds
|
|
37
|
+
* (`resolveFieldType`) and the control the form renders it with
|
|
38
|
+
* (`controlForDeclaredType`, which sets the field's `controlType` when the row
|
|
39
|
+
* has none). The output schema is the data type's; the choices ride on the
|
|
40
|
+
* field's `options`, never in the schema.
|
|
41
|
+
*
|
|
42
|
+
* Kept as written on the wire — NOT resolved to the data type, which would
|
|
43
|
+
* drop the control: a `singleSelect` written as `string` renders as a text box.
|
|
44
|
+
*/
|
|
45
|
+
const CONTROL_TYPES = {
|
|
46
|
+
singleSelect: { data: 'string', control: 'dropdown' },
|
|
47
|
+
multiSelect: { data: 'array', control: 'multiSelect' },
|
|
48
|
+
enum: { data: 'string', control: 'dropdown' },
|
|
49
|
+
};
|
|
50
|
+
/** The choice-control spellings, for `check`'s messages and the contract test. */
|
|
51
|
+
export const CONTROL_FORM_FIELD_TYPES = Object.keys(CONTROL_TYPES);
|
|
52
|
+
/**
|
|
53
|
+
* Every form-field type the SDK accepts in source: the 11 canonical ones, the
|
|
54
|
+
* SDK's legacy aliases, and the three choice-control spellings.
|
|
55
|
+
*/
|
|
56
|
+
export const FORM_FIELD_TYPES = [
|
|
57
|
+
...CANONICAL_FORM_FIELD_TYPES, ...LEGACY_FORM_FIELD_TYPES, ...CONTROL_FORM_FIELD_TYPES,
|
|
58
|
+
];
|
|
37
59
|
/**
|
|
38
60
|
* The tail of a "Valid: …" diagnostic: the canonical types, then the legacy
|
|
39
|
-
* spellings in parentheses.
|
|
61
|
+
* spellings and the choice controls in parentheses.
|
|
40
62
|
*
|
|
41
63
|
* @example
|
|
42
|
-
* validFormFieldTypesText() // 'string,
|
|
64
|
+
* validFormFieldTypesText() // 'string, …, array (or the older text, date-time; or a choice control: singleSelect, …)'
|
|
43
65
|
*/
|
|
44
66
|
export function validFormFieldTypesText() {
|
|
45
|
-
return `${CANONICAL_FORM_FIELD_TYPES.join(', ')} (or the older ${LEGACY_FORM_FIELD_TYPES.join(', ')}
|
|
67
|
+
return `${CANONICAL_FORM_FIELD_TYPES.join(', ')} (or the older ${LEGACY_FORM_FIELD_TYPES.join(', ')}; `
|
|
68
|
+
+ `or a choice control: ${CONTROL_FORM_FIELD_TYPES.join(', ')})`;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The data type and control a choice-control spelling names, or `undefined`
|
|
72
|
+
* for any other type.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* controlFormFieldType('singleSelect') // { data: 'string', control: 'dropdown' }
|
|
76
|
+
* controlFormFieldType('multiSelect') // { data: 'array', control: 'multiSelect' }
|
|
77
|
+
* controlFormFieldType('string') // undefined
|
|
78
|
+
*/
|
|
79
|
+
export function controlFormFieldType(type) {
|
|
80
|
+
const entry = typeof type === 'string' ? ownEntry(CONTROL_TYPES, type) : undefined;
|
|
81
|
+
return entry === undefined ? undefined : { ...entry };
|
|
46
82
|
}
|
|
47
83
|
/**
|
|
48
84
|
* The product's other data-type spellings, each with the canonical type it
|
|
@@ -53,10 +89,9 @@ export function validFormFieldTypesText() {
|
|
|
53
89
|
* resolve to `string`. `T[]` and `a | b` are matched by pattern in
|
|
54
90
|
* `canonicalFormFieldType`.
|
|
55
91
|
*
|
|
56
|
-
* Deliberately left out: the
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* renders. Decompile keeps them as written, and `check` reports them.
|
|
92
|
+
* Deliberately left out: the choice-control spellings (`singleSelect`,
|
|
93
|
+
* `multiSelect`, `enum` — `CONTROL_TYPES` above, kept as written because the
|
|
94
|
+
* product infers a `controlType` from them).
|
|
60
95
|
*/
|
|
61
96
|
const PRODUCT_ALIASES = {
|
|
62
97
|
...SDK_ALIASES,
|
|
@@ -104,18 +139,15 @@ const PRODUCT_ALIASES = {
|
|
|
104
139
|
ref: 'string',
|
|
105
140
|
any: 'string',
|
|
106
141
|
};
|
|
107
|
-
/**
|
|
108
|
-
* The product's control spellings: it accepts them as a field `type` and infers
|
|
109
|
-
* a picker control from them (`CONTROL_FOR_DECLARED_TYPE`). The SDK does not
|
|
110
|
-
* model them yet.
|
|
111
|
-
*/
|
|
112
|
-
const CONTROL_FORM_FIELD_TYPES = ['singleSelect', 'multiSelect', 'enum'];
|
|
113
142
|
/** The product aliases, for the contract test. */
|
|
114
143
|
export const FORM_FIELD_TYPE_ALIASES = PRODUCT_ALIASES;
|
|
115
144
|
const isCanonical = (t) => typeof t === 'string' && Object.hasOwn(CANONICAL_JSON_SCHEMA, t);
|
|
116
145
|
/**
|
|
117
146
|
* The canonical type a wire or source spelling names, or `undefined` when it
|
|
118
|
-
* names none the SDK models.
|
|
147
|
+
* names none the SDK models. A choice-control spelling is its own wire
|
|
148
|
+
* spelling, so it is `undefined` here too — its data type is
|
|
149
|
+
* `controlFormFieldType(t).data`; resolving it here would let a caller write
|
|
150
|
+
* `string` for a dropdown.
|
|
119
151
|
*
|
|
120
152
|
* @example
|
|
121
153
|
* canonicalFormFieldType('integer') // 'integer'
|
|
@@ -159,20 +191,15 @@ export function sourceFormFieldType(wire) {
|
|
|
159
191
|
}
|
|
160
192
|
/**
|
|
161
193
|
* Why a form-field type is refused: a product alias the SDK does not accept in
|
|
162
|
-
* source (`"System.Int32"` is the platform's .NET name for `integer`), a
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* `unknownWhy` is the caller's reason for the last case; the default is a human
|
|
166
|
-
* task's.
|
|
194
|
+
* source (`"System.Int32"` is the platform's .NET name for `integer`), or a type
|
|
195
|
+
* the product does not recognize at all. `unknownWhy` is the caller's reason for
|
|
196
|
+
* the second case; the default is a human task's.
|
|
167
197
|
*/
|
|
168
198
|
export function formFieldTypeWhy(type, unknownWhy = `which is not a quick-form field type: the platform rejects the form`) {
|
|
169
199
|
const canonical = canonicalFormFieldType(type);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return `which the platform accepts as a picker control but the SDK does not model yet`;
|
|
174
|
-
}
|
|
175
|
-
return unknownWhy;
|
|
200
|
+
return canonical !== undefined
|
|
201
|
+
? `which is the platform's alias of '${canonical}' — the SDK writes '${canonical}'`
|
|
202
|
+
: unknownWhy;
|
|
176
203
|
}
|
|
177
204
|
/** The `suggestion` for an unknown form-field type: its canonical type, the nearest valid one, else `type: 'string'`. */
|
|
178
205
|
export function formFieldTypeHint(type) {
|
|
@@ -189,9 +216,86 @@ export function formFieldTypeHint(type) {
|
|
|
189
216
|
* @example
|
|
190
217
|
* formFieldJsonSchema('date') // { type: 'string', format: 'date' }
|
|
191
218
|
* formFieldJsonSchema('text') // { type: 'string' }
|
|
219
|
+
* formFieldJsonSchema('multiSelect') // { type: 'array' }
|
|
192
220
|
*/
|
|
193
221
|
export function formFieldJsonSchema(type) {
|
|
194
|
-
const canonical = canonicalFormFieldType(type);
|
|
222
|
+
const canonical = canonicalFormFieldType(type) ?? controlFormFieldType(type)?.data;
|
|
195
223
|
const schema = canonical === undefined ? undefined : ownEntry(CANONICAL_JSON_SCHEMA, canonical);
|
|
196
224
|
return schema === undefined ? undefined : { ...schema };
|
|
197
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* A picker field's choices as the form row carries them (`options`, each
|
|
228
|
+
* `{ id, value, label }` — the product's `optionShape`), copied so the wire
|
|
229
|
+
* never aliases the author's array. `undefined` when the field has none.
|
|
230
|
+
* Both serializers (human task, quick-form escalation) write through this.
|
|
231
|
+
*/
|
|
232
|
+
export function formFieldOptionsWire(options) {
|
|
233
|
+
return options === undefined ? undefined : options.map((o) => ({ id: o.id, value: o.value, label: o.label }));
|
|
234
|
+
}
|
|
235
|
+
/** Whether a value is one well-formed choice: string `id`, `value` and `label`, all three the product requires. */
|
|
236
|
+
function isFormFieldOption(o) {
|
|
237
|
+
if (o === null || typeof o !== 'object')
|
|
238
|
+
return false;
|
|
239
|
+
const r = o;
|
|
240
|
+
return typeof r.id === 'string' && typeof r.value === 'string' && typeof r.label === 'string';
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* A form row's `options` read back for source: the well-formed choices
|
|
244
|
+
* (string `id`, `value`, `label` — all three the product requires), or
|
|
245
|
+
* `undefined` when the row has no `options` array. A malformed entry is left
|
|
246
|
+
* out, not coerced: `{ id: 'o1', value: 1 }` is not rewritten to
|
|
247
|
+
* `value: '1', label: ''`. Decompile (human task and quick-form escalation)
|
|
248
|
+
* reads through this, so a canvas dropdown's choices survive an edit.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* liftFormFieldOptions([{ id: 'o1', value: 'a', label: 'A' }, { id: 'o2', value: 2 }])
|
|
252
|
+
* // [{ id: 'o1', value: 'a', label: 'A' }]
|
|
253
|
+
*/
|
|
254
|
+
export function liftFormFieldOptions(raw) {
|
|
255
|
+
if (!Array.isArray(raw))
|
|
256
|
+
return undefined;
|
|
257
|
+
return raw.filter(isFormFieldOption).map((o) => ({ id: o.id, value: o.value, label: o.label }));
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* What is wrong with a form field's `options`, or `undefined` when nothing is.
|
|
261
|
+
* `check` reports each (human task and quick-form escalation):
|
|
262
|
+
*
|
|
263
|
+
* - `'malformed'`: `options` is there but is not an array of `{ id, value,
|
|
264
|
+
* label }` strings. Serialize would throw or write rows the platform refuses.
|
|
265
|
+
* - `'missing'`: a choice-control type (`singleSelect`, `multiSelect`, `enum`)
|
|
266
|
+
* with no choices. The form publishes an empty picker.
|
|
267
|
+
* - `'ignored'`: choices on a type that is not a choice control. The form
|
|
268
|
+
* renders a plain input and never shows them.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* formFieldOptionsProblem('singleSelect', undefined) // 'missing'
|
|
272
|
+
* formFieldOptionsProblem('singleSelect', {}) // 'malformed'
|
|
273
|
+
* formFieldOptionsProblem('string', [{ id: 'o1', value: 'a', label: 'A' }]) // 'ignored'
|
|
274
|
+
* formFieldOptionsProblem('enum', [{ id: 'o1', value: 'a', label: 'A' }]) // undefined
|
|
275
|
+
*/
|
|
276
|
+
export function formFieldOptionsProblem(type, options) {
|
|
277
|
+
if (options !== undefined && (!Array.isArray(options) || !options.every(isFormFieldOption)))
|
|
278
|
+
return 'malformed';
|
|
279
|
+
const isControl = controlFormFieldType(type) !== undefined;
|
|
280
|
+
if (isControl && (options === undefined || options.length === 0))
|
|
281
|
+
return 'missing';
|
|
282
|
+
if (!isControl && options !== undefined && options.length > 0)
|
|
283
|
+
return 'ignored';
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The reason text for a `formFieldOptionsProblem`, worded for a diagnostic
|
|
288
|
+
* that starts "… the field "<id>" …".
|
|
289
|
+
*/
|
|
290
|
+
export function formFieldOptionsWhy(problem, type) {
|
|
291
|
+
switch (problem) {
|
|
292
|
+
case 'malformed':
|
|
293
|
+
return `has \`options\` that are not a list of { id, value, label } strings. Serialize cannot write them, `
|
|
294
|
+
+ `and the platform requires all three on every choice`;
|
|
295
|
+
case 'missing':
|
|
296
|
+
return `is a ${JSON.stringify(type)} picker with no \`options\`, so the form publishes an empty picker`;
|
|
297
|
+
case 'ignored':
|
|
298
|
+
return `has \`options\` but its type ${JSON.stringify(type)} is not a choice control, so the form renders a `
|
|
299
|
+
+ `plain input and never shows them. Use 'singleSelect' or 'enum' (a dropdown) or 'multiSelect'`;
|
|
300
|
+
}
|
|
301
|
+
}
|
package/dist/decompile.js
CHANGED
|
@@ -50,7 +50,7 @@ import { OUTCOME_HANDLE_NODE_TYPES } from './core/hitl-routing.js';
|
|
|
50
50
|
import { contributionPort, forwardFanOut } from './core/definition-handles.js';
|
|
51
51
|
import { STICKY_NOTE_TYPE } from './core/definitionless-node-types.js';
|
|
52
52
|
import { eventFilterFromTreeLeaf } from './core/event-filters.js';
|
|
53
|
-
import { sourceFormFieldType } from './core/form-field-type.js';
|
|
53
|
+
import { liftFormFieldOptions, sourceFormFieldType } from './core/form-field-type.js';
|
|
54
54
|
/**
|
|
55
55
|
* The trigger id of the flow currently being decompiled, so a reference through a
|
|
56
56
|
* RENAMED trigger is still recognized as `input('x')` rather than falling back to
|
|
@@ -1306,6 +1306,12 @@ function emitAgentCluster(node, imp, inputNames, graph) {
|
|
|
1306
1306
|
['id', tsText(f.id)],
|
|
1307
1307
|
['type', tsLiteral(sourceFormFieldType(f.type ?? 'string'))],
|
|
1308
1308
|
['direction', tsLiteral(String(f.direction ?? 'output'))],
|
|
1309
|
+
['options', (() => {
|
|
1310
|
+
const options = liftFormFieldOptions(f.options);
|
|
1311
|
+
return options === undefined ? undefined : `[${options.map((o) => opts([
|
|
1312
|
+
['id', tsLiteral(o.id)], ['value', tsLiteral(o.value)], ['label', tsLiteral(o.label)],
|
|
1313
|
+
])).join(', ')}]`;
|
|
1314
|
+
})()],
|
|
1309
1315
|
])).join(', ')}]` : undefined],
|
|
1310
1316
|
['app', app === undefined ? undefined : opts([
|
|
1311
1317
|
['key', tsText(app.key)],
|
|
@@ -2276,7 +2282,7 @@ function armCb(param, segs) {
|
|
|
2276
2282
|
function returnSeg(node, ctx) {
|
|
2277
2283
|
const updates = ctx.flow.variables?.variableUpdates?.[node.id] ?? [];
|
|
2278
2284
|
const outputIds = new Set(readFlowGlobals(ctx.flow.variables).filter((g) => g.direction === 'out').map((g) => g.id));
|
|
2279
|
-
const opts = returnOptsArg(node);
|
|
2285
|
+
const opts = returnOptsArg(node, ctx.refTargets?.has(node.id) ?? false);
|
|
2280
2286
|
const entries = updates.map((u) => `${key(u.variableId)}: ${emitVal(u.expression, ctx.imp, ctx.inputNames)}`);
|
|
2281
2287
|
const written = new Set(updates.map((u) => u.variableId));
|
|
2282
2288
|
for (const [name, out] of Object.entries(node.outputs ?? {})) {
|
|
@@ -2317,9 +2323,10 @@ function isIdentityEndSource(raw, name, isOutput = false) {
|
|
|
2317
2323
|
* it — compile then refused its own decompiled source with `layout: "endNiceDay"
|
|
2318
2324
|
* names no step or node in this flow`.
|
|
2319
2325
|
*/
|
|
2320
|
-
function returnOptsArg(node) {
|
|
2326
|
+
function returnOptsArg(node, referenced = false) {
|
|
2321
2327
|
const parts = [];
|
|
2322
|
-
|
|
2328
|
+
// A `.stepToRef` into this End needs it named, `end` included (#1223).
|
|
2329
|
+
if (node.id !== 'end' || referenced)
|
|
2323
2330
|
parts.push(`name: ${tsLiteral(node.id)}`);
|
|
2324
2331
|
const ver = verClause(node);
|
|
2325
2332
|
if (ver)
|
|
@@ -2365,7 +2372,12 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
|
|
|
2365
2372
|
const segs = [];
|
|
2366
2373
|
while (cursor !== undefined && cursor !== stop && cursor !== EXIT) {
|
|
2367
2374
|
// A ref forward: this path re-enters a step the main path reaches later.
|
|
2375
|
+
// That includes the main path's End: one End with several incoming edges is
|
|
2376
|
+
// a valid flow (the canvas draws it, `edge add` writes it), and a named End is
|
|
2377
|
+
// a ref target, so the side path joins it instead of minting a second End
|
|
2378
|
+
// (#1223). The main path's `.return(...)` then carries the End's `name`.
|
|
2368
2379
|
if (refInto?.has(cursor)) {
|
|
2380
|
+
(ctx.refTargets ??= new Set()).add(cursor);
|
|
2369
2381
|
segs.push(`.stepToRef(${tsLiteral(cursor)})`);
|
|
2370
2382
|
return segs;
|
|
2371
2383
|
}
|
|
@@ -2375,8 +2387,9 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
|
|
|
2375
2387
|
// A ref backward or sideways: this path has arrived somewhere already emitted.
|
|
2376
2388
|
// Before, the walk simply stopped here — which silently dropped the rest of a
|
|
2377
2389
|
// cycle, and a backward edge is now something the builder can author. Ends and
|
|
2378
|
-
// terminates are excluded:
|
|
2379
|
-
//
|
|
2390
|
+
// terminates are excluded: each path emitting its own is a valid flow. (A
|
|
2391
|
+
// side path into the MAIN path's End is the lookahead above, #1223; an End
|
|
2392
|
+
// emitted earlier may be unnamed, so it is not a ref target here.)
|
|
2380
2393
|
if (ctx.emitted.has(cursor) && node.type !== T.end && node.type !== T.terminate) {
|
|
2381
2394
|
segs.push(`.stepToRef(${tsLiteral(cursor)})`);
|
|
2382
2395
|
return segs;
|
package/dist/flow-sdk.d.ts
CHANGED
|
@@ -467,6 +467,10 @@ export interface ReturnOptions extends NodeOptions {
|
|
|
467
467
|
* same values, label and version are one End reached from both paths; Ends that
|
|
468
468
|
* differ are de-duplicated to `done` and `done2`.
|
|
469
469
|
*
|
|
470
|
+
* A named End is also a {@link StepList.stepToRef} target: another path can
|
|
471
|
+
* end at this End rather than at one of its own. `check` refuses the ref
|
|
472
|
+
* (REF_AMBIGUOUS_TARGET) when the name is shared by another End or a step.
|
|
473
|
+
*
|
|
470
474
|
* ```ts
|
|
471
475
|
* .branch('checkTemperature', js`${input('tempF')} > 60`,
|
|
472
476
|
* (t) => t.return({}, { name: 'endNiceDay', label: 'Nice Day' }),
|
|
@@ -1196,7 +1200,19 @@ declare class StepList {
|
|
|
1196
1200
|
* can prove it refuses; what depends on the runtime it warns about and leaves to
|
|
1197
1201
|
* `uip maestro flow validate`.
|
|
1198
1202
|
*
|
|
1199
|
-
*
|
|
1203
|
+
* The target may also be a NAMED End, `.return({…}, { name })`: the path then
|
|
1204
|
+
* ends at that End node instead of one of its own, so one End has several
|
|
1205
|
+
* incoming edges, as the canvas draws it.
|
|
1206
|
+
*
|
|
1207
|
+
* ```ts
|
|
1208
|
+
* .step('fetch', http({ …, branches: [{ name: 'missing', condition: … }] }))
|
|
1209
|
+
* .stepToList('branch-missing', (b) => b.step('log', script({ … })).stepToRef('done'))
|
|
1210
|
+
* .step('use', script({ … }))
|
|
1211
|
+
* .return({}, { name: 'done' }) // both paths end at this one End
|
|
1212
|
+
* ```
|
|
1213
|
+
*
|
|
1214
|
+
* @param target - The step to continue at, named as it was in `.step(...)`, or
|
|
1215
|
+
* the `name` of a `.return(...)` End.
|
|
1200
1216
|
* @returns This builder. Nothing may follow: the path has been handed off.
|
|
1201
1217
|
* @see stepToList
|
|
1202
1218
|
*/
|
|
@@ -1216,7 +1232,8 @@ declare class StepList {
|
|
|
1216
1232
|
* ```
|
|
1217
1233
|
*
|
|
1218
1234
|
* @param port - The port of this step to leave, e.g. `'error'`.
|
|
1219
|
-
* @param target - The step to continue at, named as it was in `.step(...)
|
|
1235
|
+
* @param target - The step to continue at, named as it was in `.step(...)`, or
|
|
1236
|
+
* the `name` of a `.return(...)` End.
|
|
1220
1237
|
* @returns This builder, so calls chain — a side exit ends nothing.
|
|
1221
1238
|
* @see stepToList
|
|
1222
1239
|
*/
|
package/dist/flow-sdk.js
CHANGED
|
@@ -840,7 +840,7 @@ class StepList {
|
|
|
840
840
|
const target = maybeTarget === undefined ? portOrTarget : maybeTarget;
|
|
841
841
|
if (typeof target !== 'string' || target.length === 0) {
|
|
842
842
|
throw new TypeError(`.stepToRef() takes the NAME of the step to continue at — .stepToRef('priceIt') — and got ${JSON.stringify(target)}. `
|
|
843
|
-
+ `It is the same name you passed to .step(), so the edge can be wired after the whole flow is built.`);
|
|
843
|
+
+ `It is the same name you passed to .step() (or a .return() End's \`name\`), so the edge can be wired after the whole flow is built.`);
|
|
844
844
|
}
|
|
845
845
|
// A non-default port leaves the PRECEDING step, so it is guarded the same way
|
|
846
846
|
// `.stepToList()` is; the default port continues the list and needs no source.
|