@uipath/maestro-builder-sdk 6.56.0 → 6.58.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.
Files changed (75) hide show
  1. package/dist/api-index.md +6 -2
  2. package/dist/api-members.md +1 -1
  3. package/dist/case/subscription.d.ts +4 -2
  4. package/dist/case/subscription.js +5 -2
  5. package/dist/cli/commands/flow.js +2 -0
  6. package/dist/core/actions.js +2 -2
  7. package/dist/core/binding-messages.d.ts +2 -1
  8. package/dist/core/binding-messages.js +3 -4
  9. package/dist/core/byoa.d.ts +3 -2
  10. package/dist/core/byoa.js +3 -3
  11. package/dist/core/carried-definitions.d.ts +0 -18
  12. package/dist/core/carried-definitions.js +3 -2
  13. package/dist/core/ceql.d.ts +2 -1
  14. package/dist/core/ceql.js +3 -2
  15. package/dist/core/compile-refusal.d.ts +21 -0
  16. package/dist/core/compile-refusal.js +26 -0
  17. package/dist/core/connector-inputs.js +2 -1
  18. package/dist/core/connector-resolve-error.d.ts +2 -1
  19. package/dist/core/connector-resolve-error.js +3 -4
  20. package/dist/core/connectors.js +2 -1
  21. package/dist/core/event-filters.js +2 -1
  22. package/dist/core/hitl-routing.d.ts +7 -82
  23. package/dist/core/hitl-routing.js +95 -6
  24. package/dist/core/library.js +4 -3
  25. package/dist/core/lookups.js +6 -5
  26. package/dist/core/nodes/registry.js +3 -2
  27. package/dist/core/nodes/versioned.js +3 -2
  28. package/dist/core/process-resource-sort-order.js +1 -1
  29. package/dist/core/read-text.d.ts +0 -28
  30. package/dist/core/read-text.js +3 -2
  31. package/dist/core/refusals.d.ts +465 -0
  32. package/dist/core/refusals.js +504 -0
  33. package/dist/core/script-function.d.ts +126 -0
  34. package/dist/core/script-function.js +511 -0
  35. package/dist/core/typescript-module.js +2 -1
  36. package/dist/decompile-cli.js +11 -3
  37. package/dist/decompile.d.ts +9 -0
  38. package/dist/decompile.js +76 -3
  39. package/dist/flow-expr-check.d.ts +2 -1
  40. package/dist/flow-expr-check.js +4 -2
  41. package/dist/flow-node-type.d.ts +21 -0
  42. package/dist/flow-sdk.js +17 -16
  43. package/dist/flow-step-tree.js +1 -1
  44. package/dist/flow-variables.js +3 -2
  45. package/dist/load.js +19 -5
  46. package/dist/nodes/agent/flow/inline-agent.js +7 -6
  47. package/dist/nodes/connector/flow/node-type.js +2 -1
  48. package/dist/nodes/data-fabric/flow/factories.js +2 -1
  49. package/dist/nodes/data-fabric/flow/verbs.js +2 -1
  50. package/dist/nodes/delay/flow/factories.js +2 -1
  51. package/dist/nodes/document/flow/ixp.js +5 -1
  52. package/dist/nodes/hitl/flow/check.js +4 -2
  53. package/dist/nodes/hitl/flow/node-type.js +6 -5
  54. package/dist/nodes/http/flow/factories.js +2 -1
  55. package/dist/nodes/http/flow/node-type.js +6 -5
  56. package/dist/nodes/process/flow/factories.js +2 -1
  57. package/dist/nodes/queue/flow/node-type.js +3 -2
  58. package/dist/nodes/script/flow/factories.d.ts +42 -1
  59. package/dist/nodes/script/flow/factories.js +8 -9
  60. package/dist/nodes/script/flow/node-type.js +71 -0
  61. package/dist/nodes/script/flow/runtime.d.ts +20 -0
  62. package/dist/nodes/script/flow/runtime.js +29 -0
  63. package/dist/nodes/script/flow/spec.d.ts +32 -1
  64. package/dist/nodes/transform/flow/node-type.js +2 -1
  65. package/dist/raw-node.js +3 -2
  66. package/dist/ref-resolve.d.ts +0 -36
  67. package/dist/ref-resolve.js +9 -8
  68. package/dist/schema-version.d.ts +0 -38
  69. package/dist/schema-version.js +3 -2
  70. package/dist/script-function-scope.d.ts +57 -0
  71. package/dist/script-function-scope.js +203 -0
  72. package/dist/sdk-resolve.js +2 -1
  73. package/dist/serialize.d.ts +5 -2
  74. package/dist/serialize.js +115 -83
  75. package/package.json +2 -2
package/dist/decompile.js CHANGED
@@ -112,6 +112,38 @@ let runAbsorbed = new Map();
112
112
  let runFlowId = 'flow';
113
113
  /** `DecompileOptions.readSidecar` for the current run. */
114
114
  let runReadSidecar;
