@uipath/maestro-builder-sdk 6.6.0 → 6.7.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.
package/dist/decompile.js CHANGED
@@ -182,6 +182,8 @@ const T = {
182
182
  loop: 'core.logic.loop',
183
183
  doWhile: 'core.logic.dowhile',
184
184
  dataFabricRead: 'core.datafabric.read',
185
+ dataFabricCreate: 'core.datafabric.create',
186
+ dataFabricDelete: 'core.datafabric.delete',
185
187
  documentClassify: 'uipath.document.classify',
186
188
  dynamicExtract: 'uipath.ixp.extract-document-builder',
187
189
  dataFabricUpdate: 'core.datafabric.update',
@@ -497,31 +499,93 @@ function emitStepSpec(node, imp, inputNames, o, graph) {
497
499
  ])})`;
498
500
  }
499
501
  case T.dataFabricRead:
500
- case T.dataFabricUpdate: {
502
+ case T.dataFabricCreate:
503
+ case T.dataFabricUpdate:
504
+ case T.dataFabricDelete: {
501
505
  const cfg = (inputs.entityConfig ?? {});
502
506
  const entity = str(String(cfg.entityName ?? ''));
503
507
  const folder = typeof cfg._folderKey === 'string' ? str(cfg._folderKey) : undefined;
508
+ const resourceKey = typeof cfg._resourceKey === 'string' ? str(cfg._resourceKey) : undefined;
504
509
  const val = (v) => {
510
+ // 1.3 let `recordId` and a create's field value be a `{ type:
511
+ // 'jsExpression', expression }` object as well as a `=js:` string. A
512
+ // product-authored node can carry either; both recover as an expression.
513
+ const obj = v;
514
+ if (obj && typeof obj === 'object' && obj.type === 'jsExpression') {
515
+ return emitBareExpr(String(obj.expression ?? ''), imp, inputNames);
516
+ }
505
517
  const raw = typeof v === 'string' ? v : String(v ?? '');
506
518
  return raw.startsWith('=js:') ? emitBareExpr(raw.slice(4), imp, inputNames) : str(raw);
507
519
  };
