@uipath/maestro-builder-sdk 6.46.2 → 6.46.4

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.
@@ -19,6 +19,7 @@ import { resolveCaseSubscription } from './subscription.js';
19
19
  import { externalDescriptorRefusal } from './external.js';
20
20
  import { CASE_FORMAT_PROFILE, CASE_SCHEMA_VERSIONS } from './format-profile.js';
21
21
  import { resourceIdentity } from './resource-identity.js';
22
+ import { ownEntry } from '../core/own-entry.js';
22
23
  import { resolveSchemaVersion } from '../schema-version.js';
23
24
  import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
24
25
  import { readEventFilter, eventFilterProblem, eventFilterTreeLeaf } from '../core/event-filters.js';
@@ -81,7 +82,8 @@ export function serialize(built, opts = {}) {
81
82
  bindings: opts.bindings,
82
83
  bindingSink: new Map(),
83
84
  varCompanions: new Map(),
84
- connectorOutputCounts: new Map(),
85
+ outputIds: new Set(),
86
+ ownOutputVars: ownOutputVars(built),
85
87
  slaId: new Map(),
86
88
  slaIdByName: new Map(),
87
89
  escalationId: new Map(),
@@ -523,6 +525,18 @@ function applyPreservedIdentities(result, preservation) {
523
525
  const triggerNodes = nodes.filter((node) => node.type === 'case-management:Trigger' || node.type === 'uipath.case.trigger');
524
526
  (identities.triggers ?? []).forEach((trigger, index) => take(triggerNodes[index], trigger.id));
525
527
  const stages = nodes.filter((node) => String(node.type).endsWith('Stage'));
528
+ // Every task-output row id in the plan, kept current as preserved ids are
529
+ // taken. Output ids are unique plan-wide (`claimOutputId`); a preserved id that
530
+ // another row already holds would undo that. Example: an older SDK wrote two
531
+ // action rows both `{ id: 'action', var: 'action' }`; this pass gives the
532
+ // second `action2`, and taking its preserved `action` would make it a duplicate.
533
+ const heldOutputIds = new Set();
534
+ for (const stage of stages)
535
+ for (const lane of stage.data?.tasks ?? [])
536
+ for (const task of lane)
537
+ for (const row of task?.data?.outputs ?? [])
538
+ if (typeof row?.id === 'string')
539
+ heldOutputIds.add(row.id);
526
540
  for (const stageIdentity of identities.stages ?? []) {
527
541
  const stage = stages.find((candidate) => candidate.data?.label === stageIdentity.label);
528
542
  take(stage, stageIdentity.id);
@@ -547,7 +561,19 @@ function applyPreservedIdentities(result, preservation) {
547
561
  // A positional match never takes a row a name match owns.
548
562
  : named.has(outputRows[index]?.name) ? undefined : outputRows[index];
549
563
  const currentId = target?.id;
564
+ // An own-name row's id IS its variable's name (`emitTaskOutput`). A
565
+ // preserved id that differs — an `o<hash>` slot from a plan an older
566
+ // SDK wrote — would rename the variable out from under every
567
+ // `=vars.<name>` reader, so the typed rule wins and the row migrates.
568
+ if (target && OWN_NAME_OUTPUT_ROWS.has(target) && row.id !== target.id)
569
+ return;
570
+ if (target && typeof row.id === 'string' && row.id !== currentId && heldOutputIds.has(row.id))
571
+ return;
550
572
  take(target, row.id);
573
+ if (target && typeof row.id === 'string' && typeof currentId === 'string') {
574
+ heldOutputIds.delete(currentId);
575
+ heldOutputIds.add(row.id);
576
+ }
551
577
  if (typeof currentId === 'string' && typeof row.id === 'string' && target?.value === currentId) {
552
578
  target.value = row.id;
553
579
  }
@@ -884,7 +910,12 @@ function taskData(task, resolver, taskId, stageId, ids) {
884
910
  // companion so `=vars.<var>` resolves downstream.
885
911
  if (!task.ref)
886
912
  return {};
887
- const data = { name: task.ref.name, folderPath: task.ref.folderPath };
913
+ // The resource is named through a name/folderPath pair of root bindings,
914
+ // never literals: `validate` refuses a literal (CASE_MGMT_RESOURCE_TASK_
915
+ // LITERAL_BINDING), and deploy re-targets a resource only through its
916
+ // binding (#1240).
917
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
918
+ const data = { name: nameRef, folderPath: folderRef };
888
919
  const elementId = `${stageId}-${taskId}`;
889
920
  if (task.inputs?.length)
890
921
  data.inputs = task.inputs.map((inp, i) => emitTaskInput(inp, elementId, ids, `${elementId}:in:${i}:${inp.name}`));
@@ -906,6 +937,13 @@ function taskData(task, resolver, taskId, stageId, ids) {
906
937
  }
907
938
  if (a.labels)
908
939
  data.labels = a.labels;
940
+ // The Action App the task opens, bound like any resource: an `app`
941
+ // name/folderPath pair keyed `<deployment folder>.<app>` (#1240).
942
+ if (task.ref) {
943
+ const { nameRef, folderRef } = referenceBindings('action', task.ref, resolver, ids);
944
+ data.name = nameRef;
945
+ data.folderPath = folderRef;
946
+ }
909
947
  if (a.actionCatalogName)
910
948
  data.actionCatalogName = a.actionCatalogName;
911
949
  // HITL form fields: inputs are read-only context the assignee sees, outputs
@@ -1104,10 +1142,15 @@ function emitActionField(f, outputElementId, resolver) {
1104
1142
  if (f.value !== undefined)
1105
1143
  row.value = f.value;
1106
1144
  if (outputElementId !== undefined && f.value !== undefined) {
1107
- row.id = f.value;
1145
+ // `id == var` like the Designer; a second form output into the same case
1146
+ // variable takes a suffixed id and becomes a reassigned row (`originalVar`).
1147
+ const id = resolver ? claimOutputId(resolver, f.value) : f.value;
1148
+ row.id = id;
1108
1149
  row.var = f.value;
1150
+ if (id !== f.value)
1151
+ row.originalVar = id;
1109
1152
  row.source = `=${f.name}`;
1110
- row.target = `=${f.value}`;
1153
+ row.target = `=${id}`;
1111
1154
  row.elementId = outputElementId;
1112
1155
  if (resolver && !resolver.varCompanions.has(f.value)) {
1113
1156
  resolver.varCompanions.set(f.value, {
@@ -1130,13 +1173,43 @@ function emitTaskInput(inp, elementId, ids, seed) {
1130
1173
  const slot = ids.make('v', 8, seed);
1131
1174
  return { name: inp.name, type: inp.type, id: slot, var: slot, elementId, value: inp.value };
1132
1175
  }
1133
- /** One `data.outputs[]` row (the extract shape): `source` field → the `var` case variable. */
1176
+ /** Rows `emitTaskOutput` wrote in the own-name shape (`id == var`); see applyPreservedIdentities. */
1177
+ const OWN_NAME_OUTPUT_ROWS = new WeakSet();
1178
+ /**
1179
+ * One `data.outputs[]` row: `source` field → the `var` case variable. Two shapes,
1180
+ * as the Case Designer writes them:
1181
+ *
1182
+ * - **own name** — the output keeps its field's name (a top-level `source` whose
1183
+ * field is the `var`, or the Designer's camelCase of it, either one optionally
1184
+ * with a numeric suffix: `=APIOutput1` into `APIOutput1`, `aPIOutput1` or
1185
+ * `aPIOutput12`; see `keepsOwnName`). The row's `id` IS the `var`, with no
1186
+ * `originalVar`: `{ id: 'APIOutput1', var: 'APIOutput1', target: '=APIOutput1' }`.
1187
+ * - **reassigned** — the output feeds a differently named variable (`=age` into
1188
+ * `estimatedAge`, or any nested `=Error.Message`). The row keeps its own minted
1189
+ * slot `id`, points `var` at the case variable, and repeats the slot in
1190
+ * `originalVar`, which is how the Designer recognises a reassignment.
1191
+ *
1192
+ * Output ids are unique across the plan (`claimOutputId`). When an own-name id
1193
+ * is already taken — a second task writing the same variable — the row takes
1194
+ * the suffixed id (`APIOutput12`) and falls back to the reassigned shape, so its
1195
+ * `var` still names the shared case variable.
1196
+ */
1134
1197
  function emitTaskOutput(o, elementId, ids, seed, resolver) {
1135
- const slot = ids.make('o', 8, seed);
1136
- const field = o.source.replace(/^=/, '').split('.').pop() || o.source;
1198
+ const path = o.source.replace(/^=/, '');
1199
+ const field = path.split('.').pop() || o.source;
1137
1200
  // Register the readable companion once (declared vars take precedence in the merge).
1138
1201
  if (!resolver.varCompanions.has(o.var))
1139
1202
  resolver.varCompanions.set(o.var, { id: o.var, name: o.var, type: o.type, elementId: 'root', custom: true });
1203
+ if (isOwnNameOutput(o)) {
1204
+ const id = claimOutputId(resolver, o.var);
1205
+ if (id === o.var) {
1206
+ const row = { name: field, type: o.type, id, var: o.var, value: o.var, source: o.source, target: `=${id}`, elementId };
1207
+ OWN_NAME_OUTPUT_ROWS.add(row);
1208
+ return row;
1209
+ }
1210
+ return { name: field, type: o.type, id, var: o.var, originalVar: id, value: o.var, source: o.source, target: `=${id}`, elementId };
1211
+ }
1212
+ const slot = claimOutputId(resolver, ids.make('o', 8, seed));
1140
1213
  return {
1141
1214
  name: field,
1142
1215
  type: o.type,
@@ -1160,25 +1233,66 @@ const BUSINESS_RULE_TASK_VERSION = 'v3';
1160
1233
  * pattern) is a different namespace, and a binding that uses it is not a
1161
1234
  * BusinessRule resource to the bindings_v2 reader. */
1162
1235
  const BUSINESS_RULE_RESOURCE = 'BusinessRule';
1163
- /** Mint (once per resourceKey + attribute) one root `BusinessRule` binding. */
1164
- function businessRuleBinding(resolver, ids, resourceKey, propertyAttribute, value) {
1236
+ /**
1237
+ * The root binding `resource` and `resourceSubType` each published-resource
1238
+ * task kind is bound by — the Studio Web / v1-skill table (`bindings/impl-json.md`
1239
+ * § Per Task Type). Every kind but the business rule binds as a `process`; the
1240
+ * subtype tells the platform which kind of process. An RPA process and a Flow
1241
+ * carry no subtype on the binding.
1242
+ */
1243
+ const REFERENCE_BINDING = {
1244
+ process: { resource: 'process', subType: 'ProcessOrchestration' },
1245
+ agent: { resource: 'process', subType: 'Agent' },
1246
+ rpa: { resource: 'process' },
1247
+ 'api-workflow': { resource: 'process', subType: 'Api' },
1248
+ 'case-management': { resource: 'process', subType: 'CaseManagement' },
1249
+ 'flow-process': { resource: 'process' },
1250
+ };
1251
+ /**
1252
+ * Mint (once per resource + resourceKey + attribute) one root binding row of a
1253
+ * resource task's name/folderPath pair, and return its id. Two tasks on one
1254
+ * resource share the pair: a second pair under the same key is what validate
1255
+ * reads as two resources claiming one binding (CASE_MGMT_BINDING_KEY_SHARED).
1256
+ */
1257
+ function resourceBinding(resolver, ids, binding, resourceKey, propertyAttribute, value) {
1165
1258
  for (const entry of resolver.bindingSink.values()) {
1166
- if (entry.resource === BUSINESS_RULE_RESOURCE && entry.resourceKey === resourceKey
1259
+ if (entry.resource === binding.resource && entry.resourceKey === resourceKey
1167
1260
  && entry.propertyAttribute === propertyAttribute)
1168
1261
  return entry.id;
1169
1262
  }
1170
- const id = ids.make('b', 8, `binding:${BUSINESS_RULE_RESOURCE}:${resourceKey}:${propertyAttribute}`);
1263
+ const id = ids.make('b', 8, `binding:${binding.resource}:${resourceKey}:${propertyAttribute}`);
1264
+ // All seven fields: Studio Web fails to render a case whose binding lacks
1265
+ // `name` or `type`.
1171
1266
  resolver.bindingSink.set(id, {
1172
1267
  id,
1173
1268
  name: propertyAttribute,
1174
1269
  type: 'string',
1175
- resource: BUSINESS_RULE_RESOURCE,
1270
+ resource: binding.resource,
1271
+ ...(binding.subType ? { resourceSubType: binding.subType } : {}),
1176
1272
  resourceKey,
1177
1273
  default: value,
1178
1274
  propertyAttribute,
1179
1275
  });
1180
1276
  return id;
1181
1277
  }
1278
+ /** The binding `resource` an action task's Action App is bound by (no subtype). */
1279
+ const ACTION_APP_RESOURCE = 'app';
1280
+ /** The `=bindings.<id>` name/folderPath references of a published-resource task. */
1281
+ function referenceBindings(kind, ref, resolver, ids) {
1282
+ const binding = kind === 'business-rule'
1283
+ ? { resource: BUSINESS_RULE_RESOURCE }
1284
+ : kind === 'action'
1285
+ ? { resource: ACTION_APP_RESOURCE }
1286
+ : ownEntry(REFERENCE_BINDING, kind);
1287
+ if (!binding)
1288
+ throw new Error(`task kind "${kind}" is not a published-resource task.`);
1289
+ // An in-solution resource (`folder: 'solution_folder'`) keeps the sentinel
1290
+ // in its key and binds an empty folderPath — see resource-identity.ts.
1291
+ const { resourceKey, folderPath } = resourceIdentity(ref.name, ref.folderPath);
1292
+ const nameId = resourceBinding(resolver, ids, binding, resourceKey, 'name', ref.name);
1293
+ const folderId = resourceBinding(resolver, ids, binding, resourceKey, 'folderPath', folderPath);
1294
+ return { nameRef: `=bindings.${nameId}`, folderRef: `=bindings.${folderId}` };
1295
+ }
1182
1296
  /**
1183
1297
  * The `data` of a `business-rule` task, as Studio Web writes it and as the
1184
1298
  * CLI's `tasks describe --type business-rule` + splice produce it:
@@ -1194,12 +1308,7 @@ function businessRuleBinding(resolver, ids, resourceKey, propertyAttribute, valu
1194
1308
  * (`=<Decision>.<column>`).
1195
1309
  */
1196
1310
  function businessRuleTaskData(task, resolver, elementId, ids) {
1197
- const ref = task.ref;
1198
- // An in-solution rule (`folder: 'solution_folder'`) keeps the sentinel in its
1199
- // key and binds an empty folderPath — see resource-identity.ts.
1200
- const { resourceKey, folderPath } = resourceIdentity(ref.name, ref.folderPath);
1201
- const nameId = businessRuleBinding(resolver, ids, resourceKey, 'name', ref.name);
1202
- const folderId = businessRuleBinding(resolver, ids, resourceKey, 'folderPath', folderPath);
1311
+ const { nameRef, folderRef } = referenceBindings(task.kind, task.ref, resolver, ids);
1203
1312
  const ownRow = (name, source, seed, extra = {}) => {
1204
1313
  const slot = ids.make('v', 8, `${elementId}:${seed}`);
1205
1314
  return { name, type: 'jsonSchema', id: slot, var: slot, value: slot, source, target: `=${slot}`, elementId, ...extra };
@@ -1215,8 +1324,8 @@ function businessRuleTaskData(task, resolver, elementId, ids) {
1215
1324
  ...(task.outputs ?? []).map((o, i) => emitTaskOutput(o, elementId, ids, `${elementId}:out:${i}:${o.var}`, resolver)),
1216
1325
  ];
1217
1326
  return {
1218
- name: `=bindings.${nameId}`,
1219
- folderPath: `=bindings.${folderId}`,
1327
+ name: nameRef,
1328
+ folderPath: folderRef,
1220
1329
  version: BUSINESS_RULE_TASK_VERSION,
1221
1330
  inputs: (task.inputs ?? []).map((inp, i) => emitTaskInput(inp, elementId, ids, `${elementId}:in:${i}:${inp.name}`)),
1222
1331
  outputs,
@@ -1477,9 +1586,76 @@ function connectorMetadata(rc, op) {
1477
1586
  /** The `outputs[]`: a `response` json-schema from the op's output schema, plus a
1478
1587
  * standard `Error` schema. */
1479
1588
  function connectorOutputVar(resolver, base) {
1480
- const occurrence = (resolver.connectorOutputCounts.get(base) ?? 0) + 1;
1481
- resolver.connectorOutputCounts.set(base, occurrence);
1482
- return occurrence === 1 ? base : `${base}${occurrence}`;
1589
+ return claimOutputId(resolver, base);
1590
+ }
1591
+ /**
1592
+ * Claim a task-output row id from the document-wide pool, the Designer's
1593
+ * uniqueness rule: the first claimant of `base` gets `base`, later ones get
1594
+ * `base2`, `base3`, … (the counter starts at 2 — the Designer-saved plan has
1595
+ * `action` / `action2` and `error` / `error2`). Every output emitter (resource,
1596
+ * action and connector tasks) claims through here, so no two task outputs in a
1597
+ * plan share an id.
1598
+ */
1599
+ function claimOutputId(resolver, base) {
1600
+ let id = base;
1601
+ // A suffix never lands on a variable another output keeps as its own name
1602
+ // (`ownOutputVars`): with `aPIOutput1`, a second writer of it, and a task that
1603
+ // writes `aPIOutput12` itself, the second writer takes `aPIOutput13` whatever
1604
+ // order the tasks come in.
1605
+ for (let n = 2; resolver.outputIds.has(id) || (id !== base && resolver.ownOutputVars.has(id)); n++)
1606
+ id = `${base}${n}`;
1607
+ resolver.outputIds.add(id);
1608
+ return id;
1609
+ }
1610
+ /** Whether a task output is written in the own-name shape (see `emitTaskOutput`). */
1611
+ function isOwnNameOutput(o) {
1612
+ const path = o.source.replace(/^=/, '');
1613
+ return !path.includes('.') && keepsOwnName(o.var, path);
1614
+ }
1615
+ /**
1616
+ * Every variable a task in the plan writes under its own name: own-name task
1617
+ * outputs and action form outputs (whose row id is the variable). Collected
1618
+ * before any row is emitted, so `claimOutputId` can keep suffixes off them.
1619
+ */
1620
+ function ownOutputVars(built) {
1621
+ const vars = new Set();
1622
+ for (const stage of built.stages)
1623
+ for (const lane of stage.lanes)
1624
+ for (const task of lane) {
1625
+ for (const o of task.outputs ?? [])
1626
+ if (isOwnNameOutput(o))
1627
+ vars.add(o.var);
1628
+ for (const f of task.action?.outputs ?? [])
1629
+ if (f.value !== undefined)
1630
+ vars.add(f.value);
1631
+ }
1632
+ return vars;
1633
+ }
1634
+ /**
1635
+ * Whether an output into `name` keeps its field's name: `name` is the field, or
1636
+ * the Designer's camelCase of it, optionally followed by a numeric suffix. The
1637
+ * suffix is how the Designer names the variable a second writer of the same field
1638
+ * gets (`=APIOutput1` → `aPIOutput12`, `=Action` → `action2`): it saves that row
1639
+ * as `{ id: 'aPIOutput12', var: 'aPIOutput12' }` with no `originalVar`, so the
1640
+ * suffixed spelling is an own-name output too, and round-trips as one.
1641
+ */
1642
+ function keepsOwnName(name, field) {
1643
+ // Only an identifier field: `camelizeName` drops other characters, so `items[0]`
1644
+ // would otherwise match `items0`. The suffix is the Designer's counter, which
1645
+ // starts at 2 and has no leading zero: for the field `APIOutput1`, `APIOutput12`
1646
+ // matches; `APIOutput11` (suffix 1) and `APIOutput102` (suffix 02) do not.
1647
+ if (!/^[A-Za-z_]\w*$/.test(field))
1648
+ return false;
1649
+ return [field, camelizeName(field)].some((base) => name === base || (name.startsWith(base) && /^(?:[2-9]|[1-9]\d+)$/.test(name.slice(base.length))));
1650
+ }
1651
+ /**
1652
+ * The Designer's `camelizeDisplayName`: drop non-identifier characters, split on
1653
+ * spaces/underscores, lower the first letter of the first word and upper the
1654
+ * first letter of the rest (`APIOutput1` → `aPIOutput1`).
1655
+ */
1656
+ function camelizeName(name) {
1657
+ const words = name.replace(/[^a-zA-Z0-9_ ]/g, '').split(/[\s_]+/).filter(Boolean);
1658
+ return words.map((w, i) => (i === 0 ? w.charAt(0).toLowerCase() : w.charAt(0).toUpperCase()) + w.slice(1)).join('');
1483
1659
  }
1484
1660
  /** The `Error` output's schema: the designer's (and the CLI's) `ERROR_OUTPUT_SCHEMA`. */
1485
1661
  const ERROR_OUTPUT_SCHEMA = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.46.2",
3
+ "version": "6.46.4",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -93,5 +93,5 @@
93
93
  "@types/node": "^22.7.0",
94
94
  "esbuild": "^0.28.1"
95
95
  },
96
- "gitref": "c3c40cdfae94553b2d63ec072fec2c1b1f45f805"
96
+ "gitref": "d2cb008a329ef32af20be81f73a2ec726e22a13c"
97
97
  }