115
+ /**
116
+ * A host's own say on decompile's default script form, beside the TypeScript
117
+ * hand-over in core/typescript-module.ts:
118
+ * `globalThis[Symbol.for('uipath.maestro-builder-sdk.scripts')] = 'string'`.
119
+ */
120
+ const HOST_SCRIPTS = Symbol.for('uipath.maestro-builder-sdk.scripts');
121
+ /**
122
+ * Function scripts by default only under Node. The exact text a function form
123
+ * stores is what the loader's `Function.prototype.toString()` returns, which is
124
+ * the author's own text under Node's type stripping (what `compile` and `uip`
125
+ * run) and a reprint under other loaders. A host that runs the SDK in a page has
126
+ * not been measured, so there a decompiled script stays a string unless asked.
127
+ */
128
+ function defaultScripts() {
129
+ const host = globalThis[HOST_SCRIPTS];
130
+ if (host === 'function' || host === 'string')
131
+ return host;
132
+ // Real Node, tested as core/typescript-module.ts does: a page bundle may shim
133
+ // `process.versions.node` (cli-browser's banner sets it) but does not define
134
+ // `process.getBuiltinModule`.
135
+ return typeof globalThis.process?.getBuiltinModule === 'function' ? 'function' : 'string';
136
+ }
137
+ /** How this run prints scripts (`DecompileOptions.scripts`). */
138
+ let runScripts = 'function';
139
+ /** The module-level functions print hooks declared this run, in order. */
140
+ let runModuleFunctions = [];
141
+ /**
142
+ * Each step's reserved function name, by node: keyed by the node itself, not its
143
+ * id, because a root step and a subflow step may share an id, and two
144
+ * `function script_<id>` declarations would make the module unloadable.
145
+ */
146
+ let runFunctionNames = new Map();
115
147
  function warnOnce(message) {
116
148
  if (runWarned.has(message))
117
149
  return;
@@ -336,6 +368,8 @@ class Imports {
336
368
  locals = new Set(['flow']);
337
369
  /** Names a package may not take bare: the SDK's helpers and every built-in factory. */
338
370
  reserved;
371
+ /** The SDK's type-only names (`FlowVars`): `import { type X }`, erased under type stripping. */
372
+ types = new Set();
339
373
  constructor() {
340
374
  this.reserved = new Set([
341
375
  ...SDK_HELPER_NAMES,
@@ -368,14 +402,25 @@ class Imports {
368
402
  this.locals.add(local);
369
403
  return local;
370
404
  }
405
+ /**
406
+ * A type-only name from the SDK: `import { type X }`, erased under type
407
+ * stripping, so it never needs a runtime export. Taken as a local, so no
408
+ * package factory binds the same name.
409
+ */
410
+ useType(name) {
411
+ this.types.add(name);
412
+ this.locals.add(name);
413
+ return name;
414
+ }
371
415
  /** One import per module: the SDK first, then the packages by name. */
372
416
  line(specifier) {
373
417
  const lines = [];
374
418
  for (const [module, names] of [...this.used].sort(([a], [b]) => (a === undefined ? -1 : b === undefined ? 1 : a.localeCompare(b)))) {
375
- if (names.size === 0)
419
+ const types = module === undefined ? [...this.types].filter((t) => !names.has(t)).sort().map((t) => `type ${t}`) : [];
420
+ if (names.size === 0 && types.length === 0)
376
421
  continue;
377
422
  const bindings = [...names].sort(([a], [b]) => a.localeCompare(b)).map(([name, local]) => (local === name ? name : `${name} as ${local}`));
378
- lines.push(`import { ${bindings.join(', ')} } from ${tsLiteral(module ?? specifier)};`);
423
+ lines.push(`import { ${[...bindings, ...types].join(', ')} } from ${tsLiteral(module ?? specifier)};`);
379
424
  }
380
425
  return lines.join('\n');
381
426
  }
@@ -826,6 +871,11 @@ function emitRegisteredSpec(node, imp, inputNames, graph) {
826
871
  json: (v) => tsLiteral(v),
827
872
  key: (k) => key(k),
828
873
  helper: (name) => imp.use(name),
874
+ typeHelper: (name) => imp.useType(name),
875
+ warn: (message) => warnOnce(`step ${tsLiteral(node.id)}: ${message}`),
876
+ functionName: () => moduleFunctionName(node),
877
+ declareFunction: (source) => { runModuleFunctions.push(source); },
878
+ scripts: runScripts,
829
879
  };
830
880
  const call = impl.print?.(spec, print) ?? {
831
881
  factory: impl.factory,
@@ -2838,6 +2888,9 @@ function foldOutputBacking(flow) {
2838
2888
  }
2839
2889
  export function decompile(flow, options = {}) {
2840
2890
  flow = foldOutputBacking(flow);
2891
+ runScripts = options.scripts ?? defaultScripts();
2892
+ runModuleFunctions = [];
2893
+ runFunctionNames = new Map();
2841
2894
  const importSpecifier = options.importSpecifier ?? './flow-sdk.js';
2842
2895
  runStrict = options.bestEffort === false;
2843
2896
  runWarn = options.warn;
@@ -2991,7 +3044,27 @@ export function decompile(flow, options = {}) {
2991
3044
  const hoisted = [...carriedConsts, ...runRawManifests]
2992
3045
  .map(([name, json]) => `const ${name} = ${json};`)
2993
3046
  .join('\n\n');
2994
- return [imp.line(importSpecifier), hoisted, chain].filter((p) => p.length > 0).join('\n\n') + '\n';
3047
+ const functions = runModuleFunctions.join('\n\n');
3048
+ return [imp.line(importSpecifier), hoisted, chain, functions].filter((p) => p.length > 0).join('\n\n') + '\n';
3049
+ }
3050
+ /**
3051
+ * A step's module-level function name: `script_<id>`, with what an identifier
3052
+ * cannot hold replaced by `_`, and a numeric suffix when two steps (in different
3053
+ * scopes, or with ids that sanitize alike) would get the same one.
3054
+ * The `script_` prefix keeps it clear of every SDK export (all camelCase) and of
3055
+ * the hoisted constants.
3056
+ */
3057
+ function moduleFunctionName(node) {
3058
+ const have = runFunctionNames.get(node);
3059
+ if (have)
3060
+ return have;
3061
+ const base = `script_${node.id.replace(/[^A-Za-z0-9_$]/g, '_')}`;
3062
+ const taken = new Set(runFunctionNames.values());
3063
+ let name = base;
3064
+ for (let i = 2; taken.has(name); i++)
3065
+ name = `${base}_${i}`;
3066
+ runFunctionNames.set(node, name);
3067
+ return name;
2995
3068
  }
2996
3069
  /**
2997
3070
  * Top-level nodes (no `parentId`) that no root trigger reaches over ANY edge —
@@ -1,5 +1,6 @@
1
1
  import { type ExprDiagnostic } from './core/expr-check.js';
2
2
  import type { BuiltFlow } from './flow-sdk.js';
3
+ import { CompileRefusal } from './core/compile-refusal.js';
3
4
  /** A diagnostic plus the node / field it was found in. */
4
5
  export interface LocatedDiagnostic extends ExprDiagnostic {
5
6
  where: string;
@@ -28,7 +29,7 @@ export declare function checkCookedEscapes(built: BuiltFlow): (LocatedDiagnostic
28
29
  * `message` lists every problem (so a runner that prints `err.message` shows them
29
30
  * all at once); `diagnostics` carries them structured for a caller that wants them.
30
31
  */
31
- export declare class FlowBuildError extends Error {
32
+ export declare class FlowBuildError extends CompileRefusal {
32
33
  readonly diagnostics: LocatedDiagnostic[];
33
34
  constructor(built: BuiltFlow, diagnostics: LocatedDiagnostic[]);
34
35
  }
@@ -30,6 +30,7 @@
30
30
  import { Expr, cookedEscapesOf } from './core/expr.js';
31
31
  import { checkExpression, cookedEscapeDiagnostics } from './core/expr-check.js';
32
32
  import { stepDeclaredVariables } from './flow-step-variables.js';
33
+ import { CompileRefusal } from './core/compile-refusal.js';
33
34
  const NOUN = 'input, variable, or step';
34
35
  /**
35
36
  * Every `$vars` root a reference may legitimately resolve to, across the WHOLE
@@ -269,12 +270,13 @@ export function checkCookedEscapes(built) {
269
270
  * `message` lists every problem (so a runner that prints `err.message` shows them
270
271
  * all at once); `diagnostics` carries them structured for a caller that wants them.
271
272
  */
272
- export class FlowBuildError extends Error {
273
+ export class FlowBuildError extends CompileRefusal {
273
274
  diagnostics;
274
275
  constructor(built, diagnostics) {
275
276
  const label = built.name || built.id || 'flow';
276
277
  const lines = diagnostics.map((d) => ` ✗ [${d.code}] in ${d.where}: ${d.message}${d.suggestion ? ` (did you mean "${d.suggestion}"?)` : ''}`);
277
- super(`Cannot build flow "${label}" — ${diagnostics.length} expression problem(s):\n${lines.join('\n')}`);
278
+ // The first problem's code; `diagnostics` carries every one.
279
+ super(diagnostics[0]?.code ?? 'EXPR_SYNTAX', `Cannot build flow "${label}" — ${diagnostics.length} expression problem(s):\n${lines.join('\n')}`);
278
280
  this.name = 'FlowBuildError';
279
281
  this.diagnostics = diagnostics;
280
282
  }
@@ -327,6 +327,27 @@ export interface FlowPrintContext {
327
327
  key(name: string): string;
328
328
  /** An authoring helper (`lit`, `input`, …) by name: imported into the printed source, and its identifier. */
329
329
  helper(name: string): string;
330
+ /** An exported TYPE (`FlowVars`) by name: imported as `type <name>`, and its identifier. */
331
+ typeHelper(name: string): string;
332
+ /**
333
+ * Report a finding about this step, printed once with the step named.
334
+ * Informational: it never fails `--strict`, so it is for a choice of spelling
335
+ * that loses nothing, not for something recompiling would drop.
336
+ */
337
+ warn(message: string): void;
338
+ /**
339
+ * The identifier of this step's module-level function: unique in the printed
340
+ * module, the same on every call for the step. Reserving it declares nothing;
341
+ * {@link declareFunction} does.
342
+ */
343
+ functionName(): string;
344
+ /** Add a declaration to the end of the printed module, after the flow, verbatim. */
345
+ declareFunction(source: string): void;
346
+ /**
347
+ * How scripts print: `function` (the default) as a TypeScript function where
348
+ * the body allows it, `string` always as `script({ code })`.
349
+ */
350
+ readonly scripts: 'function' | 'string';
330
351
  }
331
352
  /** What a node-local `check` may ask of the checker. */
332
353
  export interface FlowCheckContext {
package/dist/flow-sdk.js CHANGED
@@ -48,6 +48,7 @@ import { checkFlowExpressions, FlowBuildError } from './flow-expr-check.js';
48
48
  import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
49
49
  import { schemaVersionRefusal } from './schema-version.js';
50
50
  import { indexCarriedDefinitions } from './core/carried-definitions.js';
51
+ import { CompileRefusal } from './core/compile-refusal.js';
51
52
  export { FlowBuildError, checkFlowExpressions } from './flow-expr-check.js';
52
53
  const TYPE_NAMES = Object.values(types);
53
54
  /**
@@ -69,7 +70,7 @@ const TYPE_NAMES = Object.values(types);
69
70
  */
70
71
  function assertShape(method, shape) {
71
72
  if (typeof shape !== 'object' || shape === null || Array.isArray(shape)) {
72
- throw new TypeError(`.${method}() takes ONE object mapping names to types — got ${Array.isArray(shape) ? 'an array' : typeof shape}`
73
+ throw new CompileRefusal('DECLARATION_SHAPE', `.${method}() takes ONE object mapping names to types — got ${Array.isArray(shape) ? 'an array' : typeof shape}`
73
74
  + `${typeof shape === 'string' ? ` (${JSON.stringify(shape)})` : ''}. `
74
75
  + `Write .${method}({ ${typeof shape === 'string' ? shape : 'name'}: types.string }), not `
75
76
  + `.${method}('name', 'string'): a string would be iterated character by character and `
@@ -89,12 +90,12 @@ function varDeclOf(method, name, value) {
89
90
  assertType(method, name, spec.type);
90
91
  if (spec.shared !== undefined) {
91
92
  if (method !== 'input') {
92
- throw new TypeError(`.${method}() declared ${JSON.stringify(name)} with { shared }, which only an input takes: it names `
93
+ throw new CompileRefusal('SHARED_INPUT_NOT_INPUT', `.${method}() declared ${JSON.stringify(name)} with { shared }, which only an input takes: it names `
93
94
  + `the flow variable a root's input is copied into. Declare the variable with .var() and feed it from `
94
95
  + `.input({ <name>: { type, shared: ${JSON.stringify(spec.shared)} } }).`);
95
96
  }
96
97
  if (typeof spec.shared !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(spec.shared)) {
97
- throw new TypeError(`Input ${JSON.stringify(name)}: { shared } must be a variable name (letters, digits, _), got `
98
+ throw new CompileRefusal('SHARED_INPUT_NAME_INVALID', `Input ${JSON.stringify(name)}: { shared } must be a variable name (letters, digits, _), got `
98
99
  + `${JSON.stringify(spec.shared)}.`);
99
100
  }
100
101
  }
@@ -114,7 +115,7 @@ function varDeclOf(method, name, value) {
114
115
  function assertType(method, name, type) {
115
116
  if (typeof type === 'string' && TYPE_NAMES.includes(type))
116
117
  return;
117
- throw new TypeError(`.${method}() declared ${JSON.stringify(name)} with type ${JSON.stringify(type)}, which is not `
118
+ throw new CompileRefusal('DECLARATION_TYPE_UNKNOWN', `.${method}() declared ${JSON.stringify(name)} with type ${JSON.stringify(type)}, which is not `
118
119
  + `a type. Use one of ${TYPE_NAMES.map((t) => `types.${t}`).join(', ')}.`);
119
120
  }
120
121
  // ─── Triggers (what starts the flow) ────────────────────────────────────────
@@ -374,7 +375,7 @@ class StepList {
374
375
  */
375
376
  err(field) {
376
377
  if (!this.errorSource) {
377
- throw new TypeError('err(field) with no step name is only available inside an error handler — the sub-builder '
378
+ throw new CompileRefusal('ERROR_READ_OUTSIDE_HANDLER', 'err(field) with no step name is only available inside an error handler — the sub-builder '
378
379
  + '.onError((h) => …) / .stepToList(\'error\', (h) => …) passes. Elsewhere, name the step: '
379
380
  + "err('<step>', 'message').");
380
381
  }
@@ -438,7 +439,7 @@ class StepList {
438
439
  */
439
440
  stepSwitch(name, spec, cases, options) {
440
441
  if (!Array.isArray(cases) || cases.length === 0) {
441
- throw new TypeError(`.stepSwitch('${name}'): needs at least one arm. With nothing to route, use .step().`);
442
+ throw new CompileRefusal('STEP_SWITCH_NO_ARMS', `.stepSwitch('${name}'): needs at least one arm. With nothing to route, use .step().`);
442
443
  }
443
444
  const arms = cases.map((c) => {
444
445
  const arm = new ArmBuilder();
@@ -448,7 +449,7 @@ class StepList {
448
449
  // is already named by the outcome or branch it leaves. Refusing beats
449
450
  // accepting it and dropping it on the floor.
450
451
  if (arm.armLabel !== undefined) {
451
- throw new TypeError(`.stepSwitch('${name}') arm '${c.value}': .label() has nowhere to go — a port edge carries no `
452
+ throw new CompileRefusal('STEP_SWITCH_ARM_LABEL', `.stepSwitch('${name}') arm '${c.value}': .label() has nowhere to go — a port edge carries no `
452
453
  + `label, and the exit is named by "${c.value}" already. Use options.label to rename the NODE.`);
453
454
  }
454
455
  return { value: String(c.value), body: arm.steps };
@@ -551,14 +552,14 @@ class StepList {
551
552
  // version the container emits (#1010).
552
553
  const containerPort = port === 'error' && (source?.kind === 'loop' || source?.kind === 'doWhile');
553
554
  if (!source || (source.kind !== 'action' && !containerPort)) {
554
- throw new TypeError(`${called} leaves a port of the step BEFORE it, and ${source ? `the previous step is a ${source.kind}` : 'there is no previous step'}. `
555
+ throw new CompileRefusal('PORT_NO_SOURCE_STEP', `${called} leaves a port of the step BEFORE it, and ${source ? `the previous step is a ${source.kind}` : 'there is no previous step'}. `
555
556
  + `Write .step('fetch', http({…}))${called}`
556
557
  + ' — a branch, parallel, terminate or return has no port to leave from; a .loop() or .doWhile() has one '
557
558
  + "only for 'error'.");
558
559
  }
559
560
  const taken = attached.some((s) => (s.kind === 'stepToList' || s.kind === 'stepToRef') && s.port === port);
560
561
  if (taken) {
561
- throw new TypeError(`${called} is already set on step "${source.name}"'s "${port}" port. A port takes exactly ONE outgoing edge `
562
+ throw new CompileRefusal('PORT_ALREADY_ROUTED', `${called} is already set on step "${source.name}"'s "${port}" port. A port takes exactly ONE outgoing edge `
562
563
  + `(the definition says so: maxConnections: 1), so put everything that path needs in one place.`);
563
564
  }
564
565
  return source;
@@ -611,7 +612,7 @@ class StepList {
611
612
  h.errorSource = prev.name;
612
613
  bodyFn?.(h);
613
614
  if (h.steps.length === 0) {
614
- throw new TypeError(`${called} on step "${prev.name}" has an empty ${noun}. An edge to nothing is worse than none — `
615
+ throw new CompileRefusal('PORT_EMPTY_PATH', `${called} on step "${prev.name}" has an empty ${noun}. An edge to nothing is worse than none — `
615
616
  + `the path still leaves the main flow. Return a value, terminate the run, or do the compensating work.`);
616
617
  }
617
618
  this.steps.push({ kind: 'stepToList', port, body: h.steps });
@@ -637,7 +638,7 @@ class StepList {
637
638
  */
638
639
  branch(name, cond, thenFn, elseFn, options) {
639
640
  if (options?.updates) {
640
- throw new TypeError('.branch(): { updates } is not supported here — the platform strips variable updates from '
641
+ throw new CompileRefusal('UPDATES_NOT_SUPPORTED', '.branch(): { updates } is not supported here — the platform strips variable updates from '
641
642
  + 'decision/switch/merge nodes on save. Attach them to a step before or after instead.');
642
643
  }
643
644
  const t = new ArmBuilder();
@@ -707,7 +708,7 @@ class StepList {
707
708
  */
708
709
  switch(name, on, cases, defaultFn, options) {
709
710
  if (options?.updates) {
710
- throw new TypeError('.switch(): { updates } is not supported here — the platform strips variable updates from '
711
+ throw new CompileRefusal('UPDATES_NOT_SUPPORTED', '.switch(): { updates } is not supported here — the platform strips variable updates from '
711
712
  + 'decision/switch/merge nodes on save. Attach them to a step before or after instead.');
712
713
  }
713
714
  const arms = (cases ?? []).map((c) => {
@@ -789,7 +790,7 @@ class StepList {
789
790
  */
790
791
  parallel(name, arms, options) {
791
792
  if (options?.updates) {
792
- throw new TypeError('.parallel(): { updates } is not supported here — the platform strips variable updates from '
793
+ throw new CompileRefusal('UPDATES_NOT_SUPPORTED', '.parallel(): { updates } is not supported here — the platform strips variable updates from '
793
794
  + 'decision/switch/merge nodes on save. Attach them to a step before or after instead.');
794
795
  }
795
796
  const built = (arms ?? []).map((armFn) => {
@@ -924,7 +925,7 @@ class StepList {
924
925
  const port = maybeTarget === undefined ? 'output' : portOrTarget;
925
926
  const target = maybeTarget === undefined ? portOrTarget : maybeTarget;
926
927
  if (typeof target !== 'string' || target.length === 0) {
927
- throw new TypeError(`.stepToRef() takes the NAME of the step to continue at — .stepToRef('priceIt') — and got ${JSON.stringify(target)}. `
928
+ throw new CompileRefusal('REF_TARGET_NOT_NAME', `.stepToRef() takes the NAME of the step to continue at — .stepToRef('priceIt') — and got ${JSON.stringify(target)}. `
928
929
  + `It is the same name you passed to .step() (or a .return() End's \`name\`), so the edge can be wired after the whole flow is built.`);
929
930
  }
930
931
  // A non-default port leaves the PRECEDING step, so it is guarded the same way
@@ -1103,7 +1104,7 @@ class FlowBuilder extends StepList {
1103
1104
  */
1104
1105
  trigger(spec, options) {
1105
1106
  if (options?.updates) {
1106
- throw new TypeError('.trigger(): { updates } is not supported — a trigger has not run anything to assign from.');
1107
+ throw new CompileRefusal('UPDATES_NOT_SUPPORTED', '.trigger(): { updates } is not supported — a trigger has not run anything to assign from.');
1107
1108
  }
1108
1109
  this._trigger = spec;
1109
1110
  if (options)
@@ -1263,7 +1264,7 @@ class FlowBuilder extends StepList {
1263
1264
  schemaVersion(version) {
1264
1265
  const refusal = schemaVersionRefusal(FLOW_SCHEMA_VERSIONS, version);
1265
1266
  if (refusal)
1266
- throw new Error(refusal);
1267
+ throw new CompileRefusal('SCHEMA_VERSION_UNSUPPORTED', refusal);
1267
1268
  this._schemaVersion = version;
1268
1269
  return this;
1269
1270
  }
@@ -44,7 +44,7 @@ export function childStepLists(step) {
44
44
  return [];
45
45
  default: {
46
46
  const unhandled = step;
47
- throw new Error(`childStepLists: unhandled step kind ${JSON.stringify(unhandled.kind)}`);
47
+ throw new Error(`internal SDK error: childStepLists: unhandled step kind ${JSON.stringify(unhandled.kind)}`);
48
48
  }
49
49
  }
50
50
  }
@@ -1,4 +1,5 @@
1
1
  import { globalNameCollisionMessage, globalNameCollisions } from './core/global-names.js';
2
+ import { CompileRefusal } from './core/compile-refusal.js';
2
3
  /** The keys of `variables` that hold declarations rather than name one (the map spelling). */
3
4
  export const CONTAINER_KEYS = new Set(['globals', 'nodes', 'variableUpdates']);
4
5
  const DIRECTIONS = new Set(['in', 'out', 'inout']);
@@ -8,7 +9,7 @@ export function readFlowGlobals(variables, strict = false) {
8
9
  return [];
9
10
  if (typeof variables !== 'object' || Array.isArray(variables)) {
10
11
  if (strict)
11
- throw new Error('decompile: variables must be an object.');
12
+ throw new CompileRefusal('DECOMPILE_VARIABLES_SHAPE', 'decompile: variables must be an object.');
12
13
  return [];
13
14
  }
14
15
  const source = variables;
@@ -37,7 +38,7 @@ export function readFlowGlobals(variables, strict = false) {
37
38
  }
38
39
  }
39
40
  if (strict && unknown.length > 0) {
40
- throw new Error(`decompile: unrecognized variables key${unknown.length === 1 ? '' : 's'}: `
41
+ throw new CompileRefusal('DECOMPILE_VARIABLES_SHAPE', `decompile: unrecognized variables key${unknown.length === 1 ? '' : 's'}: `
41
42
  + `${unknown.map((key) => JSON.stringify(key)).join(', ')}.`);
42
43
  }
43
44
  return globals;
package/dist/load.js CHANGED
@@ -3,6 +3,9 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
3
3
  import { basename, dirname, join, resolve } from 'node:path';
4
4
  import { statSync } from 'node:fs';
5
5
  import { useThisSdkForAuthoredSource } from './sdk-resolve.js';
6
+ import { scriptFunctionLowerings } from './core/script-function.js';
7
+ import { scriptFunctionScopeProblem } from './script-function-scope.js';
8
+ import { CompileRefusal } from './core/compile-refusal.js';
6
9
  /** Resolve a user argument (a `.ts` path or a BaseName) to a flow source file. */
7
10
  export function resolveFlowFile(arg) {
8
11
  const candidates = [arg, `${arg}.flow.ts`, `${arg}.ts`];
@@ -17,10 +20,11 @@ export function resolveFlowFile(arg) {
17
20
  }
18
21
  return resolvedCandidate;
19
22
  }
20
- throw new Error(`flow source not found: tried ${candidates.join(', ')}`);
23
+ throw new CompileRefusal('FLOW_SOURCE_NOT_FOUND', `flow source not found: tried ${candidates.join(', ')}`);
21
24
  }
22
25
  export async function loadBuiltFlow(file) {
23
26
  useThisSdkForAuthoredSource();
27
+ const firstLowering = scriptFunctionLowerings().length;
24
28
  let mod;
25
29
  try {
26
30
  mod = await import(pathToFileURL(file).href);
@@ -30,10 +34,20 @@ export async function loadBuiltFlow(file) {
30
34
  }
31
35
  const exp = mod.default ?? mod.flow ?? mod.workflow;
32
36
  if (!exp)
33
- throw new Error(`${file} has no default export. Add: export default flow('id')...build();`);
37
+ throw new CompileRefusal('FLOW_NO_DEFAULT_EXPORT', `${file} has no default export. Add: export default flow('id')...build();`);
34
38
  const built = typeof exp.build === 'function' ? exp.build() : exp;
35
39
  if (!built || !Array.isArray(built.steps))
36
- throw new Error(`${file} default export is not a built flow. Did you call .build()?`);
40
+ throw new CompileRefusal('FLOW_EXPORT_NOT_BUILT', `${file} default export is not a built flow. Did you call .build()?`);
41
+ // A `script(fn)` function was lowered from its own text, which cannot show a
42
+ // name it takes from its module; this source can (script-function-scope.ts).
43
+ // Only a flow that lowered one pays for the program this builds.
44
+ // The functions this import lowered; see scriptFunctionLowerings.
45
+ const lowered = scriptFunctionLowerings().slice(firstLowering);
46
+ if (lowered.length > 0) {
47
+ const problem = scriptFunctionScopeProblem(file, lowered);
48
+ if (problem)
49
+ throw new CompileRefusal('SCRIPT_FUNCTION_SCOPE', problem);
50
+ }
37
51
  return built;
38
52
  }
39
53
  /**
@@ -83,7 +97,7 @@ export function connectorImportDiagnostic(error) {
83
97
  return undefined;
84
98
  }
85
99
  if (parent === 'connectors') {
86
- return new Error(`connector import "${unresolved}" does not name a file. The generated ` +
100
+ return new CompileRefusal('CONNECTOR_IMPORT_NOT_A_FILE', `connector import "${unresolved}" does not name a file. The generated ` +
87
101
  `descriptors are TypeScript, so the specifier keeps its real extension; ` +
88
102
  `import "${moduleFile}"`);
89
103
  }
@@ -91,7 +105,7 @@ export function connectorImportDiagnostic(error) {
91
105
  if (!statSync(descriptorData, { throwIfNoEntry: false })?.isDirectory()) {
92
106
  return undefined;
93
107
  }
94
- return new Error(`connector import "${unresolved}" does not name a file; module candidate ` +
108
+ return new CompileRefusal('CONNECTOR_IMPORT_NOT_A_FILE', `connector import "${unresolved}" does not name a file; module candidate ` +
95
109
  `"${moduleFile}" and descriptor data "${descriptorData}" are separate; ` +
96
110
  `import "${moduleFile}"`);
97
111
  }
@@ -10,6 +10,7 @@ import { processResourceBindings } from '../../../core/process-resource.js';
10
10
  import { fnv1aX4Uuid, schemaId } from '../../../core/stable-id.js';
11
11
  import { synthesizedDefinitionRefusal } from '../../../core/synthesized-definition.js';
12
12
  import { BUILTIN_TOOL_DEFS, INLINE_AGENT, PROCESS_TOOL_META, a2aToolDef, builtinToolResourceJson, connectorToolDef, connectorToolDescription, connectorToolDetail, connectorToolResourceJson, contextIndexDef, contextResourceJson, declaresMemoryHandle, escalationAppFolder, escalationDef, escalationResourceJson, fieldSchema, httpRequestToolResourceJson, inlineAgentDef, ixpToolDef, mcpToolDef, memoryResourceDef, processToolDef, renderContextQuery, toolResourceJson, } from './resources.js';
13
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
13
14
  /** Every bundled version of the inline agent, oldest first; the newest is the default (#956). */
14
15
  export const INLINE_AGENT_VERSIONS = ['1.2', '1.3', '1.4'];
15
16
  export const INLINE_AGENT_DEFAULT_VERSION = INLINE_AGENT_VERSIONS[INLINE_AGENT_VERSIONS.length - 1];
@@ -39,7 +40,7 @@ export function serializeInlineAgent(s, ctx) {
39
40
  if (!def)
40
41
  throw new Error(`internal SDK error: step "${step}": no definition resolves for ${INLINE_AGENT}@${version}.`);
41
42
  if (s.memory !== undefined && !declaresMemoryHandle(def)) {
42
- throw new Error(`"${step}" attaches \`memory\`, but ${INLINE_AGENT}@${version} declares no \`memory\` handle. `
43
+ throw new CompileRefusal('AGENT_MEMORY_VERSION_NO_HANDLE', `"${step}" attaches \`memory\`, but ${INLINE_AGENT}@${version} declares no \`memory\` handle. `
43
44
  + (version === INLINE_AGENT_DEFAULT_VERSION
44
45
  ? `Pin a version that has it with inlineAgent({ ..., version: '<x.y>' }) `
45
46
  + `(bundled with memory: ${INLINE_AGENT_VERSIONS.filter((v) => declaresMemoryHandle(ctx.definition(INLINE_AGENT, v))).join(', ') || 'none'}), or drop \`memory\`.`
@@ -134,8 +135,8 @@ export function serializeInlineAgent(s, ctx) {
134
135
  // the type `…context.index.<slug>.`, which no tenant serves, and nothing
135
136
  // after this point would say so.
136
137
  if (typeof cx?.id !== 'string' || !GUID_RE.test(cx.id)) {
137
- throw Object.assign(new Error(`inline agent "${step}": context index "${cx?.name ?? ''}" needs its index id (a GUID) as \`id\`; `
138
- + `got ${JSON.stringify(cx?.id)}. \`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`), { code: 'AGENT_CONTEXT_NO_ID' });
138
+ throw new CompileRefusal('AGENT_CONTEXT_NO_ID', `inline agent "${step}": context index "${cx?.name ?? ''}" needs its index id (a GUID) as \`id\`; `
139
+ + `got ${JSON.stringify(cx?.id)}. \`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`);
139
140
  }
140
141
  const cdef = contextIndexDef({ name: cx.name, id: cx.id });
141
142
  // `<step>Context`, deduped for a second index on the same agent, unless the
@@ -207,7 +208,7 @@ export function serializeInlineAgent(s, ctx) {
207
208
  const bdef = ownEntry(BUILTIN_TOOL_DEFS, tr.tool);
208
209
  tdef = bdef === undefined ? undefined : jsonClone(bdef);
209
210
  if (!tdef)
210
- throw new Error(`inline agent "${step}": unknown builtin tool "${tr.tool}" (analyzefiles | summarize | batchtransform).`);
211
+ throw new CompileRefusal('INLINE_AGENT_TOOL_UNKNOWN_BUILTIN', `inline agent "${step}": unknown builtin tool "${tr.tool}" (analyzefiles | summarize | batchtransform).`);
211
212
  label = tdef.display.label;
212
213
  // The designer-placed node carries the def's `inputDefaults` with the
213
214
  // author's values over them — the context rule, kept per builtin (each def
@@ -242,7 +243,7 @@ export function serializeInlineAgent(s, ctx) {
242
243
  }
243
244
  else if (tr.kind === 'connector') {
244
245
  if (!ctx.library)
245
- throw new Error(`inline agent "${step}"'s connector tool needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
246
+ throw new CompileRefusal('LIBRARY_REQUIRED', `inline agent "${step}"'s connector tool needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
246
247
  const rc = ctx.library.resolve(tr.connector, tr.operation, tr.version, tr.object);
247
248
  // An entry `prepare` synthesized from Integration Service compiles in BPMN
248
249
  // only — for a tool as for a step: `connectorToolDef` reads the same
@@ -337,7 +338,7 @@ export function serializeInlineAgent(s, ctx) {
337
338
  else {
338
339
  const meta = ownEntry(PROCESS_TOOL_META, tr.kind);
339
340
  if (!meta)
340
- throw new Error(`inline agent "${step}": unknown tool kind "${tr.kind}".`);
341
+ throw new CompileRefusal('INLINE_AGENT_TOOL_UNKNOWN_KIND', `inline agent "${step}": unknown tool kind "${tr.kind}".`);
341
342
  tdef = processToolDef(tr.kind, tr);
342
343
  label = tr.name;
343
344
  // The definition's `model.context` names `<bindings.name>` /
@@ -9,6 +9,7 @@ import { jsonClone } from '../../../core/json-clone.js';
9
9
  import { subNodeId } from '../../../core/node-id-placeholder.js';
10
10
  import { synthesizedDefinitionRefusal } from '../../../core/synthesized-definition.js';
11
11
  import { tsLiteral, tsObject } from '../../../core/ts-literal.js';
12
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
12
13
  const CONNECTOR_PREFIX = 'uipath.connector.';
13
14
  /** `<key>.<action>` after the prefix, split on the last dot; undefined for an event, a trigger, or a name with no action. */
14
15
  function splitOperation(typeName) {
@@ -65,7 +66,7 @@ function listOutputs(rc, nodeId) {
65
66
  function serializeConnector(spec, ctx) {
66
67
  const step = ctx.step;
67
68
  if (!ctx.library)
68
- throw new Error(`connector "${step}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
69
+ throw new CompileRefusal('LIBRARY_REQUIRED', `connector "${step}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
69
70
  let rc;
70
71
  try {
71
72
  rc = ctx.library.resolve(spec.key, spec.action, spec.version, spec.object);
@@ -5,6 +5,7 @@
5
5
  * them, so authors import them exactly as before.
6
6
  */
7
7
  import { builtinActionSpec } from '../../../core/actions.js';
8
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
8
9
  /**
9
10
  * Query entity records from Data Fabric (`core.datafabric.read`). The result
10
11
  * publishes to the step's `output`; downstream references re-fetch fresh data
@@ -129,6 +130,6 @@ function assertOneRecordRef(record, factory) {
129
130
  const byId = record?.byId !== undefined;
130
131
  const fromRead = typeof record?.fromRead === 'string';
131
132
  if (byId === fromRead) {
132
- throw new Error(`${factory}() targets exactly ONE of record: { byId } or record: { fromRead: '<step>' }.`);
133
+ throw new CompileRefusal('DATAFABRIC_RECORD_REF', `${factory}() targets exactly ONE of record: { byId } or record: { fromRead: '<step>' }.`);
133
134
  }
134
135
  }
@@ -1,6 +1,7 @@
1
1
  import { applyScope, baseConfig, entityConfigOf, fieldRows, filterRows, liftFields, liftRecord, liftScope, liftValue, printFields, printRecord, scopeOptions, targetRecord, } from './shared.js';
2
2
  import { checkCreate, checkRead, checkUpdate, checkWrite } from './check.js';
3
3
  import { rawNodeAdvice } from './raw.js';
4
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
4
5
  /**
5
6
  * Every Data Fabric node carries no node-level `model`, whichever path emits it
6
7
  * (a verb, or a rawNode of its definition). Studio Web never writes one
@@ -36,7 +37,7 @@ function readAt(version) {
36
37
  nodeModel: NODE_MODEL, rawNode: READ_ADVICE,
37
38
  serialize: ({ inputs }, ctx) => {
38
39
  if (version === '1.0' && inputs.resultMode !== undefined) {
39
- throw new Error("dataFabricRead(): { resultMode } needs the node's 1.4 definition, but { version: '1.0' } was "
40
+ throw new CompileRefusal('DATAFABRIC_RESULT_MODE_VERSION', "dataFabricRead(): { resultMode } needs the node's 1.4 definition, but { version: '1.0' } was "
40
41
  + 'requested. Drop the version, request 1.4, or drop resultMode.');
41
42
  }
42
43
  const cfg = baseConfig(inputs);
@@ -3,6 +3,7 @@
3
3
  * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
4
4
  */
5
5
  import { builtinActionSpec } from '../../../core/actions.js';
6
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
6
7
  /**
7
8
  * Declare a delay — a step that pauses the flow for a fixed length of time and
8
9
  * then carries on.
@@ -31,7 +32,7 @@ export function delay(inputs) {
31
32
  const hasDuration = 'duration' in inputs;
32
33
  const hasUntil = 'until' in inputs;
33
34
  if (hasDuration === hasUntil) {
34
- throw new Error("delay() takes exactly ONE of { duration } (an ISO-8601 duration, e.g. 'PT15M') or "
35
+ throw new CompileRefusal('DELAY_DURATION_XOR_UNTIL', "delay() takes exactly ONE of { duration } (an ISO-8601 duration, e.g. 'PT15M') or "
35
36
  + "{ until } (an ISO-8601 date-time, e.g. '2026-09-01T09:00:00Z').");
36
37
  }
37
38
  return builtinActionSpec({ kind: 'delay', inputs });
@@ -14,6 +14,7 @@ import { definitionHasErrorPort } from '../../../core/error-port.js';
14
14
  import { Expr } from '../../../core/expr.js';
15
15
  import { jsonClone } from '../../../core/json-clone.js';
16
16
  import { parseIxpProjectType } from './spec.js';
17
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
17
18
  const IXP_PREFIX = 'uipath.ixp.';
18
19
  export const IXP_DIAGNOSTICS = {
19
20
  IXP_FORBIDDEN_INPUT: { severity: 'error', summary: 'an IxP step setting an input the current node does not declare' },
@@ -65,7 +66,10 @@ const IXP_DEF = ixpDefinition;
65
66
  export function ixpExtractDef(spec) {
66
67
  const parts = parseIxpProjectType(spec.project);
67
68
  if (!parts) {
68
- throw new Error(`ixpExtract: "${spec.project}" is not a resolvable extractor node type. It must be `
69
+ // The three conditions check tells apart, under check's three codes.
70
+ const code = typeof spec.project !== 'string' || spec.project.trim() === '' ? 'IXP_NO_PROJECT'
71
+ : !spec.project.startsWith('uipath.ixp.') ? 'IXP_PROJECT_NOT_NODE_TYPE' : 'IXP_PROJECT_TAIL_UNPARSEABLE';
72
+ throw new CompileRefusal(code, `ixpExtract: "${spec.project}" is not a resolvable extractor node type. It must be `
69
73
  + `uipath.ixp.<slug>.<projectVersionId>-<folderKey> — copy it from `
70
74
  + `\`uip maestro flow registry pull --force && uip maestro flow registry search -f "type:startsWith=uipath.ixp"\`.`);
71
75
  }
@@ -7,7 +7,8 @@ export const HITL_DIAGNOSTICS = {
7
7
  HITL_VARIANT_UNKNOWN: { severity: 'error', summary: 'a variant that names no human-task node type' },
8
8
  HITL_OUTCOME_PORTS_VARIANT: { severity: 'error', summary: 'exposeError or outcomePorts on a variant that cannot honour it' },
9
9
  HITL_LEGACY_COMPLETED: { severity: 'warning', summary: "a task with outcomes that leaves on the legacy 'completed' handle" },
10
- HITL_LEGACY_COMPLETED_CONFLICT: { severity: 'error', summary: 'legacyCompleted with outcomePorts or exposeError, or where it changes nothing' },
10
+ HITL_LEGACY_COMPLETED_CONFLICT: { severity: 'error', summary: 'legacyCompleted with outcomePorts or exposeError' },
11
+ HITL_LEGACY_COMPLETED_NO_EFFECT: { severity: 'error', summary: "legacyCompleted where 'completed' is already the only exit" },
11
12
  HITL_DOCUMENT_WITHOUT_VARIANT: { severity: 'error', summary: 'a document on a task that is not a validation station' },
12
13
  HITL_DOCVAL_WITH_FIELDS: { severity: 'error', summary: 'form fields on a validation station' },
13
14
  HITL_DOCVAL_NO_DOCUMENT: { severity: 'error', summary: 'a validation station with no document' },
@@ -103,7 +104,8 @@ export function checkHitl(inputs, ctx) {
103
104
  return;
104
105
  }
105
106
  if (!hitlDerivesOutcomeHandles(variant) || outcomes.length === 0) {
106
- at('HITL_LEGACY_COMPLETED_CONFLICT', `Human task "${step}" sets legacyCompleted: true, but ${outcomes.length === 0 ? 'it has no outcomes'
107
+ // Its own code: this flow compiles, so the code is check's alone.
108
+ at('HITL_LEGACY_COMPLETED_NO_EFFECT', `Human task "${step}" sets legacyCompleted: true, but ${outcomes.length === 0 ? 'it has no outcomes'
107
109
  : `its variant is "${variant}", which reads its outcomes from its app`}, so 'completed' is already its only exit.`, 'Drop `legacyCompleted`.');
108
110
  return;
109
111
  }