@uipath/maestro-builder-sdk 6.20.0 → 6.20.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.
Files changed (2) hide show
  1. package/dist/decompile.js +105 -19
  2. package/package.json +2 -2
package/dist/decompile.js CHANGED
@@ -2227,6 +2227,83 @@ function sweepDroppedEdges(graph, skip) {
2227
2227
  }
2228
2228
  return lines;
2229
2229
  }
2230
+ /** Every out-edge of `id` on port `port`, in edge order. */
2231
+ function portEdges(graph, id, port) {
2232
+ return graph.outEdges(id).filter((e) => e.sourcePort === port);
2233
+ }
2234
+ /** `id`'s successor edges whose target is inside `scope` (the edges `scopeSucc` follows). */
2235
+ function scopeSuccEdges(graph, id, scope) {
2236
+ return graph.successorEdges(id).filter((e) => e.targetPort !== 'loopBack' && scope.has(e.targetNodeId));
2237
+ }
2238
+ /**
2239
+ * The nearest node that post-dominates EVERY one of `targets` — where the paths
2240
+ * that start at them all meet again — or `undefined` when they only meet at the
2241
+ * scope exit. Walks each target's `ipdom` chain (the target itself first).
2242
+ */
2243
+ function commonPostDominator(targets, ipdom) {
2244
+ const chain = (t) => {
2245
+ const out = [];
2246
+ for (let n = t; n !== undefined && n !== EXIT && !out.includes(n); n = ipdom.get(n))
2247
+ out.push(n);
2248
+ return out;
2249
+ };
2250
+ const [head, ...rest] = targets.map(chain);
2251
+ const others = rest.map((c) => new Set(c));
2252
+ return head?.find((n) => others.every((c) => c.has(n)));
2253
+ }
2254
+ /**
2255
+ * The region that starts with `edges`, the edges ONE source port (or a
2256
+ * trigger) has.
2257
+ *
2258
+ * One edge is an ordinary region (`edgeRegion`). Two or more are a fork, which
2259
+ * is what serialize emits for a `.parallel(...)` that is the first thing in a
2260
+ * flow, a subflow, an entry point's prefix or a branch / switch arm: the trigger
2261
+ * (or the arm's port) gets one edge per parallel arm, and the arms meet at the
2262
+ * parallel's merge node. When the targets meet at a merge node the fork
2263
+ * decompiles to that `.parallel(...)`, then the region continues from the
2264
+ * merge's output (flow-builder-sdk#1155).
2265
+ *
2266
+ * Every edge in `edges` is accounted (edge accounting, #1125): the fold
2267
+ * recreates each one. A fork whose arms never meet at a merge node has no
2268
+ * builder spelling. Like the same shape mid-flow (`region`), it throws under
2269
+ * `bestEffort: false`, and otherwise keeps the first arm behind a
2270
+ * `TODO: unstructured fan-out` marker, which is what reports the other edges.
2271
+ */
2272
+ function forkRegion(edges, stop, scope, ipdom, ctx, refInto) {
2273
+ if (edges.length <= 1)
2274
+ return edgeRegion(edges[0], stop, scope, ipdom, ctx, refInto);
2275
+ const targets = edges.map((e) => e.targetNodeId);
2276
+ const join = commonPostDominator(targets, ipdom);
2277
+ const joinNode = join !== undefined ? ctx.graph.byId.get(join) : undefined;
2278
+ if (join === undefined || join === stop || joinNode?.type !== T.merge) {
2279
+ if (ctx.o.bestEffort === false)
2280
+ throw new Error(`decompile: unstructured fan-out (${edges.length} edges).`);
2281
+ for (const e of edges)
2282
+ ctx.graph.accounted.add(e);
2283
+ return [
2284
+ `/* TODO: unstructured fan-out (${commentText(edges.length)} edges) */`,
2285
+ ...region(targets[0], stop, scope, ipdom, ctx, refInto),
2286
+ ];
2287
+ }
2288
+ for (const e of edges)
2289
+ ctx.graph.accounted.add(e);
2290
+ const fork = parallelSeg(join, joinNode, targets, scope, ipdom, ctx, refInto);
2291
+ if (fork.next.brk || fork.next.cont)
2292
+ return [fork.seg, fork.next.brk ? '.break()' : '.continue()'];
2293
+ return [fork.seg, ...region(fork.next.cursor, stop, scope, ipdom, ctx, refInto)];
2294
+ }
2295
+ /**
2296
+ * The `.parallel(join, [arms])` segment for a fork whose `targets` (one per
2297
+ * edge; the caller accounts those edges) meet at the merge node `join`. Each
2298
+ * arm is the region up to `join`. `next` is where the walk goes after the
2299
+ * merge: its `output` edge, accounted by `continueOn` (`brk` / `cont` when
2300
+ * that edge leaves through the enclosing loop's `break` / spelled `continue`).
2301
+ */
2302
+ function parallelSeg(join, joinNode, targets, scope, ipdom, ctx, refInto) {
2303
+ const arms = targets.map((t, i) => armCb(`a${i}`, region(t, join, scope, ipdom, ctx, refInto)));
2304
+ const seg = `.parallel(${tsLiteral(join)}, [\n${arms.map((a) => reindent(a, ' ')).join(',\n')},\n]${verArg(joinNode)})`;
2305
+ return { seg, next: continueOn(joinNode, 'output', ctx, refInto) };
2306
+ }
2230
2307
  /** Re-indent every non-empty line of a (possibly multi-line) segment by `pad`. */
