@uipath/maestro-builder-sdk 6.15.5 → 6.16.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 +109 -108
- package/dist/api-members.md +416 -409
- package/dist/bpmn/bpmn-sdk.d.ts +170 -18
- package/dist/bpmn/bpmn-sdk.js +123 -5
- package/dist/bpmn/check.js +79 -6
- package/dist/bpmn/connector.js +5 -2
- package/dist/bpmn/data-service.d.ts +10 -2
- package/dist/bpmn/data-service.js +10 -1
- package/dist/bpmn/decompile.js +13 -2
- package/dist/bpmn/intsvc.d.ts +8 -0
- package/dist/bpmn/intsvc.js +43 -2
- package/dist/bpmn/serialize.d.ts +14 -0
- package/dist/bpmn/serialize.js +38 -9
- package/dist/bpmn/typed-node.js +76 -4
- package/dist/bpmn/uipath-moddle.v1.json +20 -0
- package/dist/check.js +29 -3
- package/dist/core/connector-inputs.d.ts +20 -0
- package/dist/core/connector-inputs.js +51 -0
- package/dist/core/event-checks.d.ts +8 -6
- package/dist/core/event-checks.js +8 -6
- package/dist/decompile.js +64 -8
- package/dist/flow-expr-check.js +6 -0
- package/dist/flow-sdk.d.ts +52 -0
- package/dist/flow-sdk.js +29 -0
- package/dist/flow-variables.d.ts +74 -0
- package/dist/flow-variables.js +101 -0
- package/dist/serialize.js +50 -4
- package/package.json +2 -2
package/dist/bpmn/decompile.js
CHANGED
|
@@ -56,6 +56,7 @@ import BpmnModdle from 'bpmn-moddle';
|
|
|
56
56
|
import { createRequire } from 'node:module';
|
|
57
57
|
import { registryType, rowDeclaration } from './typed-node.js';
|
|
58
58
|
import { errorSchema, errorSchemaWithResponse } from './bpmn-sdk.js';
|
|
59
|
+
import { SCRIPT_ARGS_ENVELOPE, defaultScriptInputSchema } from './serialize.js';
|
|
59
60
|
import { BPMN_SCHEMA_VERSIONS } from './format-profile.js';
|
|
60
61
|
import { preserveSchemaVersion } from '../schema-version.js';
|
|
61
62
|
const require = createRequire(import.meta.url);
|
|
@@ -654,7 +655,17 @@ function scriptVersionOf(el) {
|
|
|
654
655
|
/** A `uipath:mapping` script task. */
|
|
655
656
|
function emitScriptTask(chain, el, payload, type) {
|
|
656
657
|
const args = (payload?.input ?? []).find((r) => r.name === 'args');
|
|
657
|
-
|
|
658
|
+
// The serializer writes the product's envelope — `vars`/`metadata`, and the default
|
|
659
|
+
// inputSchema declaring them — on every script task, so the source need not repeat
|
|
660
|
+
// them: an `args` that is exactly the envelope decompiles to no `inputs` at all, and
|
|
661
|
+
// only what the designer added (`"iterator[0]":"=iterator[0]"`) comes through. A
|
|
662
|
+
// schema that differs from the default (the designer's carries `$schema`) is kept.
|
|
663
|
+
const rawInputs = args ? safeJson(args.body) : undefined;
|
|
664
|
+
const inputs = rawInputs
|
|
665
|
+
? Object.fromEntries(Object.entries(rawInputs).filter(([k, v]) => !(k in SCRIPT_ARGS_ENVELOPE) || v !== SCRIPT_ARGS_ENVELOPE[k]))
|
|
666
|
+
: undefined;
|
|
667
|
+
const schemaBody = payload?.context?.inputSchema?.body;
|
|
668
|
+
const schemaIsDefault = schemaBody !== undefined && rawInputs !== undefined && JSON.stringify(safeJson(schemaBody)) === JSON.stringify(defaultScriptInputSchema(rawInputs));
|
|
658
669
|
const outputs = {};
|
|
659
670
|
for (const row of payload?.output ?? [])
|
|
660
671
|
outputs[row.var ?? row.name] = row.source;
|
|
@@ -680,7 +691,7 @@ function emitScriptTask(chain, el, payload, type) {
|
|
|
680
691
|
'type',
|
|
681
692
|
type !== undefined && type !== 'BPMN.Variables' && type !== 'BPMN.ScriptTask' ? str(type) : undefined,
|
|
682
693
|
],
|
|
683
|
-
['inputSchema',
|
|
694
|
+
['inputSchema', schemaBody !== undefined && !schemaIsDefault ? jsonLiteral(schemaBody) : undefined],
|
|
684
695
|
['inputs', record(inputs)],
|
|
685
696
|
['outputs', verbatim ? undefined : record(outputs)],
|
|
686
697
|
['outputRows', verbatim],
|
package/dist/bpmn/intsvc.d.ts
CHANGED
|
@@ -66,6 +66,14 @@ export declare function intsvcOutputVar(n: EventNode | ExternalNode): string | u
|
|
|
66
66
|
* @returns The moddle element.
|
|
67
67
|
*/
|
|
68
68
|
export declare function createConnectorEvent(ctx: ConnectorCtx, n: EventNode): El;
|
|
69
|
+
/**
|
|
70
|
+
* The `body` of a connector event node: the subscription's filter in the one place the
|
|
71
|
+
* platform reads it. `scope` (the `filter` option — the operation's event parameters, by
|
|
72
|
+
* equality) becomes both the `queryParams` and the leading `(k == 'v')` clauses;
|
|
73
|
+
* `filters` (payload conditions) follow, each in parentheses, all AND-joined. No scope and
|
|
74
|
+
* no filters is `{}`, which is what the designer writes for an unfiltered subscription.
|
|
75
|
+
*/
|
|
76
|
+
export declare function eventFilterBody(nodeId: string, scope: Record<string, unknown> | undefined, filters: readonly unknown[] | undefined): Record<string, unknown>;
|
|
69
77
|
/**
|
|
70
78
|
* An external agent / workflow invocation.
|
|
71
79
|
*
|
package/dist/bpmn/intsvc.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createTypedNode, typedNodeOutputVar } from './typed-node.js';
|
|
2
2
|
import { connectorBindingRef } from './connector.js';
|
|
3
|
+
import { eventFilterJmes, eventFilterProblem, readEventFilter } from '../core/event-filters.js';
|
|
3
4
|
/**
|
|
4
5
|
* Which extension type an external invocation lowers to.
|
|
5
6
|
*
|
|
@@ -96,8 +97,18 @@ export function createConnectorEvent(ctx, n) {
|
|
|
96
97
|
// and watches nothing. A curated entry carries its own and the author passes none.
|
|
97
98
|
objectName: n.object ?? op.objectName ?? '',
|
|
98
99
|
};
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
// The subscription's filter goes in the `body` row, where the platform reads it:
|
|
101
|
+
// `PO.BpmnEngine` `BpmnInputExtensions.GetEventFilters` takes `body.filters.expression`
|
|
102
|
+
// (and `queryParams` registers the scope with the connector). This emitter used to write
|
|
103
|
+
// `<uipath:input name="filter" target="filter">{…}</uipath:input>`, an input no handler
|
|
104
|
+
// reads: on flow-v2-run-12 the wait so scoped validated and matched every mail in the
|
|
105
|
+
// mailbox (outlook-waitfor-email 1.00 → 0.43), while the v1 arm and every Studio Web
|
|
106
|
+
// export write `{"filters":{"expression":"(parentFolderId == '…') && (contains(subject, '…'))"},"queryParams":{"parentFolderId":"…"}}`.
|
|
107
|
+
//
|
|
108
|
+
// The expression is the scope's equality clauses AND-joined with the authored
|
|
109
|
+
// `filters`, each lowered by the designer's own grammar (`core/event-filters.ts`) — the
|
|
110
|
+
// same lowering the Flow serializer does for `onEvent`/`waitForEvent`.
|
|
111
|
+
inputs.body = eventFilterBody(n.id, n.filter, n.filters);
|
|
101
112
|
if (n.parameters !== undefined)
|
|
102
113
|
inputs.parameters = n.parameters;
|
|
103
114
|
return createTypedNode(ctx.m, n.id, type, {
|
|
@@ -108,6 +119,36 @@ export function createConnectorEvent(ctx, n) {
|
|
|
108
119
|
skipCondition: n.skipCondition,
|
|
109
120
|
}, (varId) => ctx.varTypeOf?.(varId));
|
|
110
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The `body` of a connector event node: the subscription's filter in the one place the
|
|
124
|
+
* platform reads it. `scope` (the `filter` option — the operation's event parameters, by
|
|
125
|
+
* equality) becomes both the `queryParams` and the leading `(k == 'v')` clauses;
|
|
126
|
+
* `filters` (payload conditions) follow, each in parentheses, all AND-joined. No scope and
|
|
127
|
+
* no filters is `{}`, which is what the designer writes for an unfiltered subscription.
|
|
128
|
+
*/
|
|
129
|
+
export function eventFilterBody(nodeId, scope, filters) {
|
|
130
|
+
const scopeEntries = Object.entries(scope ?? {}).filter(([, v]) => v !== undefined && v !== null && String(v) !== '');
|
|
131
|
+
const scopeClauses = scopeEntries.map(([k, v]) => `(${k} == '${String(v).replace(/'/g, "\\'")}')`);
|
|
132
|
+
const conditions = (filters ?? []).map((leaf, i) => {
|
|
133
|
+
const { read } = readEventFilter(leaf);
|
|
134
|
+
if (!read) {
|
|
135
|
+
throw new Error(`connector event "${nodeId}": filters[${i}] must carry a field and exactly ONE operator ` +
|
|
136
|
+
`(contains, startsWith, endsWith, equals, notEquals, lessThan, lessThanOrEqual, greaterThan, ` +
|
|
137
|
+
`greaterThanOrEqual) — got ${JSON.stringify(leaf)}.`);
|
|
138
|
+
}
|
|
139
|
+
const problem = eventFilterProblem(read);
|
|
140
|
+
if (problem)
|
|
141
|
+
throw new Error(`connector event "${nodeId}": filters[${i}] on \`${read.field}\`: ${problem}`);
|
|
142
|
+
return `(${eventFilterJmes(read)})`;
|
|
143
|
+
});
|
|
144
|
+
const expression = [...scopeClauses, ...conditions].join(' && ');
|
|
145
|
+
const body = {};
|
|
146
|
+
if (expression !== '')
|
|
147
|
+
body.filters = { expression };
|
|
148
|
+
if (scopeEntries.length > 0)
|
|
149
|
+
body.queryParams = Object.fromEntries(scopeEntries);
|
|
150
|
+
return body;
|
|
151
|
+
}
|
|
111
152
|
/**
|
|
112
153
|
* An external agent / workflow invocation.
|
|
113
154
|
*
|
package/dist/bpmn/serialize.d.ts
CHANGED
|
@@ -26,6 +26,20 @@ export declare function serialize(built: BuiltBpmn, opts?: BpmnSerializeOpts): P
|
|
|
26
26
|
*/
|
|
27
27
|
export declare function finalizeBpmnXml(xml: string): string;
|
|
28
28
|
export declare function processId(id: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* The `args` keys every Studio Web script task carries, in the order the designer writes
|
|
31
|
+
* them. The platform runtime injects these — and only these — into the script's scope.
|
|
32
|
+
*/
|
|
33
|
+
export declare const SCRIPT_ARGS_ENVELOPE: {
|
|
34
|
+
readonly vars: "=vars";
|
|
35
|
+
readonly metadata: "=metadata";
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The `uipath:inputSchema` the product writes for a script task's `args`: `vars` and
|
|
39
|
+
* `metadata` as objects, plus an untyped property per extra key. Exported so the
|
|
40
|
+
* decompiler can recognise it and leave it out of the source.
|
|
41
|
+
*/
|
|
42
|
+
export declare function defaultScriptInputSchema(args: Record<string, unknown>): Record<string, unknown>;
|
|
29
43
|
/**
|
|
30
44
|
* The variable declarations a scope's output-bearing nodes imply.
|
|
31
45
|
*
|
package/dist/bpmn/serialize.js
CHANGED
|
@@ -444,21 +444,50 @@ function buildErrorMappingExt(ctx, rows) {
|
|
|
444
444
|
}),
|
|
445
445
|
});
|
|
446
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* The `args` keys every Studio Web script task carries, in the order the designer writes
|
|
449
|
+
* them. The platform runtime injects these — and only these — into the script's scope.
|
|
450
|
+
*/
|
|
451
|
+
export const SCRIPT_ARGS_ENVELOPE = { vars: '=vars', metadata: '=metadata' };
|
|
452
|
+
/**
|
|
453
|
+
* The `uipath:inputSchema` the product writes for a script task's `args`: `vars` and
|
|
454
|
+
* `metadata` as objects, plus an untyped property per extra key. Exported so the
|
|
455
|
+
* decompiler can recognise it and leave it out of the source.
|
|
456
|
+
*/
|
|
457
|
+
export function defaultScriptInputSchema(args) {
|
|
458
|
+
const properties = {};
|
|
459
|
+
for (const k of Object.keys(args))
|
|
460
|
+
properties[k] = k in SCRIPT_ARGS_ENVELOPE ? { type: 'object' } : {};
|
|
461
|
+
return { type: 'object', properties };
|
|
462
|
+
}
|
|
447
463
|
function createScriptTask(ctx, n) {
|
|
448
464
|
const { m } = ctx;
|
|
465
|
+
// The `args` row is the product's fixed envelope — `vars` and `metadata`, always, in
|
|
466
|
+
// that order — with the author's `inputs` after it. The platform runtime injects the
|
|
467
|
+
// ENVELOPE into the script's scope and nothing else: in flow-v2-run-12 every script
|
|
468
|
+
// that read an `inputs` key as a bare identifier faulted on the cloud with
|
|
469
|
+
// `<key> is not defined` (3 of 3 cloud-executed script tasks; `check`, `compile` and
|
|
470
|
+
// `uip maestro bpmn validate` had all passed), while every Studio Web export in
|
|
471
|
+
// `research/bpmn-roundtrip` and every v1-authored process in the same run carries
|
|
472
|
+
// exactly this envelope and reads `vars.<id>`. The author's keys are kept so an import
|
|
473
|
+
// round-trips (`iterator[0]` is how the designer scopes a loop body) and so a key the
|
|
474
|
+
// author knows the runtime honours can still be passed; a bare read of one is what
|
|
475
|
+
// `check` reports as SCRIPT_BARE_INPUT_REF. See docs/BPMN_EVAL_RUN12_FINDINGS.md §4 P0-1.
|
|
476
|
+
const args = { ...SCRIPT_ARGS_ENVELOPE, ...n.inputs };
|
|
477
|
+
const inputSchema = n.inputSchema ?? defaultScriptInputSchema(args);
|
|
449
478
|
const mapping = m.create('uipath:Mapping', {
|
|
450
479
|
version: 'v1',
|
|
451
480
|
type: m.create('uipath:Type', { value: n.type, version: 'v1' }),
|
|
452
|
-
// The context holds only the schema here — a script task has no context ROWS.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}),
|
|
481
|
+
// The context holds only the schema here — a script task has no context ROWS. Real
|
|
482
|
+
// script tasks all carry the schema; the product refuses to run without the envelope
|
|
483
|
+
// it declares, so an absent one is filled in rather than left out.
|
|
484
|
+
context: m.create('uipath:Context', {
|
|
485
|
+
inputSchema: m.create('uipath:InputSchema', {
|
|
486
|
+
type: 'jsonSchema',
|
|
487
|
+
body: typeof inputSchema === 'string' ? inputSchema : JSON.stringify(inputSchema),
|
|
460
488
|
}),
|
|
461
|
-
|
|
489
|
+
}),
|
|
490
|
+
input: [m.create('uipath:Input', { name: 'args', type: 'json', target: 'bodyField', body: JSON.stringify(args) })],
|
|
462
491
|
// Spelled-out rows win over derived ones: `outputs` names each row after the
|
|
463
492
|
// variable it writes, and the product names it after what the script returns
|
|
464
493
|
// while binding a differently-named variable. See ScriptTaskOpts.outputRows.
|
package/dist/bpmn/typed-node.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { resolveFromRegistry } from './registry-fallback.js';
|
|
3
|
+
import { nestConnectorBody } from '../core/connector-inputs.js';
|
|
3
4
|
import { types } from '../core/expr.js';
|
|
4
5
|
const require = createRequire(import.meta.url);
|
|
5
6
|
const REGISTRY = require('./registry-types.json');
|
|
@@ -64,7 +65,20 @@ export function isSnapshotType(type) {
|
|
|
64
65
|
* constant for the one type known to need it rather than derived.
|
|
65
66
|
*/
|
|
66
67
|
export const REGISTRY_GAPS = {
|
|
67
|
-
'
|
|
68
|
+
// `folderKey` — the job-start family's v1 addressing field. The registry declares
|
|
69
|
+
// `folderId`, but the engine's v1 branch reads `FolderKey` (`ExecuteApiWorkflowArgs`:
|
|
70
|
+
// `input.GetValueFromContext<string>(nameof(OrchestratorFolderContext.FolderKey))`), and
|
|
71
|
+
// the v1 skill verified live that `releaseKey` without it faults with `Could not get
|
|
72
|
+
// value for key:FolderKey from context in input`. Emitted only for a node addressed by
|
|
73
|
+
// `releaseKey` — see `OrchestratorOpts.folderKey`.
|
|
74
|
+
'Orchestrator.StartAgentJob': { resourceSubType: 'Agent', extraContext: ['folderKey'] },
|
|
75
|
+
'Orchestrator.StartJob': { extraContext: ['folderKey'] },
|
|
76
|
+
'Orchestrator.ExecuteApiWorkflowAsync': { extraContext: ['folderKey'] },
|
|
77
|
+
'Orchestrator.StartAgenticProcess': { extraContext: ['folderKey'] },
|
|
78
|
+
'Orchestrator.StartAgenticProcessAsync': { extraContext: ['folderKey'] },
|
|
79
|
+
'Orchestrator.StartCaseMgmtProcess': { extraContext: ['folderKey'] },
|
|
80
|
+
'Orchestrator.StartCaseMgmtProcessAsync': { extraContext: ['folderKey'] },
|
|
81
|
+
'Orchestrator.BusinessRules': { extraContext: ['folderKey'] },
|
|
68
82
|
// `notEnforcedInputs` — required InputFields the platform does NOT actually enforce.
|
|
69
83
|
//
|
|
70
84
|
// MEASURED, on a real trigger that carries `connection`, `folderKey`, `connectorKey`,
|
|
@@ -226,9 +240,19 @@ function contextRows(m, type, spec, supplied, verbatim = false) {
|
|
|
226
240
|
return rows;
|
|
227
241
|
}
|
|
228
242
|
for (const field of spec.context) {
|
|
229
|
-
|
|
243
|
+
let value = supplied[field.name] ?? field.default;
|
|
230
244
|
if (value === undefined || value === null)
|
|
231
245
|
continue;
|
|
246
|
+
// A json field whose REGISTRY default is the text `null` is unrunnable as declared:
|
|
247
|
+
// the platform parses the row to a JSON null and `IntSvcUnifiedHttpRequestArgs`
|
|
248
|
+
// throws `Body cannot be null` on it (incident 102018 — flow-v2-run-12
|
|
249
|
+
// slack-http-fallback, a GET with no body, faulted on exactly this after check,
|
|
250
|
+
// compile and validate had passed). Omitting the row is the same null. So a
|
|
251
|
+
// default of `null` on a json field is written as `{}`, the empty object the handler
|
|
252
|
+
// accepts; an author who wrote `null` themselves still gets it verbatim, and an
|
|
253
|
+
// import's `"null"` text round-trips untouched (see decompile's json-row rules).
|
|
254
|
+
if (field.type === 'json' && !(field.name in supplied) && field.default === 'null')
|
|
255
|
+
value = {};
|
|
232
256
|
emitted.add(field.name);
|
|
233
257
|
// A `json` field goes in the CDATA BODY, not a `value` attribute. The product
|
|
234
258
|
// validator parses that field as JSON and reads the attribute WITHOUT XML
|
|
@@ -266,6 +290,28 @@ function contextRows(m, type, spec, supplied, verbatim = false) {
|
|
|
266
290
|
}
|
|
267
291
|
return rows;
|
|
268
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* The `target` values a payload row can carry — `BpmnInputTarget` in PO.BpmnEngine
|
|
295
|
+
* (`InputResolutionActivities` routes `Body`/`BodyField`/`Header`/`PathVariable`/`Query`/
|
|
296
|
+
* `PathParameters`/`QueryParameters`/`File`), plus the connector emitter's `multipart`.
|
|
297
|
+
* An undeclared `separateInputs` key that is one of these addresses a slot; any other
|
|
298
|
+
* key is a field — see `inputRows`.
|
|
299
|
+
*/
|
|
300
|
+
const PAYLOAD_TARGETS = new Set([
|
|
301
|
+
'body',
|
|
302
|
+
'bodyField',
|
|
303
|
+
'header',
|
|
304
|
+
'headers',
|
|
305
|
+
'pathVariable',
|
|
306
|
+
'pathParameters',
|
|
307
|
+
'query',
|
|
308
|
+
'queryParameters',
|
|
309
|
+
'file',
|
|
310
|
+
'multipart',
|
|
311
|
+
]);
|
|
312
|
+
function isPlainRecord(value) {
|
|
313
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
314
|
+
}
|
|
269
315
|
/**
|
|
270
316
|
* The payload rows, per the type's `inputPattern`.
|
|
271
317
|
*
|
|
@@ -308,11 +354,37 @@ function inputRows(m, spec, inputs) {
|
|
|
308
354
|
// `recordId` as `"1111-…"`, quotes included, where every real export writes
|
|
309
355
|
// `type="string" value="1111-…"`. A structured value is still a json body; a
|
|
310
356
|
// scalar is written as the `value` attribute, the way a declared scalar is.
|
|
357
|
+
//
|
|
358
|
+
// An undeclared key is a TARGET, then — `body`, `pathParameters`, `queryParameters`
|
|
359
|
+
// — and only a target. A key that is none of the platform's targets is a request
|
|
360
|
+
// FIELD the author wrote where a slot belongs (`inputs: { script: '…' }` on
|
|
361
|
+
// `Intsvc.ActivityExecution`), and keyed as its own target it reaches a row the
|
|
362
|
+
// platform never reads: on flow-v2-run-12 the JDBC `execute-query-synchronously` node
|
|
363
|
+
// so written went out as `<uipath:input name="script" … target="script"/>` and every
|
|
364
|
+
// one of seven cloud runs faulted `Integration Services bad request`, while the v1 arm's
|
|
365
|
+
// `<uipath:input name="body" target="body">{"script":"…"}</uipath:input>` completed
|
|
366
|
+
// first time. Such keys fold into the type's own payload row (`inputName`/`inputTarget`,
|
|
367
|
+
// `body` for the Integration Service family), nested by their dotted ids, exactly as
|
|
368
|
+
// `.connector()` writes a request. A type with no payload row keeps keying them.
|
|
311
369
|
const declared = new Map(spec.inputs.map((f) => [f.name, f]));
|
|
312
|
-
|
|
370
|
+
const fieldKeys = spec.inputName !== null && spec.inputTarget !== null
|
|
371
|
+
? keys.filter((k) => !declared.has(k) && !PAYLOAD_TARGETS.has(k) && k !== spec.inputName)
|
|
372
|
+
: [];
|
|
373
|
+
const folded = new Set(fieldKeys);
|
|
374
|
+
const rows = keys
|
|
375
|
+
.filter((k) => !folded.has(k))
|
|
376
|
+
.map((k) => {
|
|
313
377
|
const field = declared.get(k);
|
|
314
|
-
|
|
378
|
+
let value = inputs[k];
|
|
379
|
+
if (k === spec.inputName && fieldKeys.length > 0 && isPlainRecord(value)) {
|
|
380
|
+
value = { ...nestConnectorBody(Object.fromEntries(fieldKeys.map((f) => [f, inputs[f]]))), ...value };
|
|
381
|
+
}
|
|
382
|
+
return payloadInput(m, k, field?.target ?? k, value, field?.type ?? inferredInputType(value));
|
|
315
383
|
});
|
|
384
|
+
if (fieldKeys.length > 0 && !keys.includes(spec.inputName)) {
|
|
385
|
+
rows.push(payloadInput(m, spec.inputName, spec.inputTarget, nestConnectorBody(Object.fromEntries(fieldKeys.map((f) => [f, inputs[f]]))), 'json'));
|
|
386
|
+
}
|
|
387
|
+
return rows;
|
|
316
388
|
}
|
|
317
389
|
if (!spec.inputName)
|
|
318
390
|
return [];
|
|
@@ -267,6 +267,16 @@
|
|
|
267
267
|
"name": "skipCondition",
|
|
268
268
|
"isAttr": true,
|
|
269
269
|
"type": "String"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"name": "argumentOverrides",
|
|
273
|
+
"isAttr": true,
|
|
274
|
+
"type": "String"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
"name": "argumentsInput",
|
|
278
|
+
"isAttr": true,
|
|
279
|
+
"type": "String"
|
|
270
280
|
}
|
|
271
281
|
]
|
|
272
282
|
},
|
|
@@ -301,6 +311,16 @@
|
|
|
301
311
|
"name": "skipCondition",
|
|
302
312
|
"isAttr": true,
|
|
303
313
|
"type": "String"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"name": "argumentOverrides",
|
|
317
|
+
"isAttr": true,
|
|
318
|
+
"type": "String"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"name": "argumentsInput",
|
|
322
|
+
"isAttr": true,
|
|
323
|
+
"type": "String"
|
|
304
324
|
}
|
|
305
325
|
]
|
|
306
326
|
},
|
package/dist/check.js
CHANGED
|
@@ -16,7 +16,7 @@ import { hitlDerivesOutcomeHandles, hitlExitModel, hitlRoutesPerOutcome } from '
|
|
|
16
16
|
import { childStepLists, declaredExits, hasExits, stopsAtEnd } from './core/step-ports.js';
|
|
17
17
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
18
18
|
import { compareSchemaVersions, schemaVersionRefusal } from './schema-version.js';
|
|
19
|
-
import { planFlowGlobals } from './flow-variables.js';
|
|
19
|
+
import { planFlowGlobals, planSharedInputs, sharedInputRoots } from './flow-variables.js';
|
|
20
20
|
export function check(built, opts = {}) {
|
|
21
21
|
let diags = [];
|
|
22
22
|
const uses = collectConnectionUses(() => { diags = checkFlow(built, [], opts); });
|
|
@@ -81,11 +81,17 @@ function checkFlow(built, ancestors, opts = {}) {
|
|
|
81
81
|
declared.add(built.triggerId);
|
|
82
82
|
for (const ep of built.entryPoints ?? [])
|
|
83
83
|
declared.add(ep.id);
|
|
84
|
+
// A variable an input feeds with `{ shared }` is declared by the SDK when the
|
|
85
|
+
// author did not declare it (#924), so it is a readable `$vars.<name>` too.
|
|
86
|
+
const sharedAutoVars = planSharedInputs({ roots: sharedInputRoots(built), outputs: built.outputs, vars: built.vars }).autoVars;
|
|
87
|
+
for (const a of sharedAutoVars)
|
|
88
|
+
declared.add(a.name);
|
|
84
89
|
// Declared type per name, for the output/return category check below.
|
|
85
90
|
const typeOf = new Map([
|
|
86
91
|
...built.inputs.map((x) => [x.name, String(x.type)]),
|
|
87
92
|
...(built.entryPoints ?? []).flatMap((ep) => ep.inputs.map((x) => [x.name, String(x.type)])),
|
|
88
93
|
...built.vars.map((x) => [x.name, String(x.type)]),
|
|
94
|
+
...sharedAutoVars.map((x) => [x.name, x.type]),
|
|
89
95
|
]);
|
|
90
96
|
const outputType = new Map(built.outputs.map((x) => [x.name, String(x.type)]));
|
|
91
97
|
const stepNames = new Set();
|
|
@@ -1411,6 +1417,14 @@ function checkSubflow(step, child, inputs, parent, ancestors, diags) {
|
|
|
1411
1417
|
at('SUBFLOW_CHILD_TRIGGER', `Subflow "${step}" runs a flow with its own \`.trigger(…)\`. A subflow is started by its caller, never by a `
|
|
1412
1418
|
+ `trigger of its own — the schedule would be dropped. Remove the .trigger() from "${child.id}".`);
|
|
1413
1419
|
}
|
|
1420
|
+
// `{ shared }` merges one input across a flow's ROOTS (#924). A subflow has
|
|
1421
|
+
// one root, its caller, so there is nothing to merge; serialize refuses it.
|
|
1422
|
+
for (const inp of child.inputs) {
|
|
1423
|
+
if (inp.shared === undefined)
|
|
1424
|
+
continue;
|
|
1425
|
+
at('SHARED_INPUT_IN_SUBFLOW', `Subflow "${step}" runs "${child.id}", whose input "${inp.name}" has { shared: "${inp.shared}" }. A subflow has `
|
|
1426
|
+
+ `one root — its caller — so there is nothing to merge. Read the input with input('${inp.name}') inside the child.`);
|
|
1427
|
+
}
|
|
1414
1428
|
const declared = new Map(child.inputs.map((i) => [i.name, String(i.type)]));
|
|
1415
1429
|
const given = inputs ?? {};
|
|
1416
1430
|
for (const key of Object.keys(given)) {
|
|
@@ -1653,7 +1667,14 @@ const INTERVAL_WORDS = [
|
|
|
1653
1667
|
* said `Errors: 0`, validate refused the file).
|
|
1654
1668
|
*/
|
|
1655
1669
|
function checkGlobalNames(built, diags) {
|
|
1656
|
-
|
|
1670
|
+
// `{ shared }` inputs (#924) may add `.var()`s; the plan is the serializer's
|
|
1671
|
+
// own (`emittableGlobals`), so check and the emitted file agree on them.
|
|
1672
|
+
const shared = planSharedInputs({ roots: sharedInputRoots(built), outputs: built.outputs ?? [], vars: built.vars ?? [] });
|
|
1673
|
+
for (const p of shared.problems) {
|
|
1674
|
+
diags.push({ level: p.level, code: p.code, message: p.message, ...(p.suggestion ? { suggestion: p.suggestion } : {}) });
|
|
1675
|
+
}
|
|
1676
|
+
const vars = [...(built.vars ?? []), ...shared.autoVars];
|
|
1677
|
+
for (const p of planFlowGlobals({ inputs: built.inputs ?? [], outputs: built.outputs ?? [], vars }).problems) {
|
|
1657
1678
|
diags.push({ level: 'error', code: p.code, message: p.message, ...(p.suggestion ? { suggestion: p.suggestion } : {}) });
|
|
1658
1679
|
}
|
|
1659
1680
|
}
|
|
@@ -1665,7 +1686,12 @@ function checkGlobalNames(built, diags) {
|
|
|
1665
1686
|
*/
|
|
1666
1687
|
function checkNodeUpdates(built, diags) {
|
|
1667
1688
|
const inputs = new Set(built.inputs.map((v) => v.name));
|
|
1668
|
-
const writable = new Set([
|
|
1689
|
+
const writable = new Set([
|
|
1690
|
+
...built.vars.map((v) => v.name),
|
|
1691
|
+
...built.outputs.map((v) => v.name),
|
|
1692
|
+
// A variable declared by `{ shared }` (#924) is a `.var()` like any other.
|
|
1693
|
+
...planSharedInputs({ roots: sharedInputRoots(built), outputs: built.outputs, vars: built.vars }).autoVars.map((v) => v.name),
|
|
1694
|
+
]);
|
|
1669
1695
|
const visit = (steps) => {
|
|
1670
1696
|
for (const s of steps) {
|
|
1671
1697
|
const updates = s.options?.updates;
|
|
@@ -78,3 +78,23 @@ interface ConnectorSlots {
|
|
|
78
78
|
};
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Fold a body keyed by the registry's dotted field ids into the nested request
|
|
83
|
+
* object those ids describe: `{"fields.project.key":"CE"}` → `{"fields":{"project":{"key":"CE"}}}`.
|
|
84
|
+
*
|
|
85
|
+
* `buildConnectorInputs` keeps the DOTTED keys on purpose — Flow's `bodyParameters` is a
|
|
86
|
+
* flat map and Flow's runtime (`convertDotNotationToNested`) nests it before the call.
|
|
87
|
+
* The Maestro BPMN runtime does not: `IntSvcSendTaskHandler` sends the `body` input as
|
|
88
|
+
* written, so a dotted key reaches the connector as a literal property name. Measured on
|
|
89
|
+
* flow-v2-run-12: the SDK's Jira `create-issue` body went out flat and Integration
|
|
90
|
+
* Service answered 400 `project: Specify a valid project ID or key`; the v1 arm, which
|
|
91
|
+
* writes the nested form by hand, created the ticket. A BPMN body therefore goes through
|
|
92
|
+
* this before it is serialised.
|
|
93
|
+
*
|
|
94
|
+
* A `[*]` segment names an array. On a leaf (`users[*]`) an array value is kept and a
|
|
95
|
+
* scalar becomes a one-element list, as the product's own conversion does; on an inner
|
|
96
|
+
* segment (`attachments[*].url`) each element of an array value becomes one object, and
|
|
97
|
+
* a scalar one. Later keys win over earlier ones at the same leaf; a nested object the
|
|
98
|
+
* author already wrote under a plain key is merged into, not replaced.
|
|
99
|
+
*/
|
|
100
|
+
export declare function nestConnectorBody(flat: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -259,6 +259,57 @@ const SLOT_FOR_LOCATION = {
|
|
|
259
259
|
query: 'queryParameters',
|
|
260
260
|
path: 'pathParameters',
|
|
261
261
|
};
|
|
262
|
+
/**
|
|
263
|
+
* Fold a body keyed by the registry's dotted field ids into the nested request
|
|
264
|
+
* object those ids describe: `{"fields.project.key":"CE"}` → `{"fields":{"project":{"key":"CE"}}}`.
|
|
265
|
+
*
|
|
266
|
+
* `buildConnectorInputs` keeps the DOTTED keys on purpose — Flow's `bodyParameters` is a
|
|
267
|
+
* flat map and Flow's runtime (`convertDotNotationToNested`) nests it before the call.
|
|
268
|
+
* The Maestro BPMN runtime does not: `IntSvcSendTaskHandler` sends the `body` input as
|
|
269
|
+
* written, so a dotted key reaches the connector as a literal property name. Measured on
|
|
270
|
+
* flow-v2-run-12: the SDK's Jira `create-issue` body went out flat and Integration
|
|
271
|
+
* Service answered 400 `project: Specify a valid project ID or key`; the v1 arm, which
|
|
272
|
+
* writes the nested form by hand, created the ticket. A BPMN body therefore goes through
|
|
273
|
+
* this before it is serialised.
|
|
274
|
+
*
|
|
275
|
+
* A `[*]` segment names an array. On a leaf (`users[*]`) an array value is kept and a
|
|
276
|
+
* scalar becomes a one-element list, as the product's own conversion does; on an inner
|
|
277
|
+
* segment (`attachments[*].url`) each element of an array value becomes one object, and
|
|
278
|
+
* a scalar one. Later keys win over earlier ones at the same leaf; a nested object the
|
|
279
|
+
* author already wrote under a plain key is merged into, not replaced.
|
|
280
|
+
*/
|
|
281
|
+
export function nestConnectorBody(flat) {
|
|
282
|
+
const out = {};
|
|
283
|
+
for (const [name, value] of Object.entries(flat)) {
|
|
284
|
+
const parts = connectorPath(name);
|
|
285
|
+
place(out, parts, 0, value);
|
|
286
|
+
}
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
function place(target, parts, index, value) {
|
|
290
|
+
const part = parts[index];
|
|
291
|
+
const last = index === parts.length - 1;
|
|
292
|
+
if (last) {
|
|
293
|
+
target[part.key] = part.array && !Array.isArray(value) ? [value] : value;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (part.array) {
|
|
297
|
+
const elements = Array.isArray(value) ? value : [value];
|
|
298
|
+
const existing = Array.isArray(target[part.key]) ? target[part.key] : [];
|
|
299
|
+
target[part.key] = elements.map((element, i) => {
|
|
300
|
+
const slot = isRecord(existing[i]) ? existing[i] : {};
|
|
301
|
+
place(slot, parts, index + 1, element);
|
|
302
|
+
return slot;
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const next = isRecord(target[part.key]) ? target[part.key] : {};
|
|
307
|
+
target[part.key] = next;
|
|
308
|
+
place(next, parts, index + 1, value);
|
|
309
|
+
}
|
|
310
|
+
function isRecord(value) {
|
|
311
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
312
|
+
}
|
|
262
313
|
function connectorPath(name) {
|
|
263
314
|
return name.split('.').map((raw) => ({
|
|
264
315
|
key: raw.endsWith('[*]') ? raw.slice(0, -3) : raw,
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
* `.waitForEvent()` node carries the same facts under its own names, and
|
|
10
10
|
* `bpmn/check.ts` adapts one to the other.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
12
|
+
* BPMN carries `filters` too, since flow-v2-run-12: both families write payload
|
|
13
|
+
* conditions into the subscription the platform reads (Flow's `filterExpression`,
|
|
14
|
+
* BPMN's `body.filters.expression`), lowered by the same grammar, so the
|
|
15
|
+
* `EVENT_FILTER_*` family fires for both. The subscription rules are the rest:
|
|
16
|
+
* whether it resolves at all, whether a generic event named its object, and whether
|
|
17
|
+
* its scope names parameters the operation actually declares.
|
|
18
|
+
* `skill-bpmn-outlook-waitfor-email` in `flow-v2-run-8` failed on exactly that last
|
|
19
|
+
* question, and in run 12 on having nowhere to put a payload condition.
|
|
18
20
|
*/
|
|
19
21
|
import { type LookupResolutions } from './lookups.js';
|
|
20
22
|
import { type ByoaEventFacts, type ByoaLedger } from './byoa.js';
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
* `.waitForEvent()` node carries the same facts under its own names, and
|
|
10
10
|
* `bpmn/check.ts` adapts one to the other.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
12
|
+
* BPMN carries `filters` too, since flow-v2-run-12: both families write payload
|
|
13
|
+
* conditions into the subscription the platform reads (Flow's `filterExpression`,
|
|
14
|
+
* BPMN's `body.filters.expression`), lowered by the same grammar, so the
|
|
15
|
+
* `EVENT_FILTER_*` family fires for both. The subscription rules are the rest:
|
|
16
|
+
* whether it resolves at all, whether a generic event named its object, and whether
|
|
17
|
+
* its scope names parameters the operation actually declares.
|
|
18
|
+
* `skill-bpmn-outlook-waitfor-email` in `flow-v2-run-8` failed on exactly that last
|
|
19
|
+
* question, and in run 12 on having nowhere to put a payload condition.
|
|
18
20
|
*/
|
|
19
21
|
import { isLookupToken, resolvedValue, unresolvedLookupMessage, } from './lookups.js';
|
|
20
22
|
import { prepareCommand } from './cli-spelling.js';
|