@uipath/maestro-builder-sdk 6.66.7 → 6.66.9

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/serialize.js CHANGED
@@ -11,7 +11,7 @@ import { LOOP_DEFAULT_VERSION, bodyUsesBreak, loopDeclaredInputs, loopHasInnerHa
11
11
  import { Expr, toExpr, SCHEDULE_PRESETS, flatNameSegmentsOf, parallelFork } from './flow-sdk.js';
12
12
  import { FlowAction, FlowTrigger } from './core/node-classes.js';
13
13
  import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
14
- import { CompileRefusal } from './core/compile-refusal.js';
14
+ import { CompileRefusal, LibraryRequiredRefusal } from './core/compile-refusal.js';
15
15
  import { nearestName } from './core/edit-distance.js';
16
16
  import { rawNodeInputProblem, registeredRawType } from './raw-node-advice.js';
17
17
  import { planContributionBindings } from './core/contribution-bindings.js';
@@ -736,17 +736,31 @@ function resolveEvent(library, kind, sub, where) {
736
736
  /**
737
737
  * LIBRARY_REQUIRED for an event subscription at `site`, after the refusals that
738
738
  * need no library: its binding labels, judged as `eventDetail` judges them, so a
739
- * run without a library (`check`'s inner loop) still hears about them.
739
+ * run without a library (`check`'s inner loop) still hears about them. The
740
+ * refusal carries the connection the node is bound to, so its collect-mode
741
+ * stand-in still takes part in the file-wide connection rule (`connectionConnectors`).
740
742
  */
741
743
  function libraryRequiredForEvent(opts, sub, nodeId, site, call) {
744
+ let use;
742
745
  if (sub !== null && typeof sub === 'object') {
746
+ // With no library no parameter is declared, so every `where` key lands in
747
+ // eventParameters and folds into the match expression, exactly as
748
+ // `eventDetail` places an undeclared key. A lookup token is a value only
749
+ // a library resolves; it is not judged here.
750
+ if (sub.where !== null && typeof sub.where === 'object') {
751
+ const scope = Object.fromEntries(Object.entries(sub.where).filter(([, v]) => !isLookupToken(v)));
752
+ eventScopeClauses(flattenWhere(scope, new Set()), nodeId);
753
+ }
743
754
  if (Array.isArray(sub.filters))
744
755
  eventFilterReads(sub.filters, nodeId);
745
756
  requireBindingPair(site, sub, eventTarget(sub));
746
- resolveBinding(opts, sub.connection, 'connection', `event node "${nodeId}"`, eventTarget(sub));
757
+ const connection = resolveBinding(opts, sub.connection, 'connection', `event node "${nodeId}"`, eventTarget(sub));
747
758
  resolveBinding(opts, sub.folder, 'folder', `event node "${nodeId}"`, eventTarget(sub));
759
+ const connector = eventTarget(sub)?.key;
760
+ if (connection !== undefined && connector !== undefined)
761
+ use = { connector, connection };
748
762
  }
749
- return new CompileRefusal('LIBRARY_REQUIRED', `${call} needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
763
+ return new LibraryRequiredRefusal(`${call} needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`, use);
750
764
  }
751
765
  /** The trigger kinds a `.trigger()` or `.entryPoint()` spec may carry. */
752
766
  const TRIGGER_KINDS = new Set(['manual', 'scheduled', 'event', 'form', 'conversation', 'voice']);
@@ -916,6 +930,36 @@ function eventFilterReads(filters, nodeId) {
916
930
  return read;
917
931
  });
918
932
  }
933
+ /**
934
+ * The match-expression clauses of a subscription's EVENT parameters, one
935
+ * `k == '<value>'` per value, or the refusal of the first value compile cannot
936
+ * write there. The clause compares against a JMESPath string, so the value must
937
+ * be a string, number or boolean: an object or array would be stringified to
938
+ * `'[object Object]'` (or a comma list), a subscription that matches nothing
939
+ * (EVENT_WHERE_NOT_SCALAR). A string with a backslash JMESPath engines read
940
+ * apart is EVENT_FILTER_BACKSLASH. An absent or empty value (`''` or `[]`, so a
941
+ * `where` built from a possibly-empty list) writes no clause.
942
+ *
943
+ * Only event parameters fold into the expression; a query or path parameter is
944
+ * sent with the request that registers the subscription and is never read as
945
+ * JMESPath, so neither rule applies to it.
946
+ */
947
+ function eventScopeClauses(eventParameters, nodeId) {
948
+ return Object.entries(eventParameters)
949
+ .filter(([, v]) => v !== undefined && v !== null && v !== '' && !(Array.isArray(v) && v.length === 0))
950
+ .map(([k, v]) => {
951
+ if (typeof v !== 'string' && typeof v !== 'number' && typeof v !== 'boolean') {
952
+ throw new CompileRefusal('EVENT_WHERE_NOT_SCALAR', `event node "${nodeId}": \`where.${k}\` is ${Array.isArray(v) ? 'an array' : isLookupToken(v) ? 'an unresolved lookup() nested inside an object' : 'an object'}, `
953
+ + `and an event parameter is matched as one text value (\`${k} == '<value>'\`): written as it is, `
954
+ + `the subscription would compare against ${JSON.stringify(String(v))} and match nothing. `
955
+ + `Give \`${k}\` a string, number or boolean.`, `where: { '${k}': '<value>' }`);
956
+ }
957
+ const problem = jmesStringProblem(String(v));
958
+ if (problem)
959
+ throw new CompileRefusal('EVENT_FILTER_BACKSLASH', `event node "${nodeId}": \`where.${k}\`: ${problem}`);
960
+ return `${k} == ${jmesString(String(v))}`;
961
+ });
962
+ }
919
963
  function eventDetail(rc, sub, nodeId, opts, site) {
920
964
  requireBindingPair(site, sub, eventTarget(sub));
921
965
  const conn = resolveBinding(opts, sub.connection, 'connection', `event node "${nodeId}"`, eventTarget(sub));
@@ -976,14 +1020,7 @@ function eventDetail(rc, sub, nodeId, opts, site) {
976
1020
  // subscription's own scope — the mandatory clause the platform AND-joins —
977
1021
  // whereas a query or path parameter is part of the request that registers
978
1022
  // the subscription, not a property of the events it matches.
979
- const scopeClauses = Object.entries(eventParameters)
980
- .filter(([, v]) => v !== undefined && v !== null && String(v) !== '')
981
- .map(([k, v]) => {
982
- const problem = jmesStringProblem(String(v));
983
- if (problem)
984
- throw new CompileRefusal('EVENT_FILTER_BACKSLASH', `event node "${nodeId}": \`where.${k}\`: ${problem}`);
985
- return `${k} == ${jmesString(String(v))}`;
986
- });
1023
+ const scopeClauses = eventScopeClauses(eventParameters, nodeId);
987
1024
  // Each authored filter is read structurally (which operator, which value) and
988
1025
  // lowered with the designer's own grammar — see `event-filters.ts` for the
989
1026
  // measured table.
@@ -1499,6 +1536,21 @@ function terminateOutputs(scope) {
1499
1536
  }
1500
1537
  return outs;
1501
1538
  }
1539
+ /**
1540
+ * A step or entry point named `start` in a scope where `$vars.start` is
1541
+ * rewritten to the scope's start node (#1518): the root while `.triggerId()`
1542
+ * renames the trigger (`renamedTrigger`), or any subflow body (its start node
1543
+ * is `<caller>Start`). The rewrite would point the node's own readers at that
1544
+ * start node, so the name is refused instead. A step-level refusal: `check`
1545
+ * reports it on the step (or entry point), so the message names no scope.
1546
+ */
1547
+ function startReadRefusal(what, renamedTrigger, code = 'STEP_ID_COLLISION') {
1548
+ const owner = renamedTrigger !== undefined
1549
+ ? `the trigger, which .triggerId("${renamedTrigger}") renamed: compile rewrites \`$vars.start\` to \`$vars.${renamedTrigger}\``
1550
+ : "this subflow body's own start node: compile rewrites `$vars.start` to it";
1551
+ return new CompileRefusal(code, `[${code}] ${what}: \`$vars.start\` (what \`input()\` reads) names ${owner}, so reads of `
1552
+ + `${what} would go there. Give it another name.`);
1553
+ }
1502
1554
  /**
1503
1555
  * Rewrite `$vars.<step>` references through a scope's rename map.
1504
1556
  *
@@ -1716,6 +1768,13 @@ function serializeFlow(built, opts) {
1716
1768
  * placeholder check exempts them.
1717
1769
  */
1718
1770
  const unboundDrafts = new Set();
1771
+ /**
1772
+ * The connection each collect-mode stand-in was bound to, by node id: a node
1773
+ * refused only for want of a library (`LibraryRequiredRefusal`) has no
1774
+ * `detail` to read, so the file-wide connection rule (`connectionConnectors`)
1775
+ * is handed it here. Empty outside collect mode.
1776
+ */
1777
+ const standInConnections = new Map();
1719
1778
  /**
1720
1779
  * RESOURCE bindings collected while emitting, keyed by binding id — `resource:
1721
1780
  * 'process'` for the rpa family, `resource: 'queue'` for the queue family.
@@ -1877,9 +1936,16 @@ function serializeFlow(built, opts) {
1877
1936
  // trigger node's default id. When the flow renames the trigger, register that
1878
1937
  // rename so the SAME pass that rewrites step-name references rewrites these
1879
1938
  // too, rather than teaching every call site about the trigger.
1880
- if (built.triggerId && built.triggerId !== 'start') {
1881
- root.rename.nodeIds.set('start', [built.triggerId]);
1882
- }
1939
+ //
1940
+ // That rewrite cannot tell an `input()` read from a read of a node that is
1941
+ // really named `start` (`entryInput('start', 'x')` and `$vars.start.output.x`
1942
+ // in a step's code are the same text), so while the trigger is renamed the
1943
+ // name `start` is the trigger's: a step or an entry point that takes it is
1944
+ // refused (`startReadRefusal`), never rewritten to point at the trigger. A
1945
+ // subflow body renames `start` the same way, so its steps are held to it too.
1946
+ const renamedTrigger = built.triggerId && built.triggerId !== 'start' ? built.triggerId : undefined;
1947
+ if (renamedTrigger)
1948
+ root.rename.nodeIds.set('start', [renamedTrigger]);
1883
1949
  // Every node id emitted anywhere in the file. `start` is taken before
1884
1950
  // anything else: the root trigger's id is fixed, so a step named like the
1885
1951
  // trigger is refused (claimStepIds).
@@ -1984,7 +2050,9 @@ function serializeFlow(built, opts) {
1984
2050
  * thrown before any node is emitted). Renaming the second to `x2` left
1985
2051
  * `.stepToRef('x')` and `$vars.x` pointing at the first, with no word said.
1986
2052
  * - In the flow itself, a step named like the trigger is refused the same
1987
- * way (STEP_ID_COLLISION).
2053
+ * way (STEP_ID_COLLISION). So is a step named `start` wherever
2054
+ * `$vars.start` is rewritten to a start node: the flow while `.triggerId()`
2055
+ * renames the trigger, and every subflow body (#1518, `startReadRefusal`).
1988
2056
  * - A subflow body whose ids are already taken (a second copy of the same
1989
2057
  * child, or a child step sharing a name with a caller's step) is the one
1990
2058
  * place ids move: the whole copy is prefixed with the calling step's name,
@@ -2019,6 +2087,10 @@ function serializeFlow(built, opts) {
2019
2087
  // A subflow copy's named Ends (`alsoIds`) decide the prefix with its steps (#1253).
2020
2088
  const prefix = copyPrefix !== undefined && [...seen.keys(), ...alsoIds].some((id) => takenIds.has(id)) ? copyPrefix : '';
2021
2089
  for (const [safe, n] of seen) {
2090
+ // `$vars.start` names this scope's start node, not a step (see `renamedTrigger`).
2091
+ if (safe === 'start' && (copyPrefix !== undefined || renamedTrigger)) {
2092
+ refuseOn(scope, n, startReadRefusal(`step "${n}"`, copyPrefix !== undefined ? undefined : renamedTrigger));
2093
+ }
2022
2094
  if (!prefix && takenIds.has(safe)) {
2023
2095
  throw new CompileRefusal('STEP_ID_COLLISION', `${where}: [STEP_ID_COLLISION] step "${n}" has the trigger's node id "${safe}", and compile will not `
2024
2096
  + `rename it "${safe}2". Rename the step.`);
@@ -2831,8 +2903,9 @@ function serializeFlow(built, opts) {
2831
2903
  }
2832
2904
  // A type that owns a family of names (`typeNamePrefix`) is placed under the
2833
2905
  // name its definition carries — `uipath.core.function.<key>`, not the prefix.
2834
- // The bare prefix is allowed: an empty `key` builds it, check refuses that
2835
- // input (`*_NO_KEY`), and serialize alone emits what it always did.
2906
+ // The bare prefix is a name the type owns, so it is not an internal error
2907
+ // here; a type refuses a blank key itself when that is what builds it (the
2908
+ // process family's `*_NO_KEY`, nodes/process/flow/refusals.ts).
2836
2909
  const prefix = entry.type.typeNamePrefix;
2837
2910
  const nodeType = prefix !== undefined ? def?.nodeType : impl.typeName;
2838
2911
  if (prefix !== undefined && (typeof nodeType !== 'string' || !nodeType.startsWith(prefix)
@@ -2917,6 +2990,43 @@ function serializeFlow(built, opts) {
2917
2990
  throw refusal;
2918
2991
  record(refusal, stepName, scope.stepNodeId.get(stepName) ?? scope.pending.get(stepName));
2919
2992
  }
2993
+ /**
2994
+ * Refuse a `.switch()` case the node cannot hold as written (#1518). Each
2995
+ * case becomes one `{ id, expression }` row and one `case-<id>` port, so:
2996
+ * - SWITCH_CASE_FORM: a case needs exactly one of `value` (compared with
2997
+ * `on`) and `when` (its own condition). With both, only `when` would be
2998
+ * written and the value dropped; a `value` with no `on` would be written as
2999
+ * `undefined === 1`, and a case with neither as `undefined === undefined`,
3000
+ * which is always taken.
3001
+ * - SWITCH_CASE_ID: an id must be a non-empty string, and one per case. Two
3002
+ * cases on one id (an explicit `case_2` beside the positional one, #1130)
3003
+ * would share one `case-case_2` port, edges to both arms; a blank or
3004
+ * non-string id writes a `case-` / `case-5` port.
3005
+ * In collect mode each one is reported on the switch, and the node is still
3006
+ * written as authored, so the arms are judged.
3007
+ */
3008
+ function refuseSwitchCases(scope, step) {
3009
+ const s = step.name;
3010
+ const seenIds = new Set();
3011
+ step.cases.forEach((c, i) => {
3012
+ const hasValue = c.value !== undefined;
3013
+ const hasWhen = c.when !== undefined;
3014
+ if (hasValue === hasWhen || (hasValue && !step.on)) {
3015
+ refuseOn(scope, s, new CompileRefusal('SWITCH_CASE_FORM', hasValue && hasWhen
3016
+ ? `Switch "${s}" case ${i + 1} has both \`value\` and \`when\`. Give it one: \`value\` is compared with \`on\`, \`when\` is the case's own condition.`
3017
+ : hasValue
3018
+ ? `Switch "${s}" case ${i + 1} has a \`value\` but the switch has no \`on\` to compare it with. Pass \`on\`, or give the case a \`when\` condition.`
3019
+ : `Switch "${s}" case ${i + 1} has neither \`value\` nor \`when\`.`));
3020
+ }
3021
+ const caseId = c.id ?? `case_${i + 1}`;
3022
+ if (typeof caseId !== 'string' || caseId === '' || seenIds.has(caseId)) {
3023
+ refuseOn(scope, s, new CompileRefusal('SWITCH_CASE_ID', typeof caseId !== 'string' || caseId === ''
3024
+ ? `Switch "${s}" case ${i + 1} has an id that is not a non-empty string. Leave \`id\` out for the positional case_<n>.`
3025
+ : `Switch "${s}" has two cases with the id "${caseId}", so both would leave from one 'case-${caseId}' port. Give one a different \`id\`.`));
3026
+ }
3027
+ seenIds.add(String(caseId));
3028
+ });
3029
+ }
2920
3030
  /** The step whose node `nodeId` is, in `scope`. */
2921
3031
  function stepOfNode(scope, nodeId) {
2922
3032
  for (const [name, id] of scope.stepNodeId)
@@ -2939,6 +3049,8 @@ function serializeFlow(built, opts) {
2939
3049
  catch (error) {
2940
3050
  const refusal = stepRefusal(error);
2941
3051
  record(refusal, name, id);
3052
+ if (refusal instanceof LibraryRequiredRefusal && refusal.connection)
3053
+ standInConnections.set(id, refusal.connection);
2942
3054
  root.nodes.length = mark.nodes;
2943
3055
  for (const key of [...resourceBindings.keys()])
2944
3056
  if (!mark.resources.has(key))
@@ -2975,6 +3087,8 @@ function serializeFlow(built, opts) {
2975
3087
  const refusal = stepRefusal(error);
2976
3088
  const id = scope.stepNodeId.get(step.name) ?? stepUid(scope, step.name);
2977
3089
  record(refusal, step.name, id);
3090
+ if (refusal instanceof LibraryRequiredRefusal && refusal.connection)
3091
+ standInConnections.set(id, refusal.connection);
2978
3092
  scope.nodes.length = mark.nodes;
2979
3093
  scope.edges.length = mark.edges;
2980
3094
  scope.pendingEdges.length = mark.pendingEdges;
@@ -3416,6 +3530,7 @@ function serializeFlow(built, opts) {
3416
3530
  // the runtime — so no `=js:` prefix, same as a decision's `expression`.
3417
3531
  const id = stepUid(scope, step.name);
3418
3532
  const node = makeNode(NODE_TYPE.switch, id, step.options?.label ?? step.label ?? step.name, { parentId, requestedVersion: step.options?.version });
3533
+ refuseSwitchCases(scope, step);
3419
3534
  // `id` is the author's when given (a decompiled switch keeps the
3420
3535
  // canvas's `default-1`, …: merge keeps the original node, so the edges
3421
3536
  // must leave from ITS handles), else positional (#1130).
@@ -3814,6 +3929,14 @@ function serializeFlow(built, opts) {
3814
3929
  const firstShared = built.steps.find((st) => 'name' in st && st.kind !== 'return');
3815
3930
  const sharedTarget = firstShared && 'name' in firstShared ? root.stepNodeId.get(firstShared.name) : undefined;
3816
3931
  for (const ep of entryPoints) {
3932
+ if (ep.id === 'start' && renamedTrigger) {
3933
+ // Reported on the entry point; in collect mode it is still emitted (the
3934
+ // id `start` is free in the file), so its prefix is judged too.
3935
+ const refusal = startReadRefusal(`.entryPoint("${ep.id}")`, renamedTrigger, 'ENTRY_POINT_DUP_ID');
3936
+ if (!opts.collect)
3937
+ throw refusal;
3938
+ record(refusal, ep.id, ep.id);
3939
+ }
3817
3940
  if (takenIds.has(ep.id)) {
3818
3941
  throw new CompileRefusal('ENTRY_POINT_DUP_ID', `.entryPoint("${ep.id}"): the id collides with a step, the trigger, or another entry point.`);
3819
3942
  }
@@ -3984,7 +4107,7 @@ function serializeFlow(built, opts) {
3984
4107
  assertBindingPlaceholdersResolve(flow, unboundDrafts);
3985
4108
  assertFileWideIdsUnique(flow);
3986
4109
  assertConnectorConfigurationRestorable(flow);
3987
- connectionConnectors(flow);
4110
+ connectionConnectors(flow, standInConnections);
3988
4111
  return flow;
3989
4112
  }
3990
4113
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.66.7",
3
+ "version": "6.66.9",
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",
@@ -100,5 +100,5 @@
100
100
  "@types/node": "^22.7.0",
101
101
  "esbuild": "^0.28.1"
102
102
  },
103
- "gitref": "00764f58249484b8ee341c2cc0d24b38eee8ec14"
103
+ "gitref": "91832f175b4f44a12bd4b0046fccd7bbeb968172"
104
104
  }