@uipath/maestro-builder-sdk 6.46.6 → 6.47.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.
@@ -8,7 +8,7 @@
8
8
  * `metadata.caseExitRules`. Symbolic references in rules (a stage label, a task
9
9
  * name) are resolved here to the generated ids.
10
10
  */
11
- import { IdGen } from './ids.js';
11
+ import { IdGen, offlineBindingSeed } from './ids.js';
12
12
  import { isUnrestorableSlotId } from './preserve.js';
13
13
  import { v31TaskSets } from './task-sets.js';
14
14
  import { buildConnectorInputs, nestConnectorBody, transportHttpMethod } from '../core/connector-inputs.js';
@@ -22,7 +22,9 @@ import { isResourceLabelRefusal, resolveResourceRef, resourceIdentity } from './
22
22
  import { ownEntry } from '../core/own-entry.js';
23
23
  import { resolveSchemaVersion } from '../schema-version.js';
24
24
  import { caseJsonPreservation, caseJsonPreservationErrors, } from './preserve.js';
25
+ import { businessEventProblems } from './business-event-shape.js';
25
26
  import { readEventFilter, eventFilterProblem, eventFilterTreeLeaf } from '../core/event-filters.js';
27
+ import { ruleShapeProblems } from './rule-options.js';
26
28
  /**
27
29
  * Condition `displayName` allocator.
28
30
  *
@@ -81,6 +83,8 @@ export function serialize(built, opts = {}) {
81
83
  library: opts.library,
82
84
  bindings: opts.bindings,
83
85
  bindingSink: new Map(),
86
+ ids,
87
+ offlineBindingIds: new Map(),
84
88
  varCompanions: new Map(),
85
89
  outputIds: new Set(),
86
90
  ownOutputVars: ownOutputVars(built),
@@ -88,6 +92,7 @@ export function serialize(built, opts = {}) {
88
92
  slaIdByName: new Map(),
89
93
  escalationId: new Map(),
90
94
  escalationIdByName: new Map(),
95
+ businessEvents: new Set((built.businessEvents ?? []).map((event) => event.name)),
91
96
  conditionName: conditionNamer(built),
92
97
  };
93
98
  // Preallocate SLA + escalation ids (before any rule is emitted) so a
@@ -219,6 +224,12 @@ export function serialize(built, opts = {}) {
219
224
  if (built.slaRules.length) {
220
225
  metadata.slaRules = built.slaRules.map((s, i, all) => emitSla(s, i, all.length, ids, resolver));
221
226
  }
227
+ if (built.businessEvents?.length) {
228
+ const [problem] = businessEventProblems(built.businessEvents);
229
+ if (problem)
230
+ throw new Error(problem.message);
231
+ metadata.businessEvents = built.businessEvents.map(emitBusinessEvent);
232
+ }
222
233
  // Variables: declared vars + the readable companions that task outputs bound
223
234
  // (collected during stage emission above). A declared var wins on a name clash.
224
235
  const variables = emitVariables(built.vars, ids, triggerNodeByRef);
@@ -1469,6 +1480,20 @@ function ioSlot(name, bodyObj, elementId) {
1469
1480
  const v = `v${name}`;
1470
1481
  return { name, type: 'json', target: name, body: bodyObj, var: v, id: v, elementId };
1471
1482
  }
1483
+ /**
1484
+ * The binding id for a label no bindings.json declares. The row's
1485
+ * `resourceKey`/`default` stays the label, and the id is minted from it (`b` +
1486
+ * 8, as Studio Web mints one), once per label; `offlineBindingLabel` reverses
1487
+ * it for decompile.
1488
+ */
1489
+ function offlineBindingId(resolver, seed) {
1490
+ let id = resolver.offlineBindingIds.get(seed);
1491
+ if (id === undefined) {
1492
+ id = resolver.ids.make('b', 8, seed);
1493
+ resolver.offlineBindingIds.set(seed, id);
1494
+ }
1495
+ return id;
1496
+ }
1472
1497
  /** Resolve a symbolic connection/folder to a root binding entry (collected once),
1473
1498
  * returning its id + resourceKey for the `=bindings.<id>` reference. */