520
+ const fieldObject = (rows) => {
521
+ const list = Array.isArray(rows) ? rows : [];
522
+ return `{ ${list.map((r) => `${key(String(r.field ?? ''))}: ${val(r.value)}`).join(', ')} }`;
523
+ };
524
+ const record = cfg.recordSource === 'fromRead'
525
+ ? `{ fromRead: ${str(String(cfg.readEntityNodeId ?? ''))} }`
526
+ : `{ byId: ${val(cfg.recordId)} }`;
508
527
  if (node.type === T.dataFabricRead) {
509
- const rows = Array.isArray(cfg._filters) ? cfg._filters : [];
510
- const filterRow = (r) => opts([
528
+ // The array is what the platform serializer reads and what compile
529
+ // writes; the grouped object is the newer authoring shape, tolerated
530
+ // here so a canvas-authored file still decompiles. Both flatten to the
531
+ // builder's row list, where `or: true` marks each new OR alternative.
532
+ const filterRow = (r, or = false) => opts([
511
533
  ['field', str(String(r.field ?? ''))],
512
- ['operator', r.operator !== '=' ? str(String(r.operator ?? '')) : undefined],
534
+ ['operator', r.operator !== undefined && r.operator !== '=' ? str(String(r.operator)) : undefined],
513
535
  ['value', val(r.value)],
514
- ['or', r['join'] === 'OR' ? 'true' : undefined],
536
+ ['or', or ? 'true' : undefined],
515
537
  ]);
516
- const filters = rows.length ? `[${rows.map(filterRow).join(', ')}]` : undefined;
517
- return `${imp.use('dataFabricRead')}(${opts([['entity', entity], ['filters', filters], ['folderKey', folder]])})`;
538
+ const q = cfg._filters;
539
+ let rows = [];
540
+ if (Array.isArray(q)) {
541
+ rows = q.map((r, i) => filterRow(r, i > 0 && r['join'] === 'OR'));
542
+ }
543
+ else if (q && typeof q === 'object') {
544
+ // Grouped: each root row and each group is one alternative, so every
545
+ // one but the first opens with `or: true`.
546
+ const alts = [
547
+ ...(Array.isArray(q.rows) ? q.rows.map((r) => [r]) : []),
548
+ ...(Array.isArray(q.groups) ? q.groups : [])
549
+ .map((g) => (Array.isArray(g.rows) ? g.rows : [])),
550
+ ].filter((a) => a.length);
551
+ rows = alts.flatMap((alt, ai) => alt.map((r, ri) => filterRow(r, ai > 0 && ri === 0)));
552
+ }
553
+ const filters = rows.length ? `[${rows.join(', ')}]` : undefined;
554
+ // `resultMode` only exists from 1.4, and recovering it re-selects 1.4 on
555
+ // the way back out — so a 1.0 node stays a 1.0 node through the round trip.
556
+ const mode = cfg.resultMode === 'single' || cfg.resultMode === 'multiple'
557
+ ? str(cfg.resultMode) : undefined;
558
+ const sort = cfg._sort && typeof cfg._sort === 'object'
559
+ ? opts([
560
+ ['field', str(String(cfg._sort.field ?? ''))],
561
+ ['direction', cfg._sort.direction === 'desc' ? str('desc') : undefined],
562
+ ])
563
+ : undefined;
564
+ const fields = Array.isArray(cfg._selectedFieldNames) && cfg._selectedFieldNames.length
565
+ ? `[${cfg._selectedFieldNames.map((f) => str(String(f))).join(', ')}]`
566
+ : undefined;
567
+ return `${imp.use('dataFabricRead')}(${opts([
568
+ ['entity', entity], ['filters', filters], ['resultMode', mode],
569
+ ['limit', typeof cfg._recordLimit === 'number' ? String(cfg._recordLimit) : undefined],
570
+ ['skip', typeof cfg._skip === 'number' ? String(cfg._skip) : undefined],
571
+ ['sort', sort], ['fields', fields],
572
+ ['folderKey', folder], ['resourceKey', resourceKey],
573
+ ])})`;
518
574
  }
519
- const record = cfg.recordSource === 'fromRead'
520
- ? `{ fromRead: ${str(String(cfg.readEntityNodeId ?? ''))} }`
521
- : `{ byId: ${val(cfg.recordId)} }`;
522
- const updates = Array.isArray(cfg.fieldUpdates) ? cfg.fieldUpdates : [];
523
- const set = `{ ${updates.map((u) => `${key(String(u.field ?? ''))}: ${val(u.value)}`).join(', ')} }`;
524
- return `${imp.use('dataFabricUpdate')}(${opts([['entity', entity], ['record', record], ['set', set], ['folderKey', folder]])})`;
575
+ if (node.type === T.dataFabricCreate) {
576
+ return `${imp.use('dataFabricCreate')}(${opts([
577
+ ['entity', entity], ['values', fieldObject(cfg.fieldValues)], ['folderKey', folder], ['resourceKey', resourceKey],
578
+ ])})`;
579
+ }
580
+ if (node.type === T.dataFabricDelete) {
581
+ return `${imp.use('dataFabricDelete')}(${opts([
582
+ ['entity', entity], ['record', record], ['folderKey', folder], ['resourceKey', resourceKey],
583
+ ])})`;
584
+ }
585
+ return `${imp.use('dataFabricUpdate')}(${opts([
586
+ ['entity', entity], ['record', record], ['set', fieldObject(cfg.fieldUpdates)],
587
+ ['folderKey', folder], ['resourceKey', resourceKey],
588
+ ])})`;
525
589
  }
526
590
  case T.createOutgoingCall:
527
591
  case T.endCall: {
package/dist/serialize.js CHANGED
@@ -83,7 +83,9 @@ const NODE_TYPE = {
83
83
  loop: 'core.logic.loop',
84
84
  doWhile: 'core.logic.dowhile',
85
85
  dataFabricRead: 'core.datafabric.read',
86
+ dataFabricCreate: 'core.datafabric.create',
86
87
  dataFabricUpdate: 'core.datafabric.update',
88
+ dataFabricDelete: 'core.datafabric.delete',
87
89
  documentClassify: 'uipath.document.classify',
88
90
  dynamicExtract: 'uipath.ixp.extract-document-builder',
89
91
  subflow: 'core.subflow',
@@ -2375,6 +2377,93 @@ function scheduledVersion(inputs, explicit) {
2375
2377
  }
2376
2378
  return cron ? '1.2' : undefined;
2377
2379
  }
2380
+ /**
2381
+ * Lower filter rows the way the platform's own serializer reads them.
2382
+ *
2383
+ * `_filters` is an ARRAY, and the join is a `join: 'OR'` key on the row that
2384
+ * STARTS a new alternative — `entity-vdo.ts`'s `buildQesFromEntityConfig`
2385
+ * splits the array into runs on that key, ANDs within a run, and ORs the runs
2386
+ * together. So the query model is disjunctive: `alt1 OR alt2 OR …`, each `alt`
2387
+ * an AND of its rows, which is why `[A, B(or), C]` is `A OR (B AND C)` and not
2388
+ * `(A OR B) AND C`.
2389
+ *
2390
+ * The grouped `{ logicalOperator, rows, groups }` object the authoring guide
2391
+ * shows is a NEWER shape: the vendored converter the local engine runs throws
2392
+ * `(filters ?? []).filter is not a function` on it, so emitting it would trade
2393
+ * a working offline rung for a shape only the current platform can read. The
2394
+ * array is the form both accept.
2395
+ */
2396
+ function dataFabricFilterRows(filters, render) {
2397
+ return filters.map((f, i) => ({
2398
+ field: f.field,
2399
+ operator: f.operator ?? '=',
2400
+ value: render(f.value),
2401
+ // Never on the first row: a leading `join` starts an empty run, which the
2402
+ // platform's own loop guards against, and which means nothing anyway.
2403
+ ...(i > 0 && f.or ? { join: 'OR' } : {}),
2404
+ }));
2405
+ }
2406
+ /**
2407
+ * The two `bindings[]` rows a FOLDER-SCOPED entity needs to survive export to
2408
+ * another org, and the `_resourceKey` that ties them to the node.
2409
+ *
2410
+ * Nothing else in this file emits `resource: 'Entity'`: the kind is matched by
2411
+ * `(resourceKey, propertyAttribute)` rather than by name, carries no
2412
+ * `resourceSubType`, spells the folder attribute `folderKey` (never
2413
+ * `folderPath` — a folderPath row receives the Orchestrator FQN, which the
2414
+ * Data Fabric query rejects even in the source org), and puts a DISPLAY label
2415
+ * in `name`. The capital `E` is load-bearing: packaging ignores a lowercase
2416
+ * row, so deploy emits no override at all.
2417
+ *
2418
+ * A tenant-scoped entity needs none of this — it serializes as a dotted
2419
+ * literal — which is why the pair is emitted only when the author supplied
2420
+ * both `folderKey` and `resourceKey`. `check` refuses the half-authored case,
2421
+ * because `_folderKey` with no binding falls back to a source-org literal that
2422
+ * looks portable and breaks on deploy.
2423
+ */
2424
+ function entityBindings(entity, folderKey, resourceKey) {
2425
+ const base = `b${entity.replace(/[^A-Za-z0-9]/g, '')}Entity`;
2426
+ return [
2427
+ {
2428
+ id: `${base}Name`,
2429
+ name: entity,
2430
+ type: 'string',
2431
+ resource: 'Entity',
2432
+ resourceKey,
2433
+ default: entity,
2434
+ propertyAttribute: 'name',
2435
+ },
2436
+ {
2437
+ id: `${base}Folder`,
2438
+ name: `${entity}Folder`,
2439
+ type: 'string',
2440
+ resource: 'Entity',
2441
+ resourceKey,
2442
+ default: folderKey,
2443
+ propertyAttribute: 'folderKey',
2444
+ },
2445
+ ];
2446
+ }
2447
+ /**
2448
+ * Which `core.datafabric.read` definition a step needs.
2449
+ *
2450
+ * The family's read node grew from "Read entity" into "Query entity records" at
2451
+ * 1.4, and `entityConfig.resultMode` is the input that expresses the difference.
2452
+ * The SDK keeps 1.0 pinned as the default, the same way every other family here
2453
+ * keeps its older default — a flow that reads one record must not change
2454
+ * version because the platform moved. So `resultMode` SELECTS 1.4, exactly as a
2455
+ * Quartz `every` selects the scheduled trigger's 1.2 (see `scheduledVersion`).
2456
+ */
2457
+ function dataFabricReadVersion(inputs, explicit) {
2458
+ if (explicit !== undefined) {
2459
+ if (inputs?.resultMode !== undefined && explicit === '1.0') {
2460
+ throw new Error("dataFabricRead(): { resultMode } needs the node's 1.4 definition, but { version: '1.0' } was "
2461
+ + 'requested. Drop the version, request 1.4, or drop resultMode.');
2462
+ }
2463
+ return explicit;
2464
+ }
2465
+ return inputs?.resultMode !== undefined ? '1.4' : undefined;
2466
+ }
2378
2467
  /**
2379
2468
  * The form-trigger schema: one field per flow input, the same derivation the
2380
2469
  * designer's form editor applies to a flow's arguments (`argument-fields.ts` /
@@ -3691,6 +3780,15 @@ function stepNamesOf(steps, out = []) {
3691
3780
  return out;
3692
3781
  }
3693
3782
  export function serialize(built, opts = {}) {
3783
+ /**
3784
+ * The `definitions[]` a flow carries, keyed by `type@version` — NOT by type.
3785
+ * One flow can legitimately hold two versions of the same family (a plain
3786
+ * `.loop()` at 1.0.0 beside a parallel one at 2.4; a `dataFabricRead()` at 1.0
3787
+ * beside a `resultMode` query at 1.4), and a type-keyed map emitted only the
3788
+ * last of them: every other node of that family then pinned a `typeVersion`
3789
+ * with no matching definition, which `uip maestro flow validate` refuses:
3790
+ * "Node type `core.datafabric.read:1.4` has no matching definition".
3791
+ */
3694
3792
  const emittedDefs = new Map();
3695
3793
  /**
3696
3794
  * RESOURCE bindings collected while emitting, keyed by binding id — `resource:
@@ -3774,7 +3872,7 @@ export function serialize(built, opts = {}) {
3774
3872
  def = exact;
3775
3873
  }
3776
3874
  if (def)
3777
- emittedDefs.set(type, def);
3875
+ emittedDefs.set(`${type}@${def.version}`, def);
3778
3876
  const node = {
3779
3877
  id,
3780
3878
  type,
@@ -5102,39 +5200,74 @@ export function serialize(built, opts = {}) {
5102
5200
  addEdge(scope, t, id);
5103
5201
  return [{ node: id, port: successPort(type) }];
5104
5202
  }
5105
- if (spec.kind === 'dataFabricRead' || spec.kind === 'dataFabricUpdate') {
5106
- const type = spec.kind === 'dataFabricRead' ? NODE_TYPE.dataFabricRead : NODE_TYPE.dataFabricUpdate;
5203
+ if (spec.kind === 'dataFabricRead' || spec.kind === 'dataFabricCreate'
5204
+ || spec.kind === 'dataFabricUpdate' || spec.kind === 'dataFabricDelete') {
5205
+ const type = NODE_TYPE[spec.kind];
5107
5206
  const id = stepUid(scope, step.name);
5108
- const node = makeNode(type, id, step.name, { parentId, requestedVersion: step.options?.version });
5207
+ const node = makeNode(type, id, step.name, {
5208
+ parentId,
5209
+ requestedVersion: spec.kind === 'dataFabricRead'
5210
+ ? dataFabricReadVersion(spec.inputs, step.options?.version)
5211
+ : step.options?.version,
5212
+ });
5109
5213
  const cfg = { entityName: spec.inputs.entity };
5110
- if (spec.kind === 'dataFabricRead') {
5111
- if (spec.inputs.filters?.length) {
5112
- cfg._filters = spec.inputs.filters.map((f, i) => ({
5113
- field: f.field,
5114
- operator: f.operator ?? '=',
5115
- value: String(renderValue(f.value, scope.rename)),
5116
- ...(i > 0 && f.or ? { join: 'OR' } : {}),
5117
- }));
5118
- }
5119
- }
5120
- else {
5121
- const rec = spec.inputs.record;
5122
- if (typeof rec.fromRead === 'string') {
5214
+ // `recordSource` is how update and delete both name their target; the
5215
+ // read step's emitted node id is rename-aware, for subflow copies.
5216
+ const targetRecord = (record) => {
5217
+ if (typeof record.fromRead === 'string') {
5123
5218
  cfg.recordSource = 'fromRead';
5124
- // The read step's emitted node id (rename-aware for subflow copies).
5125
- cfg.readEntityNodeId = scope.stepNodeId.get(rec.fromRead) ?? rec.fromRead;
5219
+ cfg.readEntityNodeId = scope.stepNodeId.get(record.fromRead) ?? record.fromRead;
5126
5220
  }
5127
5221
  else {
5128
5222
  cfg.recordSource = 'byId';
5129
- cfg.recordId = String(renderValue(rec.byId, scope.rename));
5223
+ cfg.recordId = String(renderValue(record.byId, scope.rename));
5130
5224
  }
5131
- cfg.fieldUpdates = Object.entries(spec.inputs.set ?? {}).map(([field, value]) => ({
5132
- field,
5133
- value: String(renderValue(value, scope.rename)),
5134
- }));
5225
+ };
5226
+ const fieldRows = (values) => Object.entries(values ?? {}).map(([field, value]) => ({
5227
+ field,
5228
+ value: String(renderValue(value, scope.rename)),
5229
+ }));
5230
+ if (spec.kind === 'dataFabricRead') {
5231
+ // Only 1.4 declares it, and only 1.4 is emitted when it is set (see
5232
+ // dataFabricReadVersion) — so writing it can never land on a 1.0 node,
5233
+ // where it would be an input the definition does not know.
5234
+ if (spec.inputs.resultMode !== undefined)
5235
+ cfg.resultMode = spec.inputs.resultMode;
5236
+ if (spec.inputs.limit !== undefined)
5237
+ cfg._recordLimit = spec.inputs.limit;
5238
+ if (spec.inputs.skip !== undefined)
5239
+ cfg._skip = spec.inputs.skip;
5240
+ if (spec.inputs.sort !== undefined) {
5241
+ cfg._sort = { field: spec.inputs.sort.field, direction: spec.inputs.sort.direction ?? 'asc' };
5242
+ }
5243
+ if (spec.inputs.fields?.length)
5244
+ cfg._selectedFieldNames = [...spec.inputs.fields];
5245
+ if (spec.inputs.filters?.length) {
5246
+ cfg._filters = dataFabricFilterRows(spec.inputs.filters, (value) => String(renderValue(value, scope.rename)));
5247
+ }
5248
+ }
5249
+ else if (spec.kind === 'dataFabricCreate') {
5250
+ cfg.fieldValues = fieldRows(spec.inputs.values);
5251
+ }
5252
+ else if (spec.kind === 'dataFabricUpdate') {
5253
+ targetRecord(spec.inputs.record);
5254
+ cfg.fieldUpdates = fieldRows(spec.inputs.set);
5255
+ }
5256
+ else {
5257
+ targetRecord(spec.inputs.record);
5135
5258
  }
5136
5259
  if (spec.inputs.folderKey !== undefined)
5137
5260
  cfg._folderKey = spec.inputs.folderKey;
5261
+ // A folder-scoped entity is portable only with its `Entity` bindings; the
5262
+ // half-authored form is refused by check(), so reaching here with both
5263
+ // means emit both. Same id for the same (entity, resourceKey), so two
5264
+ // steps on one entity share one pair rather than duplicating it.
5265
+ if (spec.inputs.folderKey !== undefined && spec.inputs.resourceKey !== undefined) {
5266
+ cfg._resourceKey = spec.inputs.resourceKey;
5267
+ for (const entry of entityBindings(spec.inputs.entity, spec.inputs.folderKey, spec.inputs.resourceKey)) {
5268
+ resourceBindings.set(entry.id, entry);
5269
+ }
5270
+ }
5138
5271
  node.inputs = { entityConfig: cfg };
5139
5272
  scope.nodes.push(node);
5140
5273
  for (const t of tails)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.6.0",
3
+ "version": "6.7.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",
@@ -87,5 +87,5 @@
87
87
  "@types/node": "^22.7.0",
88
88
  "esbuild": "^0.28.1"
89
89
  },
90
- "gitref": "fde2e4ef67894bab9ddb61be204f4d0775c08ea1"
90
+ "gitref": "598c3b8dd2e0f063680d817fee10b2a6dd21ec6a"
91
91
  }