@uipath/maestro-builder-sdk 6.37.0 → 6.39.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.
@@ -15,7 +15,7 @@ import { typedNodeAdditionalOutputVars, typedNodeOutputVar } from './typed-node.
15
15
  * @internal
16
16
  */
17
17
  export function connectorOutputVars(n) {
18
- return { response: n.outputVar ?? `${n.id}_response`, error: `${n.id}_Error` };
18
+ return { response: n.outputVar ?? `${n.id}_response`, error: n.errorVar ?? `${n.id}_Error` };
19
19
  }
20
20
  /**
21
21
  * The variables a typed node writes — the ONE definition of that set.
@@ -54,7 +54,7 @@ export function typedNodeDeclaredVars(n) {
54
54
  export function nodeDeclaredVars(n) {
55
55
  if (n.kind === 'connector') {
56
56
  const { response, error } = connectorOutputVars(n);
57
- return [response, error];
57
+ return [response, error, ...Object.keys(n.outputs ?? {})];
58
58
  }
59
59
  if (n.kind === 'typed')
60
60
  return typedNodeDeclaredVars(n);
@@ -4,6 +4,15 @@
4
4
  *
5
5
  * Usage:
6
6
  * node --experimental-strip-types decompile-cli.js <Name.bpmn> [-o Name.bpmn.ts] [--import <specifier>] [--style explicit|nested] [--keep-flow-ids] [--keep-unused-bindings]
7
+ * [--library <library-json dir>] [--connectors-local <overlay dir>] [--connectors lift|verbatim]
8
+ *
9
+ * `--connectors lift` (the default when a library opens — `--library`, else the
10
+ * cache `registry pull` filled) writes a connector task the library can name as
11
+ * `.connector(key, action, inputs, { connection, folder, … })`, and verifies each
12
+ * lift by recompiling: a task that does not come back the same, the fields the
13
+ * SDK regenerates aside, is written verbatim with the reason. `--connectors
14
+ * verbatim` keeps every task as the file spells it, which is also what happens
15
+ * with no library at all.
7
16
  *
8
17
  * The reverse of compile-cli: reads a `.bpmn` and emits builder source whose
9
18
  * default export re-serializes to the same XML. `--import` sets the module
@@ -29,6 +38,8 @@ import { defaultSourceOut, writeSource } from '../workdir.js';
29
38
  import { flagValue, runWhenInvokedDirectly } from '../cli-run.js';
30
39
  import { decompile } from './decompile.js';
31
40
  import { sameGraph } from './lift-verify.js';
41
+ import { verifyConnectorLifts } from './connector-lift-verify.js';
42
+ import { openCliLibrary } from '../core/cli-library.js';
32
43
  import { BPMN_IMPORT } from '../package-name.js';
33
44
  import { fileURLToPath } from 'node:url';
34
45
  /**
@@ -62,9 +73,14 @@ export async function run(argv) {
62
73
  return 2;
63
74
  }
64
75
  }
76
+ const connectors = flagValue(argv, '--connectors') ?? 'lift';
77
+ if (connectors !== 'lift' && connectors !== 'verbatim') {
78
+ console.error(`decompile: --connectors must be lift or verbatim, not "${connectors}"`);
79
+ return 2;
80
+ }
65
81
  const keepFlowIds = argv.includes('--keep-flow-ids');
66
82
  const keepUnusedBindings = argv.includes('--keep-unused-bindings');
67
- const flags = new Set(['-o', '--import', '--style']);
83
+ const flags = new Set(['-o', '--import', '--style', '--library', '--connectors-local', '--connectors']);
68
84
  const bare = new Set(['--keep-flow-ids', '--keep-unused-bindings']);
69
85
  const input = argv.find((a, i) => !bare.has(a) && !flags.has(a) && !flags.has(argv[i - 1]));
70
86
  if (!input) {
@@ -74,8 +90,34 @@ export async function run(argv) {
74
90
  const xml = readFileSync(input, 'utf8');
75
91
  const importSpecifier = flagValue(argv, '--import') ?? BPMN_IMPORT;
76
92
  let pruned = [];
77
- const base0 = { importSpecifier, keepUnusedBindings, onPrune: (ids) => (pruned = ids) };
78
- let source = await decompile(xml, base0);
93
+ // The connector lift: name each connector task through the library, then keep only
94
+ // the lifts that verify. One decompile to plan, one verify, and — only when a lift
95
+ // was refused — one more decompile with those tasks pinned verbatim. The pinned set
96
+ // then rides into the nested-style passes below, so the flat and nested sources the
97
+ // nested verify compares make the same connector decisions.
98
+ const library = connectors === 'lift' ? openCliLibrary(argv) : undefined;
99
+ const sdkEntry = fileURLToPath(new URL('./bpmn-sdk.js', import.meta.url));
100
+ let connectorSummary;
101
+ let verbatimConnectors = new Set();
102
+ const base0 = {
103
+ importSpecifier,
104
+ keepUnusedBindings,
105
+ onPrune: (ids) => (pruned = ids),
106
+ ...(library ? { library, connectors: 'lift' } : {}),
107
+ onConnectorLift: (s) => (connectorSummary = s),
108
+ };
109
+ let source = await decompile(xml, { ...base0, verbatimConnectors });
110
+ if (library && connectorSummary && connectorSummary.lifted.length > 0) {
111
+ const verdict = await verifyConnectorLifts(xml, connectorSummary, sdkEntry, library);
112
+ if (verdict.failed.size > 0) {
113
+ verbatimConnectors = new Set(verdict.failed.keys());
114
+ source = await decompile(xml, { ...base0, verbatimConnectors });
115
+ // The summary from the second pass says "did not verify"; carry the measured reason.
116
+ if (connectorSummary) {
117
+ connectorSummary.verbatim = connectorSummary.verbatim.map((v) => ({ id: v.id, reason: verdict.failed.get(v.id) ?? v.reason }));
118
+ }
119
+ }
120
+ }
79
121
  let lifted;
80
122
  let fallback;
81
123
  if (style === 'nested') {
@@ -83,10 +125,11 @@ export async function run(argv) {
83
125
  // package's own entry, whatever specifier the written file carries — the check is
84
126
  // about the graph, not about module resolution. The flow ids the lift renamed are
85
127
  // the one sanctioned difference.
86
- const sdkEntry = fileURLToPath(new URL('./bpmn-sdk.js', import.meta.url));
87
128
  for (const sequenceMode of [true, false]) {
88
129
  let summary;
89
- const nested = await decompile(xml, { ...base0, style: 'nested', relabelFlows: !keepFlowIds, sequenceMode, onLift: (s) => (summary = s) });
130
+ const nested = await decompile(xml, {
131
+ ...base0, verbatimConnectors, style: 'nested', relabelFlows: !keepFlowIds, sequenceMode, onLift: (s) => (summary = s),
132
+ });
90
133
  const renamed = new Map((summary?.renamedFlows ?? []).map((r) => [r.from, r.to]));
91
134
  const verdict = await sameGraph(source, nested, sdkEntry, renamed);
92
135
  if (verdict.same) {
@@ -107,6 +150,15 @@ export async function run(argv) {
107
150
  console.log(`decompile: dropped ${pruned.length} binding(s) nothing references — ${pruned.slice(0, 8).join(', ')}` +
108
151
  `${pruned.length > 8 ? `, and ${pruned.length - 8} more` : ''}. Pass --keep-unused-bindings to echo them.`);
109
152
  }
153
+ if (connectorSummary && (connectorSummary.lifted.length || connectorSummary.verbatim.length)) {
154
+ const kept = connectorSummary.verbatim.length
155
+ ? `; verbatim: ${connectorSummary.verbatim.map((v) => `${v.id} (${v.reason.split('\n')[0]})`).join('; ')}`
156
+ : '';
157
+ console.log(`decompile: connectors — lifted ${connectorSummary.lifted.length} to .connector()${kept}`);
158
+ }
159
+ else if (connectors === 'lift' && !library) {
160
+ console.log('decompile: no connector library found, connector tasks written verbatim — pass --library <library-json dir> to lift them');
161
+ }
110
162
  if (lifted) {
111
163
  const flat = lifted.leftFlat.length ? `; left flat: ${lifted.leftFlat.map((r) => `${r.id} (${r.reason})`).join('; ')}` : '';
112
164
  const renamed = lifted.renamedFlows.length
@@ -118,7 +170,17 @@ export async function run(argv) {
118
170
  else if (fallback) {
119
171
  console.log(`decompile: --style nested could not be verified, wrote the flat form — ${fallback}`);
120
172
  }
121
- reportResult({ path: resolve(out), style: lifted ? 'nested' : 'explicit', ...(lifted ? { lifted } : {}), ...(fallback ? { fallback } : {}) });
173
+ reportResult({
174
+ path: resolve(out),
175
+ style: lifted ? 'nested' : 'explicit',
176
+ ...(lifted ? { lifted } : {}),
177
+ ...(fallback ? { fallback } : {}),
178
+ // What the lift did, when a library was in play: the ids, not the preamble and
179
+ // calls the verifier consumed.
180
+ ...(library && connectorSummary && (connectorSummary.lifted.length || connectorSummary.verbatim.length)
181
+ ? { connectors: { lifted: connectorSummary.lifted, verbatim: connectorSummary.verbatim } }
182
+ : {}),
183
+ });
122
184
  return 0;
123
185
  }
124
186
  runWhenInvokedDirectly(import.meta.url, 'decompile', run);
@@ -1,7 +1,31 @@
1
+ import type { Library } from '../core/library.js';
1
2
  /** Options for {@link decompile}. */