2231
2308
  function reindent(seg, pad) {
2232
2309
  return seg.split('\n').map((l) => (l === '' ? l : pad + l)).join('\n');
@@ -2385,8 +2462,8 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2385
2462
  if (node.type === T.decision) {
2386
2463
  const r = ipdom.get(node.id) ?? stop;
2387
2464
  const cond = emitBareExpr(String((node.inputs ?? {}).expression ?? ''), ctx.imp, ctx.inputNames);
2388
- const thenSegs = edgeRegion(portEdge(ctx.graph, node.id, 'true'), r, scope, ipdom, ctx, refInto);
2389
- const elseSegs = edgeRegion(portEdge(ctx.graph, node.id, 'false'), r, scope, ipdom, ctx, refInto);
2465
+ const thenSegs = forkRegion(portEdges(ctx.graph, node.id, 'true'), r, scope, ipdom, ctx, refInto);
2466
+ const elseSegs = forkRegion(portEdges(ctx.graph, node.id, 'false'), r, scope, ipdom, ctx, refInto);
2390
2467
  const labels = armLabels(node);
2391
2468
  if (labels.trueLabel)
2392
2469
  thenSegs.unshift(`.label(${tsLiteral(labels.trueLabel)})`);
@@ -2574,16 +2651,16 @@ function region(cursor, stop, scope, ipdom, ctx, refInto) {
2574
2651
  }
2575
2652
  // Parallel: the fork's ipdom is the merge join; each successor is one arm.
2576
2653
  const join = ipdom.get(node.id);
2577
- if (join && join !== EXIT && ctx.graph.byId.get(join)?.type === T.merge) {
2578
- const arms = succ.map((t, i) => armCb(`a${i}`, region(follow(ctx.graph, node.id, t), join, scope, ipdom, ctx, refInto)));
2579
- segs.push(`.parallel(${tsLiteral(join)}, [\n${arms.map((a) => reindent(a, ' ')).join(',\n')},\n]${verArg(ctx.graph.byId.get(join))})`);
2654
+ const joinNode = join !== undefined ? ctx.graph.byId.get(join) : undefined;
2655
+ if (join && join !== EXIT && joinNode?.type === T.merge) {
2656
+ const fork = parallelSeg(join, joinNode, succ.map((t) => follow(ctx.graph, node.id, t)), scope, ipdom, ctx, refInto);
2657
+ segs.push(fork.seg);
2580
2658
  flagDropped(node, ctx, segs);
2581
- const next = continueOn(ctx.graph.byId.get(join), 'output', ctx, refInto);
2582
- if (next.brk || next.cont) {
2583
- segs.push(next.brk ? '.break()' : '.continue()');
2659
+ if (fork.next.brk || fork.next.cont) {
2660
+ segs.push(fork.next.brk ? '.break()' : '.continue()');
2584
2661
  return segs;
2585
2662
  }
2586
- cursor = next.cursor;
2663
+ cursor = fork.next.cursor;
2587
2664
  continue;
2588
2665
  }
2589
2666
  // Unstructured fan-out we can't fold — best-effort marker. Its successor
@@ -2803,7 +2880,7 @@ function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
2803
2880
  }
2804
2881
  }
2805
2882
  }
2806
- const armSegs = edgeRegion(portEdge(ctx.graph, node.id, `case-${c.id}`), r, scope, ipdom, ctx, refInto);
2883
+ const armSegs = forkRegion(portEdges(ctx.graph, node.id, `case-${c.id}`), r, scope, ipdom, ctx, refInto);
2807
2884
  const defaultLabel = `Case ${valueLiteral}`;
2808
2885
  const labelPart = c.label && c.label !== defaultLabel ? `, label: ${tsLiteral(c.label)}` : '';
2809
2886
  // The file's case id comes back when it is not the positional one serialize
@@ -2816,7 +2893,7 @@ function emitSwitch(node, ipdom, scope, ctx, stop, refInto) {
2816
2893
  const on = emitBareExpr(disc ?? '', ctx.imp, ctx.inputNames);
2817
2894
  const hasDefault = (node.inputs ?? {}).hasDefault === true || portEdge(ctx.graph, node.id, 'default') !== undefined;
2818
2895
  const defArm = hasDefault
2819
- ? `, ${armCb('d', edgeRegion(portEdge(ctx.graph, node.id, 'default'), r, scope, ipdom, ctx, refInto))}`
2896
+ ? `, ${armCb('d', forkRegion(portEdges(ctx.graph, node.id, 'default'), r, scope, ipdom, ctx, refInto))}`
2820
2897
  : '';
2821
2898
  const caseList = `[\n${caseSrcs.map((c) => reindent(c, ' ')).join(',\n')},\n]`;
2822
2899
  // The builder's `.switch(on, [{value}])` models `on === value` only. A flow whose
@@ -3020,9 +3097,11 @@ function emitChildFlow(name, child, ctx) {
3020
3097
  // Body: the child's trigger is its own manual Start; walk from its successor.
3021
3098
  const trigger = (child.nodes ?? []).find((n) => isTrigger(n.type) && !(child.edges ?? []).some((e) => e.targetNodeId === n.id));
3022
3099
  const scope = scopeIds(child.nodes ?? [], undefined);
3023
- const entry = trigger ? follow(childCtx.graph, trigger.id, scopeSucc(childCtx.graph, trigger.id, scope)[0]) : undefined;
3024
- const ipdom = entry ? postDominators(entry, scope, childCtx.graph) : new Map();
3025
- const bodySegs = entry ? region(entry, EXIT, scope, ipdom, childCtx) : [];
3100
+ const entries = trigger ? scopeSuccEdges(childCtx.graph, trigger.id, scope) : [];
3101
+ const ipdom = entries.length
3102
+ ? postDominators(entries.length > 1 ? trigger.id : entries[0].targetNodeId, scope, childCtx.graph)
3103
+ : new Map();
3104
+ const bodySegs = entries.length ? forkRegion(entries, EXIT, scope, ipdom, childCtx) : [];
3026
3105
  parts.push(...bodySegs);
3027
3106
  parts.push(...sweepDroppedEdges(childCtx.graph, new Set()));
3028
3107
  // A subflow carries its own scoped layout, resolved against its own node ids.
@@ -3144,9 +3223,13 @@ export function decompile(flow, options = {}) {
3144
3223
  const ctx = { graph, flow, imp, inputNames, o: options, emitted: new Set(), shared };
3145
3224
  const scope = scopeIds(flow.nodes ?? [], undefined);
3146
3225
  const startId = trigger?.id ?? 'start';
3147
- const first = follow(graph, startId, scopeSucc(graph, startId, scope)[0]);
3148
- const ipdom = first !== undefined ? postDominators(first, scope, graph) : new Map();
3149
- const bodySegs = first !== undefined ? region(first, EXIT, scope, ipdom, ctx) : [];
3226
+ // Walked from the trigger's successors together: a `.parallel` placed first
3227
+ // gives the trigger one edge per arm (#1155).
3228
+ const firsts = scopeSuccEdges(graph, startId, scope);
3229
+ const ipdom = firsts.length
3230
+ ? postDominators(firsts.length > 1 ? startId : firsts[0].targetNodeId, scope, graph)
3231
+ : new Map();
3232
+ const bodySegs = firsts.length ? forkRegion(firsts, EXIT, scope, ipdom, ctx) : [];
3150
3233
  // Additional roots decompile to .entryPoint(...): scoped inputs from the
3151
3234
  // globals bound to them, and a prefix region that hands off into the shared
3152
3235
  // body — the shared nodes are in ctx.emitted by now, so the walk naturally
@@ -3236,8 +3319,11 @@ function entryPointSeg(t, scope, ipdom, ctx) {
3236
3319
  optParts.push(`inputs: { ${scoped.map((g) => `${key(g.name ?? g.id)}: ${declExpr(g, true, ctx.imp, sharedOf(g))}`).join(', ')} }`);
3237
3320
  }
3238
3321
  optParts.push(...triggerOptParts(t));
3239
- const first = follow(ctx.graph, t.id, scopeSucc(ctx.graph, t.id, scope)[0]);
3240
- const prefixSegs = first !== undefined ? region(first, EXIT, scope, ipdom, ctx) : [];
3322
+ const firsts = scopeSuccEdges(ctx.graph, t.id, scope);
3323
+ // A prefix that forks needs post-dominators over ITS nodes, which the root's
3324
+ // map (computed from the default trigger) does not cover.
3325
+ const epIpdom = firsts.length > 1 ? postDominators(t.id, scope, ctx.graph) : ipdom;
3326
+ const prefixSegs = firsts.length ? forkRegion(firsts, EXIT, scope, epIpdom, ctx) : [];
3241
3327
  const optArgText = optParts.length ? `, { ${optParts.join(', ')} }` : (prefixSegs.length ? ', undefined' : '');
3242
3328
  const prefix = prefixSegs.length ? `, ${armCb('b', prefixSegs)}` : '';
3243
3329
  return `.entryPoint(${tsLiteral(t.id)}, ${factory}${optArgText}${prefix})`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.20.0",
3
+ "version": "6.20.1",
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",
@@ -92,5 +92,5 @@
92
92
  "@types/node": "^22.7.0",
93
93
  "esbuild": "^0.28.1"
94
94
  },
95
- "gitref": "30b9615751de65281973caeec9d688ad105fc925"
95
+ "gitref": "5440c25e8e833aced459eb5a0ae9109ac3c973ae"
96
96
  }