@uipath/maestro-builder-sdk 6.35.1 → 6.36.1

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.
@@ -2454,6 +2454,17 @@ declare abstract class ScopeBuilder {
2454
2454
  * is left alone.
2455
2455
  */
2456
2456
  private bindRequiredContext;
2457
+ /**
2458
+ * A binding field's resource key from its `resourceKeyPattern`, with the declared
2459
+ * bindings the key was read through — or `undefined` when the pattern does not
2460
+ * resolve from this context (see `resolveResourceKeyPattern`), in which case the
2461
+ * caller keys the binding by the value, as it always has.
2462
+ *
2463
+ * @throws when the pattern resolves except for a `=bindings.<b>` reference that no
2464
+ * `.binding()` before this node declares with a value: that key would otherwise be
2465
+ * built, so the author is told what to declare rather than given a wrong pair.
2466
+ */
2467
+ private resourceKeyFromPattern;
2457
2468
  /** @internal Shared body of the Orchestrator process-invocation methods. */
2458
2469
  private orchestratorNode;
2459
2470
  /**
@@ -2530,12 +2541,23 @@ declare abstract class ScopeBuilder {
2530
2541
  *
2531
2542
  * @remarks
2532
2543
  * **Not a DMN decision table**, despite the element and the label. The registry spec
2533
- * is an Orchestrator process invocation — `releaseKey`/`folderId`/`folderPath`/`name`
2534
- * plus `JobArguments`, `BindingPattern: businessRule` over `Resource: process` —
2535
- * structurally identical to {@link ScopeBuilder.startProcess}. `process` names a
2536
- * package whose Orchestrator process type is `BusinessRules`; whatever decision logic
2537
- * exists lives INSIDE that package, not in the `.bpmn`. So this is the job-start
2538
- * family's sixth member, not a rules subsystem, and it shares every option with it.
2544
+ * is an Orchestrator process invocation — `name`/`folderPath` plus `JobArguments` —
2545
+ * structurally a {@link ScopeBuilder.startProcess}. `process` names a package whose
2546
+ * Orchestrator process type is `BusinessRules`; whatever decision logic exists lives
2547
+ * INSIDE that package, not in the `.bpmn`. So this is the job-start family's sixth
2548
+ * member, not a rules subsystem.
2549
+ *
2550
+ * Where it departs from the family, the registry says so and this follows it:
2551
+ *
2552
+ * - the task is `version="v3"` — below v3 the engine cannot run the `.uirule`
2553
+ * package a BusinessRules project deploys — so a rule is always addressed by
2554
+ * name, and `releaseKey` / `folderKey` are refused;
2555
+ * - `name` and `folderPath` are each bound to a `BusinessRule` resource
2556
+ * (`propertyAttribute` `name` / `folderPath`), both keyed `<folder>.<name>`
2557
+ * (`<name>` alone when the folder is empty), declared on your behalf as
2558
+ * `<id>_name` / `<id>_folderPath`; an unbound `_label` carries the rule's name;
2559
+ * - two output rows: `output` (`=result`, in `outputVar`) and `Error` (`=Error`,
2560
+ * in `<id>_Error`), both `jsonSchema`.
2539
2561
  *
2540
2562
  * Distinct from the Case SDK's `rule()`, which declares DNF lifecycle conditions
2541
2563
  * (`case-entered`, `required-tasks-completed`, …) deciding when a stage enters or
@@ -2555,7 +2577,7 @@ declare abstract class ScopeBuilder {
2555
2577
  * process: 'DiscountTier',
2556
2578
  * folder: 'Finance',
2557
2579
  * input: { amount: 4200 },
2558
- * outputs: { tier: '=response.tier' },
2580
+ * outputs: { tier: '=result.DiscountTier.tier' },
2559
2581
  * })
2560
2582
  * .endEvent('done')
2561
2583
  * .sequenceFlow('start', 'tier')
@@ -2564,9 +2586,16 @@ declare abstract class ScopeBuilder {
2564
2586
  * ```
2565
2587
  *
2566
2588
  * @param id - The element id, unique in the process and used by `sequenceFlow`.
2589
+ *
2590
+ * **The result is keyed by decision name.** The platform's `output` row is the whole
2591
+ * result (`source="=result"`) and a decision's column reads as
2592
+ * `=result.<Decision>.<column>` — `vars.<id>_businessRuleResponse.<Decision>.<column>`
2593
+ * downstream — never `=result.<column>` (canvas spec `outputNotes`; `uip rules describe
2594
+ * <name>` lists the decisions and columns, and only a decision-keyed rule, one whose
2595
+ * `OutputArguments` carry `x-uipath-decision-keyed`, fits this row).
2567
2596
  * @param opts - Which rule package, folder, and input arguments. `outputVar` defaults
2568
- * to `<id>_businessRuleResponse` — see the note in the implementation for why this one
2569
- * type does not take its default from the spec.
2597
+ * to `<id>_businessRuleResponse`. `releaseKey` and `folderKey` are refused: a v3 rule
2598
+ * is addressed by name.
2570
2599
  * @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
2571
2600
  * @returns This builder, so calls chain.
2572
2601
  */
@@ -3,6 +3,7 @@ import { checkBpmnExpressions, BpmnBuildError } from './bpmn-expr-check.js';
3
3
  import { BPMN_SCHEMA_VERSIONS } from './format-profile.js';
4
4
  import { schemaVersionRefusal } from '../schema-version.js';
5
5
  import { bindingRequiredFields, REGISTRY_GAPS, registryType } from './typed-node.js';
6
+ import { resolveResourceKeyPattern } from './resource-key-pattern.js';
6
7
  import { DATA_SERVICE_ACTIVITY_TYPE, DATA_SERVICE_CONNECTOR_KEY, dataServiceWire } from './data-service.js';
7
8
  import { ownEntry } from '../core/own-entry.js';
8
9
  export { BpmnBuildError } from './bpmn-expr-check.js';
@@ -1213,22 +1214,75 @@ class ScopeBuilder {
1213
1214
  if (typeof value !== 'string' || value.startsWith('='))
1214
1215
  continue;
1215
1216
  const bindingId = `${id}_${field.name}`;
1217
+ // The key a pattern builds from the context values — a `=bindings.<b>` value as
1218
+ // the DECLARED value of `b`, since a key is never evaluated as an expression.
1219
+ // Resolved only for the fields the pattern names, and refused only when the
1220
+ // pattern would otherwise resolve: a pattern that names nothing the context has
1221
+ // (`${metadata.key}`, the live registry's pattern on a job start, whose context
1222
+ // has no `key`) leaves the key to the value, as before. This snapshot carries no
1223
+ // pattern for the job starts at all; `resource-key-pattern.ts` is tested on both.
1224
+ const keyed = this.resourceKeyFromPattern(id, type, field, context);
1216
1225
  this._bindingRegistry.declare({
1217
1226
  id: bindingId,
1218
1227
  name: field.displayName ?? field.name,
1219
1228
  resource: field.bindingInfo?.resource ?? 'process',
1220
1229
  propertyAttribute: field.bindingInfo?.propertyAttribute ?? field.name,
1221
- // A fact the registry omits and the validator enforces — see REGISTRY_GAPS.
1222
1230
  resourceSubType: ownEntry(REGISTRY_GAPS, type)?.resourceSubType,
1223
1231
  default: value,
1224
- resourceKey: value,
1232
+ resourceKey: keyed?.key ?? value,
1225
1233
  });
1234
+ // A declared binding the key was read from shares it when the author left its own
1235
+ // key unset, so the pair names one resource — only when the pattern resolved. One
1236
+ // that already carries a DIFFERENT key (a folder binding two rules share, or an
1237
+ // author's own key) would make this node's pair name two resources: refused, with
1238
+ // the binding to declare instead.
1239
+ if (keyed) {
1240
+ for (const decl of keyed.referenced) {
1241
+ if (decl.resourceKey === undefined)
1242
+ decl.resourceKey = keyed.key;
1243
+ else if (decl.resourceKey !== keyed.key) {
1244
+ const resource = field.bindingInfo?.resource ?? 'process';
1245
+ throw new Error(`${type} "${id}": its ${resource} key "${keyed.key}" is read through =bindings.${decl.id}, whose resource key is already ` +
1246
+ `"${decl.resourceKey}" (another node's pair, or the binding's own), so the pair would name two resources. ` +
1247
+ `A ${resource} binding names one: declare one per node, e.g. ` +
1248
+ `\`.binding('${decl.id}_${id}', { resource: '${decl.resource}', propertyAttribute: '${decl.propertyAttribute}', value: '${decl.default}' })\`.`);
1249
+ }
1250
+ }
1251
+ }
1226
1252
  out[field.name] = `=bindings.${bindingId}`;
1227
1253
  }
1228
1254
  return out;
1229
1255
  }