2
3
  export interface BpmnDecompileOptions {
3
4
  /** Module specifier for the emitted `import { bpmn }` line. */
4
5
  importSpecifier?: string;
6
+ /**
7
+ * The connector library. With one, an `Intsvc.ActivityExecution` the library can
8
+ * name comes back as `.connector(key, action, inputs, { connection, folder, … })` —
9
+ * the call an author writes — instead of the verbatim `.activity()` form; see
10
+ * {@link BpmnDecompileOptions.connectors}. Without one, every connector task stays
11
+ * verbatim, as before.
12
+ */
13
+ library?: Library;
14
+ /**
15
+ * How a connector task is written back when a library is given. `'lift'` (the
16
+ * default) names the operation through the library and drops what the SDK
17
+ * regenerates on compile — the `metadata` blob the canvas writes
18
+ * (`designTimeMetadata`, `metadataState`), the CRUD `operation` verb, row types,
19
+ * the response row's schema body. `'verbatim'` keeps every row as the file spells
20
+ * it. A lift is best effort by construction and safe only with the check
21
+ * `decompile-cli` runs (`verifyConnectorLifts`): the lifted call must compile to the
22
+ * same task, those regenerated fields aside, or the element is written verbatim.
23
+ */
24
+ connectors?: 'lift' | 'verbatim';
25
+ /** Connector tasks to write verbatim whatever `connectors` says — the ones a verify refused. */
26
+ verbatimConnectors?: ReadonlySet<string>;
27
+ /** Called with what the connector lift did, after decompiling. */
28
+ onConnectorLift?: (summary: ConnectorLiftSummary) => void;
5
29
  /**
6
30
  * `'explicit'` (the default) renders the graph flat: one call per element, one
7
31
  * `.sequenceFlow()` per edge. `'nested'` LIFTS the regions that the builder's
@@ -103,4 +127,23 @@ export interface LiftSummary {
103
127
  /** Whether the top level was written in `.flowMode('sequence')`. */
104
128
  sequence: boolean;
105
129
  }
