@uipath/maestro-builder-sdk 6.36.1 → 6.38.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.
Files changed (41) hide show
  1. package/dist/api-index.md +85 -83
  2. package/dist/api-members.md +339 -335
  3. package/dist/bpmn/bpmn-sdk.d.ts +33 -0
  4. package/dist/bpmn/bpmn-sdk.js +4 -0
  5. package/dist/bpmn/check.js +79 -0
  6. package/dist/bpmn/connector-lift-verify.d.ts +32 -0
  7. package/dist/bpmn/connector-lift-verify.js +220 -0
  8. package/dist/bpmn/connector.d.ts +12 -0
  9. package/dist/bpmn/connector.js +19 -1
  10. package/dist/bpmn/declared-vars.js +10 -3
  11. package/dist/bpmn/decompile-cli.js +68 -6
  12. package/dist/bpmn/decompile.d.ts +43 -0
  13. package/dist/bpmn/decompile.js +274 -9
  14. package/dist/bpmn/intsvc.d.ts +9 -10
  15. package/dist/bpmn/intsvc.js +31 -18
  16. package/dist/bpmn/serialize.js +31 -24
  17. package/dist/cli-library.js +3 -1
  18. package/dist/core/cli-library.d.ts +8 -2
  19. package/dist/core/cli-library.js +6 -4
  20. package/dist/core/connector-resolve-error.d.ts +7 -1
  21. package/dist/core/event-checks.js +2 -2
  22. package/dist/core/lookups.d.ts +22 -0
  23. package/dist/core/lookups.js +34 -1
  24. package/dist/core/synthesized-definition.d.ts +15 -0
  25. package/dist/core/synthesized-definition.js +22 -0
  26. package/dist/registry/library-build/context.d.ts +16 -0
  27. package/dist/registry/library-build/context.js +32 -5
  28. package/dist/registry/prepare/actions.d.ts +15 -0
  29. package/dist/registry/prepare/actions.js +12 -1
  30. package/dist/registry/prepare/integration-service.d.ts +105 -0
  31. package/dist/registry/prepare/integration-service.js +218 -0
  32. package/dist/registry/prepare/messages.d.ts +41 -1
  33. package/dist/registry/prepare/messages.js +82 -1
  34. package/dist/registry/prepare/pipeline.js +73 -15
  35. package/dist/registry/uip/commands.d.ts +8 -0
  36. package/dist/registry/uip/commands.js +12 -0
  37. package/dist/serialize.js +11 -0
  38. package/lib/library-json.samples/index.json +9 -0
  39. package/lib/library-json.samples/uipath-uipath-dataservice/update-entity-record@1.0.0.json +132 -0
  40. package/lib/library-json.samples/uipath-uipath-dataservice/update-entity-record@1.0.0.v1def.json +254 -0
  41. package/package.json +2 -2