1256
+ /**
1257
+ * A binding field's resource key from its `resourceKeyPattern`, with the declared
1258
+ * bindings the key was read through — or `undefined` when the pattern does not
1259
+ * resolve from this context (see `resolveResourceKeyPattern`), in which case the
1260
+ * caller keys the binding by the value, as it always has.
1261
+ *
1262
+ * @throws when the pattern resolves except for a `=bindings.<b>` reference that no
1263
+ * `.binding()` before this node declares with a value: that key would otherwise be
1264
+ * built, so the author is told what to declare rather than given a wrong pair.
1265
+ */
1266
+ resourceKeyFromPattern(id, type, field, context) {
1267
+ const pattern = field.bindingInfo?.resourceKeyPattern;
1268
+ const byId = (bindingId) => this._bindingRegistry.bindings.find((b) => b.id === bindingId);
1269
+ const resolved = resolveResourceKeyPattern(pattern, context, byId);
1270
+ if (resolved === undefined)
1271
+ return undefined;
1272
+ if ('blocked' in resolved) {
1273
+ const { field: blockedField, ref } = resolved.blocked;
1274
+ const refId = ref.slice('=bindings.'.length);
1275
+ const resource = field.bindingInfo?.resource ?? 'process';
1276
+ throw new Error(`${type} "${id}": ${blockedField} reads ${ref}, which no .binding() declares with a value before this node, ` +
1277
+ `so the ${resource} key \`${pattern}\` it is paired under cannot be built. Declare ` +
1278
+ `\`.binding('${refId}', { resource: '${resource}', propertyAttribute: '${blockedField}', value: '…' })\` first, or pass the value itself.`);
1279
+ }
1280
+ return { key: resolved.key, referenced: resolved.referenced.map((bid) => byId(bid)).filter((d) => d !== undefined) };
1281
+ }
1230
1282
  /** @internal Shared body of the Orchestrator process-invocation methods. */