1474
1499
  function bindingRef(resolver, name, resource, propertyAttribute, displayName, site) {
@@ -1483,8 +1508,12 @@ function bindingRef(resolver, name, resource, propertyAttribute, displayName, si
1483
1508
  if (refused)
1484
1509
  throw refused;
1485
1510
  const entry = resolver.bindings?.entry(name);
1486
- const id = entry?.id ?? name;
1487
1511
  const resourceKey = entry?.resourceKey ?? entry?.default ?? name;
1512
+ // Only a label with no bindings.json entry gets a minted id: an entry's
1513
+ // `resourceKey` is the real key, so its id (or its name) cannot clash with it.
1514
+ const id = entry
1515
+ ? String(entry.id ?? name)
1516
+ : offlineBindingId(resolver, offlineBindingSeed(resource, propertyAttribute, name));
1488
1517
  if (!resolver.bindingSink.has(id)) {
1489
1518
  resolver.bindingSink.set(id, {
1490
1519
  id,
@@ -1501,7 +1530,8 @@ function bindingRef(resolver, name, resource, propertyAttribute, displayName, si
1501
1530
  /**
1502
1531
  * A connector's folder is a property of its connection:
1503
1532
  * { resourceKey: <connection key>, default: <folder key>, propertyAttribute: 'folderKey' }.
1504
- * One folder label on two connections gets one row each: `shared`, `shared_2`.
1533
+ * One folder label on two connections gets one row each: `shared`, `shared_2`
1534
+ * (offline, the minted id is seeded on the connection, so the rows differ anyway).
1505
1535
  * One connection with two different folders is refused: a connection lives in
1506
1536
  * one folder, and the syncs group rows by `resourceKey`, so one value would win.
1507
1537
  * Without a connection the folder binds as a plain binding.
@@ -1515,7 +1545,9 @@ function folderBindingRef(resolver, name, connection, site) {
1515
1545
  if (refused)
1516
1546
  throw refused;
1517
1547
  const entry = resolver.bindings?.entry(name);
1518
- const baseId = entry?.id ?? name;
1548
+ const baseId = entry
1549
+ ? String(entry.id ?? name)
1550
+ : offlineBindingId(resolver, offlineBindingSeed('Connection', 'folderKey', name, connection.resourceKey));
1519
1551
  const folderKey = entry?.default ?? entry?.resourceKey ?? name;
1520
1552
  const matches = (row) => row?.resourceKey === connection.resourceKey && row?.default === folderKey
1521
1553
  && row?.propertyAttribute === 'folderKey';
@@ -1722,6 +1754,9 @@ function emitRules(rules, seed, ownerId, ids, resolver, site) {
1722
1754
  return rules.map((group, gi) => group.map((r, ri) => emitRule(r, `${seed}:${gi}:${ri}`, ownerId, ids, resolver, site)));
1723
1755
  }
1724
1756
  function emitRule(r, seed, ownerId, ids, resolver, site) {
1757
+ const [shape] = ruleShapeProblems(r);
1758
+ if (shape)
1759
+ throw new Error(`${shape.message.replace(/^Rule /, 'rule ')} (in ${site})`);
1725
1760
  const ruleId = ids.ruleId(seed);
1726
1761
  const out = { rule: r.rule, id: ruleId };
1727
1762
  if (r.rule === 'selected-stage-completed' || r.rule === 'selected-stage-exited') {
@@ -1757,10 +1792,57 @@ function emitRule(r, seed, ownerId, ids, resolver, site) {
1757
1792
  out.escalationId = escId;
1758
1793
  }
1759
1794
  }
1795
+ if (r.rule === 'api-event') {
1796
+ // The event is matched by its exact name at run time; a rule without one
1797
+ // listens for nothing, so both a missing and an undeclared name refuse.
1798
+ if (!r.businessEvent)
1799
+ throw new Error(`rule "api-event" in ${site} names no business event — write rule('api-event', { businessEvent: '<name>' }) with a name declared by .businessEvent('<name>').`);
1800
+ if (!resolver.businessEvents.has(r.businessEvent))
1801
+ throw new Error(`rule "api-event" in ${site} listens for business event "${r.businessEvent}", which the case does not declare — add .businessEvent(${JSON.stringify(r.businessEvent)}).`);
1802
+ out.businessEventType = r.businessEvent;
1803
+ }
1760
1804
  if (r.expression)
1761
1805
  out.conditionExpression = r.expression;
1762
1806
  return out;
1763
1807
  }
1808
+ const JSON_SCHEMA_DRAFT_7 = 'http://json-schema.org/draft-07/schema#';
1809
+ /**
1810
+ * True only for a payload that says nothing at all: `{}`, `{ type: 'object' }`
1811
+ * or `{ type: 'object', properties: {} }`. Any other key (`required`,
1812
+ * `$schema`, a non-object `type`, ...) is content the author wrote, so the
1813
+ * schema is written and survives a decompile/recompile round trip.
1814
+ */
1815
+ function isEmptyObjectSchema(payload) {
1816
+ return Object.entries(payload).every(([key, value]) => {
1817
+ if (key === 'type')
1818
+ return value === 'object';
1819
+ if (key === 'properties')
1820
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
1821
+ return false;
1822
+ });
1823
+ }
1824
+ /**
1825
+ * One `metadata.businessEvents[]` entry, in the shape the Case Designer saves
1826
+ * (`{ name, description?, schema? }`). `schema` is the payload's JSON Schema
1827
+ * serialized to a string with `$schema` defaulted to draft 7. The payload is an
1828
+ * object or array schema (`businessEventProblems` refuses any other type before
1829
+ * this runs), and every one except the empty object schema
1830
+ * (`isEmptyObjectSchema`) is written. That keeps more than the Designer and `uip maestro case sdd
1831
+ * convert`, which write a schema only for `type: 'array'` or a non-empty
1832
+ * `properties`: `{ type: 'object', required: ['a'] }` is written here and left
1833
+ * out there, so a decompiled payload recompiles unchanged.
1834
+ */
1835
+ function emitBusinessEvent(event) {
1836
+ const out = { name: event.name };
1837
+ if (event.description !== undefined)
1838
+ out.description = event.description;
1839
+ const payload = event.payload;
1840
+ if (payload !== undefined) {
1841
+ if (!isEmptyObjectSchema(payload))
1842
+ out.schema = JSON.stringify({ ...payload, $schema: payload.$schema ?? JSON_SCHEMA_DRAFT_7 });
1843
+ }
1844
+ return out;
1845
+ }
1764
1846
  function emitEntry(c, seed, ownerId, label, ids, resolver) {
1765
1847
  const out = {
1766
1848
  id: ids.conditionId(seed),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.46.6",
3
+ "version": "6.47.0",
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": "64e024e591ad47ef3ebcdebe54d8a07ead2aa871"
96
+ "gitref": "e0223e1b46cdf1eb1f4ab072c970824f52b87eeb"
97
97
  }