130
+ /** What the connector lift did on one decompile — see `BpmnDecompileOptions.connectors`. */
131
+ export interface ConnectorLiftSummary {
132
+ /** Element ids written as `.connector()`. */
133
+ lifted: string[];
134
+ /** Connector tasks written verbatim, each with the reason. */
135
+ verbatim: Array<{
136
+ id: string;
137
+ reason: string;
138
+ }>;
139
+ /**
140
+ * The source up to the first element — the import, the process call, its metadata,
141
+ * schemas, bindings and variables — so a verifier can compile one lifted call on its
142
+ * own (`verifyConnectorLifts`) instead of the whole process, where one refused lift
143
+ * would take every other down with it.
144
+ */
145
+ preamble: string;
146
+ /** Each lifted call's source line, by element id. */
147
+ calls: Map<string, string>;
148
+ }
106
149
  export declare function decompile(xml: string, options?: BpmnDecompileOptions): Promise<string>;
@@ -244,7 +244,225 @@ function localName(type) {
244
244
  const local = type.split(':')[1] ?? type;
245
245
  return local.charAt(0).toLowerCase() + local.slice(1);
246
246
  }
247
+ // The connector lift's state for one `decompile()` call. Planned in a pre-pass so the
248
+ // variable block, which is written before any element, already knows which tasks come
249
+ // back as `.connector()` — their declarations derive differently (`derivedVariableIds`).
250
+ // The lift's per-decompile state. Module-level `let`s, reset at the top of `decompile()`
251
+ // and read while it emits: nothing awaits between the reset and the last emit, and the
252
+ // module is not re-entered, which is the invariant this relies on. A per-call context
253
+ // object is the right shape if that ever changes.
254
+ let runLibrary;
255
+ let runLiftPlans = new Map();
256
+ let runLiftSkipped = [];
257
+ let runLiftCalls = new Map();
247
258
  /** The `uipath:*` payload element on an element, if it has one. */