1231
- orchestratorNode(id, type, opts, body) {
1283
+ orchestratorNode(id, type, opts, body,
1284
+ /** A member's own departures from the family: the business rule's v3 stamp and `_label`. */
1285
+ own = {}) {
1232
1286
  // The `uipath:type` version selects the engine's addressing branch — see
1233
1287
  // OrchestratorOpts.process. By name → v2 (`name` + `folderPath`); by key → v1
1234
1288
  // (`releaseKey` + `folderKey`). A literal release key becomes a `process`/`Key`
@@ -1254,12 +1308,13 @@ class ScopeBuilder {
1254
1308
  id,
1255
1309
  name: opts.name,
1256
1310
  type,
1257
- typeVersion: byKey ? 'v1' : 'v2',
1311
+ typeVersion: own.typeVersion ?? (byKey ? 'v1' : 'v2'),
1258
1312
  context: this.bindRequiredContext(id, type, {
1259
1313
  name: opts.process,
1260
1314
  ...(opts.folder !== undefined ? { folderPath: opts.folder } : {}),
1261
1315
  ...(releaseKey !== undefined ? { releaseKey } : {}),
1262
1316
  ...(byKey && opts.folderKey !== undefined ? { folderKey: opts.folderKey } : {}),
1317
+ ...(own.context ?? {}),
1263
1318
  }),
1264
1319
  inputs: opts.input ?? {},
1265
1320
  outputs: opts.outputs,
@@ -1355,12 +1410,23 @@ class ScopeBuilder {
1355
1410
  *
1356
1411
  * @remarks
1357
1412
  * **Not a DMN decision table**, despite the element and the label. The registry spec
1358
- * is an Orchestrator process invocation — `releaseKey`/`folderId`/`folderPath`/`name`
1359
- * plus `JobArguments`, `BindingPattern: businessRule` over `Resource: process` —
1360
- * structurally identical to {@link ScopeBuilder.startProcess}. `process` names a
1361
- * package whose Orchestrator process type is `BusinessRules`; whatever decision logic
1362
- * exists lives INSIDE that package, not in the `.bpmn`. So this is the job-start
1363
- * family's sixth member, not a rules subsystem, and it shares every option with it.
1413
+ * is an Orchestrator process invocation — `name`/`folderPath` plus `JobArguments` —
1414
+ * structurally a {@link ScopeBuilder.startProcess}. `process` names a package whose
1415
+ * Orchestrator process type is `BusinessRules`; whatever decision logic exists lives
1416
+ * INSIDE that package, not in the `.bpmn`. So this is the job-start family's sixth
1417
+ * member, not a rules subsystem.
1418
+ *
1419
+ * Where it departs from the family, the registry says so and this follows it:
1420
+ *
1421
+ * - the task is `version="v3"` — below v3 the engine cannot run the `.uirule`
1422
+ * package a BusinessRules project deploys — so a rule is always addressed by
1423
+ * name, and `releaseKey` / `folderKey` are refused;
1424
+ * - `name` and `folderPath` are each bound to a `BusinessRule` resource
1425
+ * (`propertyAttribute` `name` / `folderPath`), both keyed `<folder>.<name>`
1426
+ * (`<name>` alone when the folder is empty), declared on your behalf as
1427
+ * `<id>_name` / `<id>_folderPath`; an unbound `_label` carries the rule's name;
1428
+ * - two output rows: `output` (`=result`, in `outputVar`) and `Error` (`=Error`,
1429
+ * in `<id>_Error`), both `jsonSchema`.
1364
1430
  *
1365
1431
  * Distinct from the Case SDK's `rule()`, which declares DNF lifecycle conditions
1366
1432
  * (`case-entered`, `required-tasks-completed`, …) deciding when a stage enters or
@@ -1380,7 +1446,7 @@ class ScopeBuilder {
1380
1446
  * process: 'DiscountTier',
1381
1447
  * folder: 'Finance',
1382
1448
  * input: { amount: 4200 },
1383
- * outputs: { tier: '=response.tier' },
1449
+ * outputs: { tier: '=result.DiscountTier.tier' },
1384
1450
  * })
1385
1451
  * .endEvent('done')
1386
1452
  * .sequenceFlow('start', 'tier')
@@ -1389,28 +1455,38 @@ class ScopeBuilder {
1389
1455
  * ```
1390
1456
  *
1391
1457
  * @param id - The element id, unique in the process and used by `sequenceFlow`.
1458
+ *
1459
+ * **The result is keyed by decision name.** The platform's `output` row is the whole
1460
+ * result (`source="=result"`) and a decision's column reads as
1461
+ * `=result.<Decision>.<column>` — `vars.<id>_businessRuleResponse.<Decision>.<column>`
1462
+ * downstream — never `=result.<column>` (canvas spec `outputNotes`; `uip rules describe
1463
+ * <name>` lists the decisions and columns, and only a decision-keyed rule, one whose
1464
+ * `OutputArguments` carry `x-uipath-decision-keyed`, fits this row).
1392
1465
  * @param opts - Which rule package, folder, and input arguments. `outputVar` defaults
1393
- * to `<id>_businessRuleResponse` — see the note in the implementation for why this one
1394
- * type does not take its default from the spec.
1466
+ * to `<id>_businessRuleResponse`. `releaseKey` and `folderKey` are refused: a v3 rule
1467
+ * is addressed by name.
1395
1468
  * @param body - Boundary events attached to this activity, declared on the {@link ActivityBuilder} it receives.
1396
1469
  * @returns This builder, so calls chain.
1397
1470
  */
1398
1471
  businessRule(id, opts, body) {
1399
- // The ONE place this type departs from its family, and deliberately.
1400
- //
1401
- // Every other member takes its default output variable from the registry's
1402
- // `OutputName` (`Process response` → `<id>_processResponse`). This type's
1403
- // `OutputName` is literally its designer LABEL, `Execute business rule [Preview]`,
1404
- // so the derived default would be `<id>_executeBusinessRulePreview`: unreadable,
1405
- // and — worse — it encodes the preview marker in a name authors write expressions
1406
- // against. When `[Preview]` drops the label becomes `Execute business rule`, the
1407
- // derived default silently becomes `<id>_executeBusinessRule`, and every
1408
- // `vars.<id>_executeBusinessRulePreview` reference in every authored process starts
1409
- // resolving to undefined — a silent wrong-answer change from a cosmetic spec edit.
1410
- //
1411
- // Pinning it here makes the default independent of the label. The output ROW still
1412
- // carries the registry's own `name`, so the artifact is unchanged.
1413
- return this.orchestratorNode(id, 'Orchestrator.BusinessRules', { ...opts, outputVar: opts.outputVar ?? `${id}_businessRuleResponse` }, body);
1472
+ if (opts.releaseKey !== undefined || opts.folderKey !== undefined) {
1473
+ throw new Error(`businessRule('${id}'): a business rule is addressed by name and folder (version v3 binds the ` +
1474
+ 'BusinessRule resource by `name` + `folderPath`); drop `releaseKey` / `folderKey`.');
1475
+ }
1476
+ // The default output variable is pinned rather than derived. Derived, it would come
1477
+ // from the registry's `OutputName`, which was once the designer LABEL, `Execute
1478
+ // business rule [Preview]` — a `[Preview]` marker in a name authors write expressions
1479
+ // against, and one that changed when the marker dropped. Pinning keeps every
1480
+ // `vars.<id>_businessRuleResponse` reference valid across that kind of spec edit.
1481
+ return this.orchestratorNode(id, 'Orchestrator.BusinessRules',
1482
+ // `folderPath` is a REQUIRED binding of the type: a rule in the running job's own
1483
+ // folder is bound with an empty path, so the pair is keyed by the name alone
1484
+ // (`uip maestro bpmn validate` accepts it); leaving the field out instead drew
1485
+ // BINDING_REQUIRED and EMPTY_REQUIRED_FIELD from `check`.
1486
+ { ...opts, folder: opts.folder ?? '', outputVar: opts.outputVar ?? `${id}_businessRuleResponse` }, body,
1487
+ // `_label` is the one unbound context field: the rule's name, which the canvas
1488
+ // shows while `name` itself is a binding reference.
1489
+ { typeVersion: 'v3', context: { _label: opts.process } });
1414
1490
  }
1415
1491
  /**
1416
1492
  * Add an item to an Orchestrator **queue** (`Orchestrator.CreateQueueItem`, or
@@ -1,4 +1,4 @@
1
- import { typedNodeOutputVar } from './typed-node.js';
1
+ import { typedNodeAdditionalOutputVars, typedNodeOutputVar } from './typed-node.js';
2
2
  /**
3
3
  * The variables a connector task's output rows land in.
4
4
  *
@@ -38,7 +38,7 @@ export function typedNodeDeclaredVars(n) {
38
38
  if (n.outputRows)
39
39
  return n.outputRows.map((r) => r.var).filter((v) => v !== undefined);
40
40
  const v = typedNodeOutputVar(n.id, n.type, n.outputVar, n.spec);
41
- return [...(v ? [v] : []), ...Object.keys(n.outputs ?? {})];
41
+ return [...(v ? [v] : []), ...typedNodeAdditionalOutputVars(n.id, n.type, n.spec), ...Object.keys(n.outputs ?? {})];
42
42
  }
43
43
  /**
44
44
  * Every variable name a node DECLARES by writing to it — the platform counts a
@@ -419,18 +419,32 @@ function outputsOf(payload, _id, type) {
419
419
  const rows = payload?.output ?? [];
420
420
  const extras = {};
421
421
  let outputVar;
422
- const specName = type ? registryType(type).output?.name : undefined;
422
+ const spec = type ? registryType(type) : undefined;
423
+ const specName = spec?.output?.name;
424
+ // A row the registry declares beside the main one (a business rule's `Error`) is
425
+ // re-emitted by the builder, so lifting it as an extra would write it twice on the
426
+ // way back. Matched by NAME (and source): an export that binds it to a variable
427
+ // other than the derived `<id>_<name>` is spelled out verbatim instead
428
+ // (`outputRowsNeedSpellingOut`), which carries that variable through exactly.
429
+ const declaredBeside = new Map((spec?.additionalOutputs ?? []).map((o) => [o.name, o.source ?? undefined]));
423
430
  for (const row of rows) {
424
431
  if (specName !== undefined && row.name === specName) {
425
432
  // Only worth emitting when it differs from the default the builder derives.
426
433
  outputVar = row.var;
427
434
  continue;
428
435
  }
436
+ if (declaredBeside.has(row.name) && (declaredBeside.get(row.name) === undefined || declaredBeside.get(row.name) === row.source))
437
+ continue;
429
438
  if (row.source !== undefined)
430
439
  extras[row.var ?? row.name] = row.source;
431
440
  }
432
441
  return { outputVar, extras };
433
442
  }
443
+ /** The variable the builder derives for each row the registry declares beside the main one. */
444
+ function derivedExtraVars(id, type) {
445
+ const spec = type ? registryType(type) : undefined;
446
+ return new Map((spec?.additionalOutputs ?? []).map((o) => [o.name, `${id}_${o.name}`]));
447
+ }
434
448
  /**
435
449
  * Whether these rows can be reproduced by the compact `outputVar` + `outputs` pair,
436
450
  * or need spelling out.
@@ -445,10 +459,16 @@ function outputsOf(payload, _id, type) {
445
459
  * form stays in use wherever it is exact, so the 30 other extension types decompile
446
460
  * to exactly the source they did before, and only a row that genuinely needs the
447
461
  * long form gets it.
462
+ *
463
+ * A row the registry declares beside the main one (`derivedBeside`: a business rule's
464
+ * `Error`) that an export binds to a variable other than the derived `<id>_<name>`
465
+ * needs it too: the compact form cannot rename that row, and lifting it as an extra
466
+ * would have the builder add its own beside it.
448
467
  */
449
- function outputRowsNeedSpellingOut(payload) {
468
+ function outputRowsNeedSpellingOut(payload, derivedBeside = new Map()) {
450
469
  const rows = payload?.output ?? [];
451
- return rows.some((row) => row.body !== undefined ||
470
+ return rows.some((row) => (derivedBeside.has(row.name) && row.var !== undefined && row.var !== derivedBeside.get(row.name)) ||
471
+ row.body !== undefined ||
452
472
  row.custom !== undefined ||
453
473
  row.description !== undefined ||
454
474
  row.target !== undefined ||
@@ -522,7 +542,7 @@ function emitTypedNode(chain, el, payload, type) {
522
542
  const verbatimContext = contextRowsNeedSpellingOut(payload, type) ? emitContextRows(payload) : undefined;
523
543
  const { outputVar, extras } = outputsOf(payload, id, type);
524
544
  const defaultVar = spec.output ? `${id}_${identifierize(spec.output.name)}` : undefined;
525
- const verbatimRows = outputRowsNeedSpellingOut(payload) ? emitOutputRows(payload) : undefined;
545
+ const verbatimRows = outputRowsNeedSpellingOut(payload, derivedExtraVars(id, type)) ? emitOutputRows(payload) : undefined;
526
546
  chain.add(`.activity(${tsText(id)}, ${tsText(type)}${optionalArg(opts([
527
547
  ['name', el.name !== undefined ? tsText(el.name) : undefined],
528
548
  // Only when it differs from the registry's own template, so an
@@ -7,7 +7,10 @@
7
7
  ],
8
8
  "provenance": {
9
9
  "typeCount": 30,
10
- "uipVersion": "1.203.0-preview.180"
10
+ "uipVersion": "1.203.0-preview.180",
11
+ "refreshed": {
12
+ "Orchestrator.BusinessRules": "1.205.0-preview.256"
13
+ }
11
14
  },
12
15
  "types": {
13
16
  "A2A.AgentExecution": {
@@ -1277,40 +1280,35 @@
1277
1280
  "inputTarget": "bodyField",
1278
1281
  "context": [
1279
1282
  {
1280
- "name": "releaseKey",
1281
- "displayName": "Process",
1283
+ "name": "name",
1284
+ "displayName": "Business rule",
1282
1285
  "type": "string",
1283
- "required": false,
1286
+ "required": true,
1284
1287
  "hidden": true,
1285
- "binding": false,
1288
+ "binding": true,
1286
1289
  "default": null,
1287
1290
  "bindingInfo": {
1288
- "resource": "process",
1289
- "propertyAttribute": "Key"
1291
+ "resource": "BusinessRule",
1292
+ "propertyAttribute": "name",
1293
+ "resourceKeyPattern": "${metadata.folderPath}.${metadata.name}"
1290
1294
  }
1291
1295
  },
1292
- {
1293
- "name": "folderId",
1294
- "displayName": null,
1295
- "type": "string",
1296
- "required": false,
1297
- "hidden": true,
1298
- "binding": false,
1299
- "default": null,
1300
- "bindingInfo": null
1301
- },
1302
1296
  {
1303
1297
  "name": "folderPath",
1304
1298
  "displayName": null,
1305
1299
  "type": "string",
1306
- "required": false,
1300
+ "required": true,
1307
1301
  "hidden": true,
1308
- "binding": false,
1302
+ "binding": true,
1309
1303
  "default": null,
1310
- "bindingInfo": null
1304
+ "bindingInfo": {
1305
+ "resource": "BusinessRule",
1306
+ "propertyAttribute": "folderPath",
1307
+ "resourceKeyPattern": "${metadata.folderPath}.${metadata.name}"
1308
+ }
1311
1309
  },
1312
1310
  {
1313
- "name": "name",
1311
+ "name": "_label",
1314
1312
  "displayName": null,
1315
1313
  "type": "string",
1316
1314
  "required": false,
@@ -1322,11 +1320,19 @@
1322
1320
  ],
1323
1321
  "inputs": [],
1324
1322
  "output": {
1325
- "name": "Execute business rule [Preview]",
1326
- "type": "Orchestrator.BusinessRules",
1327
- "source": null,
1323
+ "name": "output",
1324
+ "type": "jsonSchema",
1325
+ "source": "=result",
1328
1326
  "relative": true
1329
1327
  },
1328
+ "additionalOutputs": [
1329
+ {
1330
+ "name": "Error",
1331
+ "type": "jsonSchema",
1332
+ "source": "=Error",
1333
+ "relative": true
1334
+ }
1335
+ ],
1330
1336
  "eventDefinition": null,
1331
1337
  "requiresDiscovery": true,
1332
1338
  "bindingPattern": "businessRule"
@@ -0,0 +1,38 @@
1
+ /**
2
+ * A binding field's resource key from the registry's `resourceKeyPattern`
3
+ * (`${metadata.folderPath}.${metadata.name}` on a business rule), resolved from the
4
+ * node's context values. Pure: the builder supplies the declared bindings a
5
+ * `=bindings.<b>` reference is read through.
6
+ */
7
+ /**
8
+ * What a pattern resolves to from one node's context: the key and the ids of the
9
+ * declared bindings it was read through; `blocked` when it would resolve except for a
10
+ * `=bindings.<b>` reference no declared binding supplies a value for (the author is
11
+ * told what to declare rather than given a wrong pair); `undefined` when the pattern
12
+ * does not apply to this context at all, so the caller keys the binding by the value,
13
+ * as it always has.
14
+ */
15
+ export type ResourceKeyResolution = {
16
+ key: string;
17
+ referenced: string[];
18
+ } | {
19
+ blocked: {
20
+ field: string;
21
+ ref: string;
22
+ };
23
+ } | undefined;
24
+ /**
25
+ * Resolve `pattern` from `context`. A placeholder that is not `metadata.<field>`, a
26
+ * context field that is absent or not a string, or an `=` expression that is not a
27
+ * `=bindings.<b>` reference each make the pattern inapplicable (`undefined`) — so
28
+ * `${metadata.key}` on a job start, whose context has no `key`, leaves the key to the
29
+ * value. A `=bindings.<b>` value contributes the DECLARED value of `b`, never the
30
+ * expression text: a key is never evaluated.
31
+ *
32
+ * @param pattern - The field's `resourceKeyPattern`, or `undefined` for none.
33
+ * @param context - The node's context values as the author gave them.
34
+ * @param lookup - The declared binding for an id, with its `default` value, or `undefined`.
35
+ */
36
+ export declare function resolveResourceKeyPattern(pattern: string | undefined, context: Record<string, unknown>, lookup: (bindingId: string) => {
37
+ default?: unknown;
38
+ } | undefined): ResourceKeyResolution;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * A binding field's resource key from the registry's `resourceKeyPattern`
3
+ * (`${metadata.folderPath}.${metadata.name}` on a business rule), resolved from the
4
+ * node's context values. Pure: the builder supplies the declared bindings a
5
+ * `=bindings.<b>` reference is read through.
6
+ */
7
+ /**
8
+ * Resolve `pattern` from `context`. A placeholder that is not `metadata.<field>`, a
9
+ * context field that is absent or not a string, or an `=` expression that is not a
10
+ * `=bindings.<b>` reference each make the pattern inapplicable (`undefined`) — so
11
+ * `${metadata.key}` on a job start, whose context has no `key`, leaves the key to the
12
+ * value. A `=bindings.<b>` value contributes the DECLARED value of `b`, never the
13
+ * expression text: a key is never evaluated.
14
+ *
15
+ * @param pattern - The field's `resourceKeyPattern`, or `undefined` for none.
16
+ * @param context - The node's context values as the author gave them.
17
+ * @param lookup - The declared binding for an id, with its `default` value, or `undefined`.
18
+ */
19
+ export function resolveResourceKeyPattern(pattern, context, lookup) {
20
+ if (pattern === undefined)
21
+ return undefined;
22
+ const placeholders = [...pattern.matchAll(/\$\{([\w.]+)\}/g)].map((m) => m[1]);
23
+ const values = new Map();
24
+ const referenced = [];
25
+ for (const name of placeholders) {
26
+ if (!name.startsWith('metadata.'))
27
+ return undefined;
28
+ const contextField = name.slice('metadata.'.length);
29
+ const v = context[contextField];
30
+ if (typeof v !== 'string')
31
+ return undefined;
32
+ if (!v.startsWith('=')) {
33
+ values.set(name, v);
34
+ continue;
35
+ }
36
+ if (!v.startsWith('=bindings.'))
37
+ return undefined;
38
+ const refId = v.slice('=bindings.'.length);
39
+ const decl = lookup(refId);
40
+ if (decl === undefined || typeof decl.default !== 'string')
41
+ return { blocked: { field: contextField, ref: v } };
42
+ values.set(name, decl.default);
43
+ referenced.push(refId);
44
+ }
45
+ const key = pattern.replace(/\$\{([\w.]+)\}/g, (_m, name) => values.get(name) ?? '');
46
+ return { key: key.startsWith('.') ? key.slice(1) : key, referenced };
47
+ }
@@ -36,6 +36,13 @@ export interface RegistryContextField {
36
36
  bindingInfo: {
37
37
  resource: string | null;
38
38
  propertyAttribute: string | null;
39
+ /**
40
+ * How the binding's `resourceKey` is built from the node's context values, e.g.
41
+ * `${metadata.folderPath}.${metadata.name}` for a business rule. Absent on
42
+ * entries materialized before the field was carried; the emitter then keys the
43
+ * binding by the bound value itself, as it always did.
44
+ */
45
+ resourceKeyPattern?: string;
39
46
  } | null;
40
47
  }
41
48
  /**
@@ -88,6 +95,11 @@ export interface RegistryTypeSpec {
88
95
  /** The payload fields the registry declares; empty for types with dynamic inputs. */
89
96
  inputs: RegistryInputField[];
90
97
  output: RegistryOutput | null;
98
+ /**
99
+ * Output rows the type writes besides `output` — a business rule's `Error` row
100
+ * (`source="=Error"`). Each lands in `<id>_<name>`. Absent when the type has none.
101
+ */
102
+ additionalOutputs?: RegistryOutput[];
91
103
  /** e.g. `bpmn:messageEventDefinition` — what makes a message event a message event. */
92
104
  eventDefinition: string | null;
93
105
  /** True when authoring needs a tenant read (a connection, a process, a queue). */
@@ -347,6 +359,11 @@ export interface TypedNodeInput {
347
359
  /** The type's emission spec, supplied inline — see `ActivityNodeOpts.spec`. */
348
360
  spec?: RegistryTypeSpec;
349
361
  }
362
+ /**
363
+ * The variables a typed node's ADDITIONAL output rows write (`<id>_<name>`), in
364
+ * registry order — a business rule's `<id>_Error`. Empty for every type without them.
365
+ */
366
+ export declare function typedNodeAdditionalOutputVars(id: string, type: string, inline?: RegistryTypeSpec): string[];
350
367
  /** The variable a typed node's output row writes, or `undefined` when it has none. */
351
368
  export declare function typedNodeOutputVar(id: string, type: string, outputVar?: string, inline?: RegistryTypeSpec): string | undefined;
352
369
  /** `uipath:input` payload rows exactly as the caller spelled them, deriving nothing. */
@@ -191,6 +191,15 @@ function identifierize(name) {
191
191
  .map((w, i) => (i === 0 ? w.charAt(0).toLowerCase() + w.slice(1) : w.charAt(0).toUpperCase() + w.slice(1)))
192
192
  .join('');
193
193
  }
194
+ /**
195
+ * The variables a typed node's ADDITIONAL output rows write (`<id>_<name>`), in
196
+ * registry order — a business rule's `<id>_Error`. Empty for every type without them.
197
+ */
198
+ export function typedNodeAdditionalOutputVars(id, type, inline) {
199
+ // The row's name as it is, not identifierized: `Error` → `<id>_Error`, the variable a
200
+ // connector task's error row has always landed in.
201
+ return (registryType(type, inline).additionalOutputs ?? []).map((o) => `${id}_${o.name}`);
202
+ }
194
203
  /** The variable a typed node's output row writes, or `undefined` when it has none. */
195
204
  export function typedNodeOutputVar(id, type, outputVar, inline) {
196
205
  const spec = registryType(type, inline);
@@ -536,6 +545,16 @@ varType) {
536
545
  const out = outVar ? outputRow(m, spec, outVar) : undefined;
537
546
  if (out)
538
547
  rows.push(out);
548
+ // The rows the registry declares beside the main one, in its order and at the
549
+ // default variable each — a business rule's `Error` row. They sit between the
550
+ // type's own row and the author's, which is where every real export puts them.
551
+ const extraVars = typedNodeAdditionalOutputVars(id, type, spec);
552
+ (spec.additionalOutputs ?? []).forEach((o, i) => {
553
+ const props = { name: o.name, type: o.type, var: extraVars[i] };
554
+ if (o.source)
555
+ props.source = o.source;
556
+ rows.push(m.create('uipath:Output', props));
557
+ });
539
558
  for (const [varId, source] of Object.entries(input.outputs ?? {})) {
540
559
  // Type the row from the variable it writes, not `'string'` unconditionally.
541
560
  //