@@ -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) => {
@@ -2032,15 +2275,37 @@ rehome) {
2032
2275
  // a bodied declaration through: ten `error<n>` declarations owned by boundary
2033
2276
  // events in `claims-triage` were skipped as derived and then nobody derived them
2034
2277
  // — they vanished from the recompiled process.
2035
- if (payload && type && type !== 'BPMN.Variables' && typeDescribesElement(localName(el.$type), type)) {
2278
+ // A script task's default payload type is `BPMN.Variables` — the same type a
2279
+ // variable-assignment task carries, which derives nothing. The script task with
2280
+ // spelled-out rows is admitted by its element, not its type: those rows are the
2281
+ // declaration on the way back in (`implicitNodeVars`' script arm), so they are
2282
+ // skipped here too, or the source redeclares every one of them in `.vars()`.
2283
+ const spelledOutScriptRows = localName(el.$type) === 'scriptTask' && type === 'BPMN.Variables' && outputRowsNeedSpellingOut(payload);
2284
+ if (payload && type && (spelledOutScriptRows || (type !== 'BPMN.Variables' && typeDescribesElement(localName(el.$type), type)))) {
2036
2285
  for (const row of payload.output ?? []) {
2037
2286
  if (!row.var)
2038
2287
  continue;
2039
- // A script task's rows are NOT spelled-out `outputRows` on the way back —
2040
- // `implicitNodeVars` derives its declarations from `outputs` keys, so they
2041
- // carry the id as the name and that arm's own wire type. Every other typed
2042
- // node's rows go through `rowDeclaration`, the same call the serializer makes.
2043
- candidates.set(row.var, type === 'BPMN.ScriptTask'
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
+ }
2302
+ // A script task's rows come back as `outputs` when that form reproduces
2303
+ // them, and `implicitNodeVars` then derives their declarations from the
2304
+ // keys: id as the name, that arm's own wire type. Rows that need spelling
2305
+ // out (`emitScriptTask` writes `outputRows` exactly when this holds) are
2306
+ // declared through `rowDeclaration` on the way back in, like every other
2307
+ // typed node's, so they are matched the same way here.
2308
+ candidates.set(row.var, type === 'BPMN.ScriptTask' && !outputRowsNeedSpellingOut(payload)
2044
2309
  ? { owner: el.id, wire: 'string' }
2045
2310
  : { owner: el.id, ...rowDeclaration({ name: row.name, type: row.type, schema: row.body }) });
2046
2311
  }
@@ -2084,7 +2349,7 @@ rehome) {
2084
2349
  // against 28 rows, so without the fold almost every structured output stays an
2085
2350
  // explicit `.var()`.
2086
2351
  if (String(v.body ?? '') !== String(c.schema ?? '')) {
2087
- if (c.schema !== undefined || v.body === undefined)
2352
+ if (c.schema !== undefined || v.body === undefined || c.noFold)
2088
2353
  continue;
2089
2354
  folds.set(v.id, v.body);
2090
2355
  }
@@ -35,11 +35,11 @@ type ExternalNode = Extract<BpmnNode, {
35
35
  /**
36
36
  * The extension type a Phase 3d node lowers to, WITHOUT consulting the library.
37
37
  *
38
- * The emitters below take a connector event's type from the library's
39
- * `runtime.serviceType`, which is the authority. This is the same answer derived
40
- * from the node alone, so the serializer can declare the node's output variable
41
- * before it has resolved anything — and the emitter asserts the two agree, so the
42
- * shortcut cannot silently diverge.
38
+ * The method the author called decides the placement — `.eventTrigger()` a start
39
+ * event, `.waitForEvent()` a receive task — so the type is known from the node alone,
40
+ * and the serializer can declare the node's output variable before it has resolved
41
+ * anything. The emitter uses this same value; the library only has to say the
42
+ * operation is a connector event.
43
43
  */
44
44
  export declare function intsvcServiceType(n: EventNode | ExternalNode): string;
45
45
  /**
@@ -55,11 +55,10 @@ export declare function intsvcOutputVar(n: EventNode | ExternalNode): string | u
55
55
  /**
56
56
  * A connector event node — `Intsvc.EventTrigger` or `Intsvc.WaitForEvent`.
57
57
  *
58
- * The extension type comes from the LIBRARY (`runtime.serviceType`), not from the
59
- * method the author called. That is deliberate: the descriptor is the authority on
60
- * which placement it describes, so a `trigger` descriptor reached through
61
- * `.waitForEvent()` is caught here rather than emitting a start-event type onto a
62
- * receive task — a mismatch both offline validators accept.
58
+ * The extension type comes from the method the author called; the library only has
59
+ * to say the operation is a connector event (`runtime.serviceType` is one of the
60
+ * event types). A prepared descriptor carries the START form's runtime whichever
61
+ * method reaches it, so the placement, not the descriptor, decides the type.
63
62
  *
64
63
  * @param ctx - Serializer context: moddle, library, bindings, binding sink.
65
64
  * @param n - The authored node.
@@ -1,6 +1,7 @@
1
1
  import { createTypedNode, typedNodeOutputVar } from './typed-node.js';
2
2
  import { connectorBindingRef } from './connector.js';
3
3
  import { eventFilterJmes, eventFilterProblem, jmesString, jmesStringProblem, readEventFilter } from '../core/event-filters.js';
4
+ import { resolveEventWhere } from '../core/lookups.js';
4
5
  /**
5
6
  * Which extension type an external invocation lowers to.
6
7
  *
@@ -19,11 +20,11 @@ const EVENT_SERVICE_TYPE = { trigger: 'Intsvc.EventTrigger', event: 'Intsvc.Wait
19
20
  /**
20
21
  * The extension type a Phase 3d node lowers to, WITHOUT consulting the library.
21
22
  *
22
- * The emitters below take a connector event's type from the library's
23
- * `runtime.serviceType`, which is the authority. This is the same answer derived
24
- * from the node alone, so the serializer can declare the node's output variable
25
- * before it has resolved anything — and the emitter asserts the two agree, so the
26
- * shortcut cannot silently diverge.
23
+ * The method the author called decides the placement — `.eventTrigger()` a start
24
+ * event, `.waitForEvent()` a receive task — so the type is known from the node alone,
25
+ * and the serializer can declare the node's output variable before it has resolved
26
+ * anything. The emitter uses this same value; the library only has to say the
27
+ * operation is a connector event.
27
28
  */
28
29
  export function intsvcServiceType(n) {
29
30
  return n.kind === 'connectorEvent'
@@ -45,11 +46,10 @@ export function intsvcOutputVar(n) {
45
46
  /**
46
47
  * A connector event node — `Intsvc.EventTrigger` or `Intsvc.WaitForEvent`.
47
48
  *
48
- * The extension type comes from the LIBRARY (`runtime.serviceType`), not from the
49
- * method the author called. That is deliberate: the descriptor is the authority on
50
- * which placement it describes, so a `trigger` descriptor reached through
51
- * `.waitForEvent()` is caught here rather than emitting a start-event type onto a
52
- * receive task — a mismatch both offline validators accept.
49
+ * The extension type comes from the method the author called; the library only has
50
+ * to say the operation is a connector event (`runtime.serviceType` is one of the
51
+ * event types). A prepared descriptor carries the START form's runtime whichever
52
+ * method reaches it, so the placement, not the descriptor, decides the type.
53
53
  *
54
54
  * @param ctx - Serializer context: moddle, library, bindings, binding sink.
55
55
  * @param n - The authored node.
@@ -67,14 +67,20 @@ export function createConnectorEvent(ctx, n) {
67
67
  throw new Error(`connector event ${n.key}/${n.eventName} has no runtime.serviceType in the library; ` +
68
68
  `it cannot be authored as a BPMN node.`);
69
69
  }
70
- // The library is the authority, but the serializer has already declared this
71
- // node's output variable from the placement alone (`intsvcServiceType`). If the
72
- // two ever disagree, that variable is wrong — so say so rather than emit it.
70
+ // The PLACEMENT decides the extension type — `.eventTrigger()` starts the
71
+ // process (`Intsvc.EventTrigger`), `.waitForEvent()` pauses it (`Intsvc.WaitForEvent`)
72
+ // — and the serializer has already declared this node's output variable from it
73
+ // (`intsvcServiceType`). The library's `runtime.serviceType` only has to agree
74
+ // that the operation IS a connector event: the platform ships one operation for
75
+ // both placements, the baked library carries the wait form under `.event.`, and
76
+ // `prepare` writes the same entry with the START form's runtime block (measured
77
+ // on `uipath-microsoft-outlook365/email-received`, 2026-10-16) — so reading the
78
+ // placement off that field refused every prepared `.waitForEvent()` with "use
79
+ // .eventTrigger() instead", while the unprepared same node compiled.
73
80
  const expected = intsvcServiceType(n);
74
- if (type !== expected) {
81
+ if (type !== 'Intsvc.EventTrigger' && type !== 'Intsvc.WaitForEvent') {
75
82
  throw new Error(`connector event "${n.id}" was authored as a ${n.subscribe} (${expected}), but the library ` +
76
- `describes ${n.key}/${n.eventName} as ${type}. Use ` +
77
- `${type === 'Intsvc.EventTrigger' ? '.eventTrigger()' : '.waitForEvent()'} instead.`);
83
+ `describes ${n.key}/${n.eventName} as ${type}, which is not a connector event.`);
78
84
  }
79
85
  const conn = connectorBindingRef(ctx, n.connection, 'Connection', 'ConnectionId', `${rc.connectorKey} connection`);
80
86
  const folder = connectorBindingRef(ctx, n.folder, 'Connection', 'folderKey', 'FolderKey', conn?.resourceKey);
@@ -108,10 +114,17 @@ export function createConnectorEvent(ctx, n) {
108
114
  // The expression is the scope's equality clauses AND-joined with the authored
109
115
  // `filters`, each lowered by the designer's own grammar (`core/event-filters.ts`) — the
110
116
  // same lowering the Flow serializer does for `onEvent`/`waitForEvent`.
111
- inputs.body = eventFilterBody(n.id, n.filter, n.filters);
117
+ // A `lookup()` token in the scope is replaced by the id `prepare --resolve`
118
+ // recorded, as Flow's and Case's serializers do; an unrecorded one is refused
119
+ // with the command that records it. Stringified, a token was `[object Object]`.
120
+ const scope = resolveEventWhere(n.filter, ctx.library.lookupResolutions, `connector event "${n.id}"`);
121
+ inputs.body = eventFilterBody(n.id, scope, n.filters);
112
122
  if (n.parameters !== undefined)
113
123
  inputs.parameters = n.parameters;
114
- return createTypedNode(ctx.m, n.id, type, {
124
+ return createTypedNode(ctx.m, n.id,
125
+ // The placement's type, so a prepared `.event.` entry carrying the start form's
126
+ // runtime cannot turn a wait into a start event (see the check above).
127
+ expected, {
115
128
  name: n.name ?? rc.entry.display?.label ?? n.eventName,
116
129
  inputs,
117
130
  outputVar: n.outputVar,
@@ -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
@@ -154,14 +155,7 @@ export async function serialize(built, opts = {}) {
154
155
  version: meta.caseManagement.version,
155
156
  value: meta.caseManagement.value,
156
157
  }));
157
- // Process-level marker that accompanies an Integration Service connector graph.
158
- // Derived from the GRAPH, not from whether connector wiring happened to produce
159
- // a binding: a connector with no connection/folder produced no binding and so
160
- // used to lose the marker, and a DECOMPILED connector — which carries explicit
161
- // `.binding()` calls rather than derived ones — lost it too.
162
- if (allNodes(built.nodes).some(isIntsvcConnectorNode)) {
163
- processExt.push(m.create('uipath:IntsvcActivityConfig', { version: 'v2' }));
164
- }
158
+ // No process `intsvcActivityConfig`: the CLI packs Connection rows only for V1 nodes (https://github.com/UiPath/builder-sdk/issues/1298 tracks V2).
165
159
  if (processExt.length)
166
160
  processProps.extensionElements = m.create('bpmn:ExtensionElements', { values: processExt });
167
161
  processProps.flowElements = flowElements;
@@ -671,6 +665,14 @@ export function implicitNodeVars(nodes, declared) {
671
665
  const { response, error } = connectorOutputVars(n);
672
666
  claim(response, n.id);
673
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
+ }
674
676
  }
675
677
  else if (n.kind === 'typed') {
676
678
  // Shared with the expression checker rather than repeated here. Repeating it is
@@ -711,13 +713,27 @@ export function implicitNodeVars(nodes, declared) {
711
713
  claim(typedNodeOutputVar(n.id, n.trigger.type, n.trigger.outputVar), n.id);
712
714
  }
713
715
  else if (n.kind === 'scriptTask') {
714
- // `object` (emitted as `json`), not `string`. Under the v3 contract a script
715
- // returns `{ response: <whatever it returned> }`, so an output nobody declared
716
- // holds arbitrary JSON — declaring a number or an object as `string` is the
717
- // mis-declaration this fallback exists to avoid. An output the AUTHOR declared
718
- // keeps its own type: `claim` skips an id already taken.
719
- for (const varId of Object.keys(n.outputs))
720
- claim(varId, n.id);
716
+ if (n.outputRows) {
717
+ // Spelled-out rows state their declaration, as a typed node's do — the same
718
+ // `rowDeclaration` call, so `nodeDeclaredVars`, this, and decompile's skip set
719
+ // agree. Deriving from `outputs` alone here left a script task authored with
720
+ // `outputRows` declaring nothing on the wire: the checker (once it counted the
721
+ // rows) passed, and `uip maestro bpmn validate` reported MISSING_ROOT_VARIABLE
722
+ // on the task and VARIABLE_DOES_NOT_EXIST on every read of its variables.
723
+ for (const row of n.outputRows) {
724
+ const d = rowDeclaration(row);
725
+ claim(row.var, n.id, d.type, d);
726
+ }
727
+ }
728
+ else {
729
+ // `object` (emitted as `json`), not `string`. Under the v3 contract a script
730
+ // returns `{ response: <whatever it returned> }`, so an output nobody declared
731
+ // holds arbitrary JSON — declaring a number or an object as `string` is the
732
+ // mis-declaration this fallback exists to avoid. An output the AUTHOR declared
733
+ // keeps its own type: `claim` skips an id already taken.
734
+ for (const varId of Object.keys(n.outputs))
735
+ claim(varId, n.id);
736
+ }
721
737
  }
722
738
  }
723
739
  return out;
@@ -950,15 +966,6 @@ export function publicDeclarationScope(nodes, processId) {
950
966
  function cap(s) {
951
967
  return s.charAt(0).toUpperCase() + s.slice(1);
952
968
  }
953
- /**
954
- * Whether a node is an Integration Service connector activity — the `.connector()`
955
- * method, or the same extension types reached through `.activity()`.
956
- */
957
- function isIntsvcConnectorNode(n) {
958
- if (n.kind === 'connector')
959
- return true;
960
- return n.kind === 'typed' && (n.type === 'Intsvc.ActivityExecution' || n.type === 'Intsvc.AsyncExecution');
961
- }
962
969
  /** Flatten a scope's nodes (recursing sub-processes) for global scans. */
963
970
  export function allNodes(nodes) {
964
971
  const out = [];
@@ -1,4 +1,5 @@
1
1
  import { connectorCheckOpts } from './core/cli-library.js';
2
+ import { synthesizedDefinitionRefusal } from './core/synthesized-definition.js';
2
3
  import { connectorInputErrors } from './serialize.js';
3
4
  export { openCliLibrary, connectorCheckOpts, eventResolveErrorFor, byoaCheckOpts } from './core/cli-library.js';
4
5
  /**
@@ -10,7 +11,8 @@ export { openCliLibrary, connectorCheckOpts, eventResolveErrorFor, byoaCheckOpts
10
11
  * cannot disagree.
11
12
  */
12
13
  export function flowCheckOpts(library, bindings) {
13
- const shared = connectorCheckOpts(library, bindings, connectorInputErrors);
14
+ // Flow's own refusal of an operation `prepare` synthesized from Integration Service (BPMN-only).
15
+ const shared = connectorCheckOpts(library, bindings, connectorInputErrors, synthesizedDefinitionRefusal);
14
16
  if (!library)
15
17
  return shared;
16
18
  return {