259
+ /** Context rows a connector task carries that `.connector()` writes itself, or that the lift reads. */
260
+ const CONNECTOR_CONTEXT_ROWS = new Set([
261
+ 'connectorKey', 'operation', 'objectName', 'method', 'path', 'connection', 'folderKey', 'resourceKey',
262
+ 'metadata', 'activityConfigurationVersion', 'activity',
263
+ ]);
264
+ /** Row targets `.connector()` can address — the library's parameter locations plus the body. */
265
+ const CONNECTOR_ROW_TARGETS = new Set(['path', 'query', 'body', 'multipart', 'file']);
266
+ /**
267
+ * Plan every connector task's lift in `container` (sub-processes included): the id →
268
+ * plan for the ones `.connector()` can express, and a reason for each one it cannot.
269
+ */
270
+ function planConnectorLifts(container, skip) {
271
+ for (const el of container.flowElements ?? []) {
272
+ if (el.$type === 'bpmn:SubProcess')
273
+ planConnectorLifts(el, skip);
274
+ const payload = payloadOf(el);
275
+ if (!payload || extensionTypeOf(payload) !== 'Intsvc.ActivityExecution')
276
+ continue;
277
+ if (skip.has(el.id)) {
278
+ runLiftSkipped.push({ id: el.id, reason: 'the lift did not verify' });
279
+ continue;
280
+ }
281
+ const outcome = planConnectorLift(el, payload);
282
+ if (typeof outcome === 'string')
283
+ runLiftSkipped.push({ id: el.id, reason: outcome });
284
+ else
285
+ runLiftPlans.set(el.id, outcome);
286
+ }
287
+ }
288
+ /** The `=bindings.<id>` a context value references, or `undefined` for anything else. */
289
+ function bindingIdOf(value) {
290
+ const m = typeof value === 'string' ? /^=bindings\.([A-Za-z0-9_]+)$/.exec(value) : null;
291
+ return m ? m[1] : undefined;
292
+ }
293
+ /**
294
+ * A row's value as `.connector()` takes it: an `=`-expression as the string it is;
295
+ * otherwise the literal, coerced to the type the library declares for the parameter,
296
+ * because the file writes every scalar row as `type="string"` (`value="false"`,
297
+ * `value="0"`) and the shared validator refuses a string where it declares a boolean
298
+ * or an integer.
299
+ */
300
+ function liftedInputValue(raw, dataType) {
301
+ if (raw.startsWith('='))
302
+ return raw;
303
+ switch ((dataType ?? 'string').toLowerCase()) {
304
+ case 'boolean':
305
+ return raw === 'true' ? true : raw === 'false' ? false : raw;
306
+ case 'integer':
307
+ case 'number':
308
+ return raw.trim() !== '' && Number.isFinite(Number(raw)) ? Number(raw) : raw;
309
+ default:
310
+ return raw;
311
+ }
312
+ }
313
+ /**
314
+ * The `.connector()` call for one task, or why there is none. Everything here is a
315
+ * fact the lift needs and the generic form carries; what the SDK regenerates on
316
+ * compile is deliberately not read (see `BpmnDecompileOptions.connectors`).
317
+ */
318
+ function planConnectorLift(el, payload) {
319
+ const library = runLibrary;
320
+ if (!library)
321
+ return 'no connector library';
322
+ const ctx = contextOf(payload);
323
+ const rows = payload.context?.input ?? [];
324
+ const unknownCtx = rows.map((r) => r.name).filter((n) => !CONNECTOR_CONTEXT_ROWS.has(n));
325
+ if (unknownCtx.length)
326
+ return `context row(s) .connector() does not write: ${unknownCtx.join(', ')}`;
327
+ if (rows.some((r) => r.required !== undefined || r.target !== undefined))
328
+ return 'a context row carries attributes the record form cannot';
329
+ const ctxString = (k) => (typeof ctx[k] === 'string' ? ctx[k] : undefined);
330
+ const connectorKey = ctxString('connectorKey');
331
+ if (!connectorKey)
332
+ return 'no connectorKey';
333
+ let identity;
334
+ try {
335
+ identity = library.identifyAction({
336
+ connectorKey,
337
+ httpMethod: ctxString('method'),
338
+ endpoint: ctxString('path'),
339
+ objectName: ctxString('objectName'),
340
+ });
341
+ }
342
+ catch (e) {
343
+ return `not in the library: ${e.message}`;
344
+ }
345
+ const connection = bindingIdOf(ctx.connection);
346
+ if (!connection)
347
+ return `connection is ${ctx.connection === undefined ? 'absent' : JSON.stringify(ctx.connection)}, not a =bindings.<id> reference`;
348
+ const folder = ctx.folderKey === undefined ? undefined : bindingIdOf(ctx.folderKey);
349
+ if (ctx.folderKey !== undefined && folder === undefined)
350
+ return `folderKey is ${JSON.stringify(ctx.folderKey)}, not a =bindings.<id> reference`;
351
+ let params;
352
+ let declared;
353
+ try {
354
+ const resolved = library.resolve(identity.key, identity.action, identity.version, identity.object);
355
+ params = new Map((resolved.entry.operation?.parameters ?? []).map((p) => [p.name, p.dataType]));
356
+ declared = new Set([...params.keys(), ...resolved.fields.map((f) => f.name)]);
357
+ }
358
+ catch (e) {
359
+ return `the library cannot resolve ${identity.key}/${identity.action}: ${e.message}`;
360
+ }
361
+ const inputs = {};
362
+ for (const row of payload.input ?? []) {
363
+ const target = row.target;
364
+ if (target === undefined || !CONNECTOR_ROW_TARGETS.has(target)) {
365
+ return `payload row "${row.name}" has ${target === undefined ? 'no target' : `target "${target}"`}`;
366
+ }
367
+ if (target === 'body') {
368
+ if (row.name !== 'body')
369
+ return `body row is named "${row.name}"`;
370
+ const body = safeJson(row.body ?? '{}');
371
+ if (!body || typeof body !== 'object' || Array.isArray(body))
372
+ return 'the body row is not a JSON object';
373
+ for (const [k, v] of Object.entries(body)) {
374
+ if (k in inputs)
375
+ return `"${k}" appears both as a parameter row and in the body`;
376
+ // `.connector()` validates every input against the library, and a field the
377
+ // static library does not declare — an entity's own columns on a Data Fabric
378
+ // update, a form's fields — is refused until `registry prepare` loads it.
379
+ if (!declared.has(k) && !declared.has(k.split('.')[0])) {
380
+ return `body field "${k}" is not in the library for ${identity.key}/${identity.action} (connection-specific; \`registry prepare\` loads it)`;
381
+ }
382
+ inputs[k] = v;
383
+ }
384
+ continue;
385
+ }
386
+ if (row.value === undefined)
387
+ return `payload row "${row.name}" carries no value attribute`;
388
+ if (row.name in inputs)
389
+ return `payload row "${row.name}" appears twice`;
390
+ inputs[row.name] = liftedInputValue(String(row.value), params.get(row.name));
391
+ }
392
+ let outputVar;
393
+ let errorVar;
394
+ const outputs = {};
395
+ for (const row of payload.output ?? []) {
396
+ const standard = (row.name === 'response' && row.source === '=response' && outputVar === undefined) ||
397
+ (row.name === 'Error' && row.source === '=Error' && errorVar === undefined);
398
+ // The two standard rows may carry a schema body — the SDK writes the library's
399
+ // response schema there, and regenerates it. Any other row's body, or a target or
400
+ // description on any row, is something `.connector()` cannot say.
401
+ if (row.target !== undefined || row.description !== undefined || (row.body !== undefined && !standard)) {
402
+ return `output row "${row.name}" carries attributes .connector() does not write`;
403
+ }
404
+ if (standard && row.name === 'response')
405
+ outputVar = row.var;
406
+ else if (standard)
407
+ errorVar = row.var;
408
+ else {
409
+ // An extra mapped row: `outputs` keys it by its variable, so one without a
410
+ // variable or a source, or a second row on the same variable, cannot be said.
411
+ if (typeof row.var !== 'string' || typeof row.source !== 'string')
412
+ return `output row "${row.name}" has no variable or no source`;
413
+ if (row.var in outputs)
414
+ return `two output rows write "${row.var}"`;
415
+ const plain = row.name === row.var && (row.type ?? 'string') === 'string' && !row.custom;
416
+ outputs[row.var] = plain
417
+ ? row.source
418
+ : { source: row.source, ...(row.name !== row.var ? { name: row.name } : {}), ...((row.type ?? 'string') !== 'string' ? { type: row.type } : {}), ...(row.custom ? { custom: true } : {}) };
419
+ }
420
+ }
421
+ return {
422
+ key: identity.key,
423
+ action: identity.action,
424
+ version: identity.version === '1.0.0' ? undefined : identity.version,
425
+ object: identity.object,
426
+ inputs,
427
+ connection,
428
+ folder,
429
+ outputVar: outputVar !== undefined && outputVar !== `${el.id}_response` ? outputVar : undefined,
430
+ errorVar: errorVar !== undefined && errorVar !== `${el.id}_Error` ? errorVar : undefined,
431
+ outputs: Object.keys(outputs).length ? outputs : undefined,
432
+ };
433
+ }
434
+ /** The `outputs` option of a lifted connector as source. */
435
+ function connectorOutputsSource(outputs) {
436
+ return `{ ${Object.entries(outputs)
437
+ .map(([v, spec]) => `${key(v)}: ${typeof spec === 'string' ? tsText(spec) : opts([
438
+ ['source', tsText(spec.source)],
439
+ ['name', spec.name !== undefined ? tsText(spec.name) : undefined],
440
+ ['type', spec.type !== undefined ? tsText(spec.type) : undefined],
441
+ ['custom', spec.custom ? 'true' : undefined],
442
+ ])}`)
443
+ .join(', ')} }`;
444
+ }
445
+ /** A planned connector task as its `.connector()` call. */
446
+ function emitConnectorLift(chain, el, payload, plan) {
447
+ const options = opts([
448
+ ['name', el.name !== undefined ? tsText(el.name) : undefined],
449
+ ['connection', tsText(plan.connection)],
450
+ ['folder', plan.folder !== undefined ? tsText(plan.folder) : undefined],
451
+ ['version', plan.version !== undefined ? tsText(plan.version) : undefined],
452
+ ['object', plan.object !== undefined ? tsText(plan.object) : undefined],
453
+ ['outputVar', plan.outputVar !== undefined ? tsText(plan.outputVar) : undefined],
454
+ ['errorVar', plan.errorVar !== undefined ? tsText(plan.errorVar) : undefined],
455
+ ['outputs', plan.outputs ? connectorOutputsSource(plan.outputs) : undefined],
456
+ ['skipCondition', payload.skipCondition !== undefined ? tsText(payload.skipCondition) : undefined],
457
+ ['tags', tagsOf(el)],
458
+ ['retry', emitRetry(retryOf(el))],
459
+ ['errorMapping', emitErrorMapping(errorMappingOf(el))],
460
+ ['loop', emitLoop(el)],
461
+ ]);
462
+ const call = `.connector(${tsText(el.id)}, ${tsText(plan.key)}, ${tsText(plan.action)}, ${record(plan.inputs) ?? '{}'}${optionalArg(options)})`;
463
+ runLiftCalls.set(el.id, call);
464
+ chain.add(call);
465
+ }
248
466
  function payloadOf(el) {
249
467
  for (const ext of el.extensionElements?.values ?? []) {
250
468
  if (ext.$type === 'uipath:Activity' || ext.$type === 'uipath:Event' || ext.$type === 'uipath:Mapping')
@@ -1584,6 +1802,11 @@ function emitElement(chain, el, depth, indent) {
1584
1802
  if (kind === 'startEvent' && startEventDefinitionCarriesConfig(el)) {
1585
1803
  return emitTriggerStartEvent(chain, el, payload, type);
1586
1804
  }
1805
+ // A connector task the library can name, planned in the pre-pass — see
1806
+ // `BpmnDecompileOptions.connectors` for what the lift gives up and how it is checked.
1807
+ const plan = runLiftPlans.get(el.id);
1808
+ if (plan)
1809
+ return emitConnectorLift(chain, el, payload, plan);
1587
1810
  return emitTypedNode(chain, el, payload, type);
1588
1811
  }
1589
1812
  switch (kind) {
@@ -1930,7 +2153,16 @@ export async function decompile(xml, options = {}) {
1930
2153
  // found none, so NOTHING was recognised as derived and every declaration was echoed
1931
2154
  // by hand. That was invisible — an echoed declaration takes the id before
1932
2155
  // `implicitNodeVars` can claim it, so the output was correct, just 112 lines of it.
1933
- const { ids: derived, folds } = derivedVariableIds(process, varsExt, host.rehome);
2156
+ // The connector lift, planned first: the variable block below needs to know which
2157
+ // tasks come back as `.connector()`, whose declarations derive as a connector's.
2158
+ runLibrary = options.library;
2159
+ runLiftPlans = new Map();
2160
+ runLiftSkipped = [];
2161
+ runLiftCalls = new Map();
2162
+ if (options.library && (options.connectors ?? 'lift') === 'lift') {
2163
+ planConnectorLifts(process, options.verbatimConnectors ?? new Set());
2164
+ }
2165
+ const { ids: derived, folds } = derivedVariableIds(process, varsExt, host.rehome, new Set(runLiftPlans.keys()));
1934
2166
  const schemaPlan = planSchemas([
1935
2167
  ...(varsExt?.input ?? []),
1936
2168
  ...(varsExt?.inputOutput ?? []),
@@ -1951,12 +2183,14 @@ export async function decompile(xml, options = {}) {
1951
2183
  // hand-editing". Only when something was actually folded: a document of explicit
1952
2184
  // `.var()` calls already states its order by their position, and the extra line would
1953
2185
  // be noise.
2186
+ let preambleLines = 0;
1954
2187
  if (derived.size) {
1955
2188
  const ids = [...(varsExt?.input ?? []), ...(varsExt?.inputOutput ?? []), ...(varsExt?.output ?? [])].map((v) => v.id);
1956
2189
  chain.add(`.preserve({ variableOrder: [${ids.map(tsText).join(', ')}] })`);
1957
2190
  }
1958
2191
  try {
1959
2192
  emitVariablesInto(chain, host.container, host.rehome?.to ?? host.container.id, derived, host.rehome, schemaPlan, publicDeclarationScopeOf(process, host.rehome?.to ?? host.container.id));
2193
+ preambleLines = chain.all.length;
1960
2194
  if (options.style === 'nested') {
1961
2195
  const sequence = options.sequenceMode ?? true;
1962
2196
  const summary = { handlers: 0, branches: 0, nets: 0, leftFlat: [], renamedFlows: [], sequence };
@@ -1975,11 +2209,18 @@ export async function decompile(xml, options = {}) {
1975
2209
  }
1976
2210
  finally {
1977
2211
  foldedVarSchemas = undefined;
2212
+ runLibrary = undefined;
1978
2213
  }
1979
2214
  const importFrom = options.importSpecifier ?? './bpmn-sdk.js';
1980
2215
  const imported = ['bpmn', ...[...schemaPlan.imports].sort()].join(', ');
1981
2216
  const head = `import { ${imported} } from ${tsText(importFrom)};\n\nexport default bpmn(${tsText(process.id)})`;
1982
2217
  const name = process.name !== undefined ? `\n${indent}.name(${tsText(process.name)})` : '';
2218
+ options.onConnectorLift?.({
2219
+ lifted: [...runLiftPlans.keys()],
2220
+ verbatim: runLiftSkipped,
2221
+ preamble: `${host.note}${head}${name}\n${chain.all.slice(0, preambleLines).join('\n')}`,
2222
+ calls: runLiftCalls,
2223
+ });
1983
2224
  return `${host.note}${head}${name}\n${chain.all.join('\n')}\n${indent}.build();\n`;
1984
2225
  }
1985
2226
  /**
@@ -2010,7 +2251,9 @@ export async function decompile(xml, options = {}) {
2010
2251
  */
2011
2252
  function derivedVariableIds(process, varsExt,
2012
2253
  /** The collaboration→process re-parenting, so `elementId` is compared post-move. */
2013
- rehome) {
2254
+ rehome,
2255
+ /** Connector tasks the lift writes as `.connector()`, whose declarations `implicitNodeVars` derives as a connector's. */
2256
+ lifted = new Set()) {
2014
2257
  // var id -> the declaration `implicitNodeVars` would derive for it
2015
2258
  const candidates = new Map();
2016
2259
  const walk = (container) => {
@@ -2042,6 +2285,20 @@ rehome) {
2042
2285
  for (const row of payload.output ?? []) {
2043
2286
  if (!row.var)
2044
2287
  continue;
2288
+ // A lifted connector's two variables are claimed the way `.connector()` claims
2289
+ // them — `json`, named after the id, no schema — so a file's own `jsonSchema`
2290
+ // declaration with a body stays an explicit `.var()` rather than being folded
2291
+ // onto a row the lifted call no longer has.
2292
+ if (lifted.has(el.id)) {
2293
+ // A `.connector()` call has no row to fold a declaration's schema onto, so
2294
+ // a declaration that carries one stays explicit whatever its wire type —
2295
+ // `noFold` below — rather than vanishing into `foldedVarSchemas`, which the
2296
+ // lift never reads.
2297
+ // The wire type the row implies: `json` for the response and Error rows, the
2298
+ // row's own for an extra mapped row (`integer`), as `implicitNodeVars` declares.
2299
+ candidates.set(row.var, { owner: el.id, wire: rowDeclaration({ name: row.name, type: row.type }).wire, noFold: true });
2300
+ continue;
2301
+ }
2045
2302
  // A script task's rows come back as `outputs` when that form reproduces
2046
2303
  // them, and `implicitNodeVars` then derives their declarations from the
2047
2304
  // keys: id as the name, that arm's own wire type. Rows that need spelling
@@ -2092,7 +2349,7 @@ rehome) {
2092
2349
  // against 28 rows, so without the fold almost every structured output stays an
2093
2350
  // explicit `.var()`.
2094
2351
  if (String(v.body ?? '') !== String(c.schema ?? '')) {
2095
- if (c.schema !== undefined || v.body === undefined)
2352
+ if (c.schema !== undefined || v.body === undefined || c.noFold)
2096
2353
  continue;
2097
2354
  folds.set(v.id, v.body);
2098
2355
  }
@@ -85,6 +85,7 @@ export async function serialize(built, opts = {}) {
85
85
  schemas: built.schemas,
86
86
  variableOrder: built.preservation?.variableOrder,
87
87
  bindingSink: new Map(),
88
+ declaredBindings: new Map(built.bindings.map((b) => [b.id, { name: b.name, default: b.default, resourceKey: b.resourceKey }])),
88
89
  varTypeOf: (varId) => authoredVarType.get(varId),
89
90
  };
90
91
  // `.name()` was accepted by the builder, stored on the built graph, and never
@@ -664,6 +665,14 @@ export function implicitNodeVars(nodes, declared) {
664
665
  const { response, error } = connectorOutputVars(n);
665
666
  claim(response, n.id);
666
667
  claim(error, n.id);
668
+ // An extra mapped row declares its variable as the row is typed (`string` when the
669
+ // row says nothing), not as `object`: `{ poCount: { source, type: 'integer' } }`
670
+ // writes an `integer` row and must declare an integer.
671
+ for (const [varId, spec] of Object.entries(n.outputs ?? {})) {
672
+ const row = typeof spec === 'string' ? { source: spec } : spec;
673
+ const d = rowDeclaration({ name: row.name ?? varId, type: row.type ?? 'string' });
674
+ claim(varId, n.id, d.type, d);
675
+ }
667
676
  }
668
677
  else if (n.kind === 'typed') {
669
678
  // Shared with the expression checker rather than repeated here. Repeating it is
@@ -180,6 +180,15 @@
180
180
  "activityType": "Curated",
181
181
  "objectName": "QueryEntityRecordsCurated",
182
182
  "path": "uipath-uipath-dataservice/query-entity-records@1.0.0.json"
183
+ },
184
+ {
185
+ "nodeType": "uipath.connector.uipath-uipath-dataservice.update-entity-record",
186
+ "version": "1.0.0",
187
+ "connectorKey": "uipath-uipath-dataservice",
188
+ "label": "Update Entity Record",
189
+ "activityType": "Curated",
190
+ "objectName": "UpdateEntityRecordV2",
191
+ "path": "uipath-uipath-dataservice/update-entity-record@1.0.0.json"
183
192
  }
184
193
  ]
185
194
  }