@uipath/maestro-builder-sdk 6.57.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 (59) hide show
  1. package/dist/case/subscription.d.ts +4 -2
  2. package/dist/case/subscription.js +5 -2
  3. package/dist/core/actions.js +2 -2
  4. package/dist/core/binding-messages.d.ts +2 -1
  5. package/dist/core/binding-messages.js +3 -4
  6. package/dist/core/byoa.d.ts +3 -2
  7. package/dist/core/byoa.js +3 -3
  8. package/dist/core/carried-definitions.d.ts +0 -18
  9. package/dist/core/carried-definitions.js +3 -2
  10. package/dist/core/ceql.d.ts +2 -1
  11. package/dist/core/ceql.js +3 -2
  12. package/dist/core/compile-refusal.d.ts +21 -0
  13. package/dist/core/compile-refusal.js +26 -0
  14. package/dist/core/connector-inputs.js +2 -1
  15. package/dist/core/connector-resolve-error.d.ts +2 -1
  16. package/dist/core/connector-resolve-error.js +3 -4
  17. package/dist/core/connectors.js +2 -1
  18. package/dist/core/event-filters.js +2 -1
  19. package/dist/core/hitl-routing.d.ts +7 -82
  20. package/dist/core/hitl-routing.js +95 -6
  21. package/dist/core/library.js +4 -3
  22. package/dist/core/lookups.js +6 -5
  23. package/dist/core/nodes/registry.js +3 -2
  24. package/dist/core/nodes/versioned.js +3 -2
  25. package/dist/core/process-resource-sort-order.js +1 -1
  26. package/dist/core/read-text.d.ts +0 -28
  27. package/dist/core/read-text.js +3 -2
  28. package/dist/core/refusals.d.ts +465 -0
  29. package/dist/core/refusals.js +504 -0
  30. package/dist/core/script-function.js +2 -1
  31. package/dist/core/typescript-module.js +2 -1
  32. package/dist/flow-expr-check.d.ts +2 -1
  33. package/dist/flow-expr-check.js +4 -2
  34. package/dist/flow-sdk.js +17 -16
  35. package/dist/flow-step-tree.js +1 -1
  36. package/dist/flow-variables.js +3 -2
  37. package/dist/load.js +7 -6
  38. package/dist/nodes/agent/flow/inline-agent.js +7 -6
  39. package/dist/nodes/connector/flow/node-type.js +2 -1
  40. package/dist/nodes/data-fabric/flow/factories.js +2 -1
  41. package/dist/nodes/data-fabric/flow/verbs.js +2 -1
  42. package/dist/nodes/delay/flow/factories.js +2 -1
  43. package/dist/nodes/document/flow/ixp.js +5 -1
  44. package/dist/nodes/hitl/flow/check.js +4 -2
  45. package/dist/nodes/hitl/flow/node-type.js +6 -5
  46. package/dist/nodes/http/flow/factories.js +2 -1
  47. package/dist/nodes/http/flow/node-type.js +6 -5
  48. package/dist/nodes/process/flow/factories.js +2 -1
  49. package/dist/nodes/queue/flow/node-type.js +3 -2
  50. package/dist/nodes/transform/flow/node-type.js +2 -1
  51. package/dist/raw-node.js +3 -2
  52. package/dist/ref-resolve.d.ts +0 -36
  53. package/dist/ref-resolve.js +9 -8
  54. package/dist/schema-version.d.ts +0 -38
  55. package/dist/schema-version.js +3 -2
  56. package/dist/sdk-resolve.js +2 -1
  57. package/dist/serialize.d.ts +5 -2
  58. package/dist/serialize.js +115 -83
  59. package/package.json +2 -2
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
@@ -5,6 +5,7 @@ import { statSync } from 'node:fs';
5
5
  import { useThisSdkForAuthoredSource } from './sdk-resolve.js';
6
6
  import { scriptFunctionLowerings } from './core/script-function.js';
7
7
  import { scriptFunctionScopeProblem } from './script-function-scope.js';
8
+ import { CompileRefusal } from './core/compile-refusal.js';
8
9
  /** Resolve a user argument (a `.ts` path or a BaseName) to a flow source file. */
9
10
  export function resolveFlowFile(arg) {
10
11
  const candidates = [arg, `${arg}.flow.ts`, `${arg}.ts`];
@@ -19,7 +20,7 @@ export function resolveFlowFile(arg) {
19
20
  }
20
21
  return resolvedCandidate;
21
22
  }
22
- 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(', ')}`);
23
24
  }
24
25
  export async function loadBuiltFlow(file) {
25
26
  useThisSdkForAuthoredSource();
@@ -33,10 +34,10 @@ export async function loadBuiltFlow(file) {
33
34
  }
34
35
  const exp = mod.default ?? mod.flow ?? mod.workflow;
35
36
  if (!exp)
36
- 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();`);
37
38
  const built = typeof exp.build === 'function' ? exp.build() : exp;
38
39
  if (!built || !Array.isArray(built.steps))
39
- 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()?`);
40
41
  // A `script(fn)` function was lowered from its own text, which cannot show a
41
42
  // name it takes from its module; this source can (script-function-scope.ts).
42
43
  // Only a flow that lowered one pays for the program this builds.
@@ -45,7 +46,7 @@ export async function loadBuiltFlow(file) {
45
46
  if (lowered.length > 0) {
46
47
  const problem = scriptFunctionScopeProblem(file, lowered);
47
48
  if (problem)
48
- throw new Error(problem);
49
+ throw new CompileRefusal('SCRIPT_FUNCTION_SCOPE', problem);
49
50
  }
50
51
  return built;
51
52
  }
@@ -96,7 +97,7 @@ export function connectorImportDiagnostic(error) {
96
97
  return undefined;
97
98
  }
98
99
  if (parent === 'connectors') {
99
- 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 ` +
100
101
  `descriptors are TypeScript, so the specifier keeps its real extension; ` +
101
102
  `import "${moduleFile}"`);
102
103
  }
@@ -104,7 +105,7 @@ export function connectorImportDiagnostic(error) {
104
105
  if (!statSync(descriptorData, { throwIfNoEntry: false })?.isDirectory()) {
105
106
  return undefined;
106
107
  }
107
- 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 ` +
108
109
  `"${moduleFile}" and descriptor data "${descriptorData}" are separate; ` +
109
110
  `import "${moduleFile}"`);
110
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
  }
@@ -20,6 +20,7 @@ import { jsonClone } from '../../../core/json-clone.js';
20
20
  import { schemaId } from '../../../core/stable-id.js';
21
21
  import { wireExprBody } from '../../../core/wire-expr.js';
22
22
  import { checkHitl, checkHitlRead } from './check.js';
23
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
23
24
  const BASE = 'uipath.human-in-the-loop';
24
25
  const QUICK_FORM = 'uipath.human-in-the-loop.quick-form';
25
26
  const ACTION_APP = 'uipath.human-in-the-loop.coded-action-app';
@@ -556,23 +557,23 @@ function hitlAt(typeName, version, factory) {
556
557
  const pinned = ctx.pinnedVersion;
557
558
  const exitModel = hitlExitModel(inputs, pinned);
558
559
  if (typeName !== BASE && inputs?.exposeError === true) {
559
- throw new Error(`"${ctx.step}": exposeError needs the base human task's 1.2 definition, which adds the `
560
+ throw new CompileRefusal('HITL_OUTCOME_PORTS_VARIANT', `"${ctx.step}": exposeError needs the base human task's 1.2 definition, which adds the `
560
561
  + `error output — the '${inputs?.variant}' variant has no such version. Drop the variant or exposeError.`);
561
562
  }
562
563
  if (inputs?.outcomePorts === true && !OUTCOME_HANDLE_NODE_TYPES.has(typeName)) {
563
- throw new Error(`"${ctx.step}": outcomePorts: true, but the '${inputs?.variant}' variant reads its outcomes `
564
+ throw new CompileRefusal('HITL_OUTCOME_PORTS_VARIANT', `"${ctx.step}": outcomePorts: true, but the '${inputs?.variant}' variant reads its outcomes `
564
565
  + `from its app and leaves on its single 'completed' exit. Route on out('${ctx.step}', 'Action') downstream.`);
565
566
  }
566
567
  if (inputs?.legacyCompleted === true && (inputs?.outcomePorts !== undefined || inputs?.exposeError === true)) {
567
- throw new Error(`"${ctx.step}": legacyCompleted: true leaves the task on its single 'completed' handle, so it cannot also `
568
+ throw new CompileRefusal('HITL_LEGACY_COMPLETED_CONFLICT', `"${ctx.step}": legacyCompleted: true leaves the task on its single 'completed' handle, so it cannot also `
568
569
  + `${inputs?.exposeError === true ? 'expose the error output (exposeError)' : 'set outcomePorts'}. Drop one.`);
569
570
  }
570
571
  if (inputs?.legacyCompleted === true && typeName === BASE && (pinned === '1.1' || pinned === '1.2')) {
571
- throw new Error(`"${ctx.step}": legacyCompleted: true, but { version: '${pinned}' } pins the task's per-outcome definition, `
572
+ throw new CompileRefusal('HITL_LEGACY_COMPLETED_VERSION', `"${ctx.step}": legacyCompleted: true, but { version: '${pinned}' } pins the task's per-outcome definition, `
572
573
  + `which has no 'completed' handle (flow validate refuses the edge). Drop the pin or legacyCompleted.`);
573
574
  }
574
575
  if (inputs?.exposeError === true && pinned !== undefined && pinned !== '1.2') {
575
- throw new Error(`"${ctx.step}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
576
+ throw new CompileRefusal('HITL_EXPOSE_ERROR_VERSION', `"${ctx.step}": exposeError needs the task's 1.2 definition, but { version: '${pinned}' } pins it. `
576
577
  + `Drop the pin or match it.`);
577
578
  }
578
579
  const { inputs: nodeInputs, outputs } = hitlInputs(inputs, typeName, ctx);
@@ -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 an HTTP request action, explicitly choosing standalone or managed.
8
9
  *
@@ -12,7 +13,7 @@ import { builtinActionSpec } from '../../../core/actions.js';
12
13
  */
13
14
  export function http(inputs) {
14
15
  if (typeof inputs.managed !== 'boolean') {
15
- throw new Error('http() requires managed: true or managed: false because they emit different platform nodes. '
16
+ throw new CompileRefusal('HTTP_MANAGED_REQUIRED', 'http() requires managed: true or managed: false because they emit different platform nodes. '
16
17
  + 'Use true for Managed HTTP Request/core.action.http.v2 or when non-2xx statuses must reach .onError(); '
17
18
  + 'use false for the standalone core.action.http node.');
18
19
  }
@@ -12,6 +12,7 @@ import { MANAGED_HTTP_ACTIVITY_TYPE_ID, MANAGED_HTTP_ESSENTIAL_CONFIGURATION, MA
12
12
  import { isRecord } from '../../../core/node-envelope.js';
13
13
  import { wireExprBody } from '../../../core/wire-expr.js';
14
14
  import { checkHttpRead, checkHttpStep, plainHttpOnErrorMessage } from './check.js';
15
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
15
16
  const PLAIN = 'core.action.http';
16
17
  const MANAGED = 'core.action.http.v2';
17
18
  /** The definition's `inputDefaults` at the version being emitted. */
@@ -67,7 +68,7 @@ function plainHttpInputs(i, ctx) {
67
68
  if (i.connection !== undefined
68
69
  || i.folder !== undefined
69
70
  || i.targetConnector !== undefined) {
70
- throw new Error('standalone http cannot use Integration Service connection settings; set managed: true');
71
+ throw new CompileRefusal('HTTP_BINDING_REQUIRES_MANAGED', 'standalone http cannot use Integration Service connection settings; set managed: true');
71
72
  }
72
73
  return {
73
74
  ...defaults(ctx, PLAIN),
@@ -91,7 +92,7 @@ function managedHttpBinding(i, ctx) {
91
92
  return undefined;
92
93
  if (typeof i.connection !== 'string' || i.connection.trim() === ''
93
94
  || typeof i.folder !== 'string' || i.folder.trim() === '') {
94
- throw new Error('managed http connection mode requires both a non-empty connection and folder');
95
+ throw new CompileRefusal('HTTP_MANAGED_BINDING_INCOMPLETE', 'managed http connection mode requires both a non-empty connection and folder');
95
96
  }
96
97
  // The same resolution a connector step gets: a self-referential binding is an
97
98
  // error, a stub is a warning.
@@ -121,11 +122,11 @@ function managedHttpBinding(i, ctx) {
121
122
  */
122
123
  function managedHttpInputs(i, binding, ctx) {
123
124
  if (i.targetConnector !== undefined && !binding) {
124
- throw new Error('managed http targetConnector requires both a non-empty connection and folder');
125
+ throw new CompileRefusal('HTTP_TARGET_CONNECTOR_REQUIRES_BINDING', 'managed http targetConnector requires both a non-empty connection and folder');
125
126
  }
126
127
  if (i.targetConnector !== undefined
127
128
  && (typeof i.targetConnector !== 'string' || i.targetConnector.trim() === '')) {
128
- throw new Error('managed http targetConnector must be a non-empty connector key');
129
+ throw new CompileRefusal('HTTP_TARGET_CONNECTOR_EMPTY', 'managed http targetConnector must be a non-empty connector key');
129
130
  }
130
131
  const url = ctx.render(i.url);
131
132
  const bodyParameters = {
@@ -270,7 +271,7 @@ function liftHttp(node, ctx, managed) {
270
271
  const connectionValue = detail.connectionId ?? detail.connectionResourceId ?? req.connection;
271
272
  const retry = inputs.retryCount !== undefined && Number(inputs.retryCount) !== 0 ? Number(inputs.retryCount) : undefined;
272
273
  if (retry !== undefined && !Number.isFinite(retry)) {
273
- throw new Error(`decompile: expected a number, got ${JSON.stringify(inputs.retryCount)}.`);
274
+ throw new CompileRefusal('DECOMPILE_NOT_A_NUMBER', `decompile: expected a number, got ${JSON.stringify(inputs.retryCount)}.`);
274
275
  }
275
276
  const branchRows = Array.isArray(inputs.branches) ? inputs.branches : [];
276
277
  const returns = liftReturns(node) ?? returnsFromReads(ctx.reads());
@@ -4,6 +4,7 @@
4
4
  * docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports them.
5
5
  */
6
6
  import { builtinActionSpec } from '../../../core/actions.js';
7
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
7
8
  /**
8
9
  * Invoke a deployed RPA (robotic process automation) workflow — a classic
9
10
  * UI-automation process, published to Orchestrator, run by a robot.
@@ -166,7 +167,7 @@ export function agenticProcess(inputs) {
166
167
  // code — and silently dropping the returns would validate a read that can
167
168
  // never be satisfied.
168
169
  if (inputs.completion === 'fire-and-forget' && inputs.returns !== undefined) {
169
- throw new Error("agenticProcess(): completion 'fire-and-forget' publishes no output — remove `returns`, "
170
+ throw new CompileRefusal('FIRE_AND_FORGET_RETURNS', "agenticProcess(): completion 'fire-and-forget' publishes no output — remove `returns`, "
170
171
  + "or use completion: 'wait' to read the process result.");
171
172
  }
172
173
  return builtinActionSpec({ kind: 'agenticProcess', inputs });
@@ -3,6 +3,7 @@ import { jsonClone } from '../../../core/json-clone.js';
3
3
  import { isRecord } from '../../../core/node-envelope.js';
4
4
  import { checkQueueItem, itemDataIsString, queueItemExprMessage } from './check.js';
5
5
  import { QUEUE_ITEM_RECORD_SCHEMA } from './record.js';
6
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
6
7
  const CREATE = 'core.action.queue.create';
7
8
  const CREATE_AND_WAIT = 'core.action.queue.create-and-wait';
8
9
  /**
@@ -39,7 +40,7 @@ function queueItemDef(spec, nodeType, stepName, ids, base) {
39
40
  // The template at the version being emitted: the registry resolved it exactly
40
41
  // (an unregistered pin is NODE_VERSION_UNREGISTERED before this runs).
41
42
  if (!base)
42
- throw new Error(`no bundled definition for "${nodeType}"`);
43
+ throw new Error(`internal SDK error: no bundled definition for "${nodeType}"`);
43
44
  const def = jsonClone(base);
44
45
  def.model.bindings.resourceKey = spec.key;
45
46
  def.model.context = [
@@ -172,7 +173,7 @@ function itemDataString(item, step, typeName, version) {
172
173
  return '';
173
174
  for (const [k, v] of rows) {
174
175
  if (v instanceof Expr)
175
- throw new Error(queueItemExprMessage(step, typeName, version, k));
176
+ throw new CompileRefusal('QUEUE_ITEM_EXPR_UNSUPPORTED', queueItemExprMessage(step, typeName, version, k));
176
177
  }
177
178
  return JSON.stringify(item);
178
179
  }
@@ -11,6 +11,7 @@ import { wireExprBody } from '../../../core/wire-expr.js';
11
11
  import { jsonClone } from '../../../core/json-clone.js';
12
12
  import { VARIANT_OPERATION } from './spec.js';
13
13
  import { checkTransform } from './check.js';
14
+ import { CompileRefusal } from '../../../core/compile-refusal.js';
14
15
  /**
15
16
  * Emit a transform's `inputs` in the canonical shape the node's runtime reads:
16
17
  * `collection` as a BARE variable path (not `=js:`-prefixed — it is a path
@@ -210,7 +211,7 @@ function transformAt(typeName, variant, version, factory) {
210
211
  // (TRANSFORM_VARIANT_UNKNOWN). A compile that skipped check refuses here
211
212
  // rather than emit the generic node, which would run and be the wrong node.
212
213
  if (variant === undefined && inputs?.variant !== undefined && inputs.variant !== null) {
213
- throw new Error(`transform step "${ctx.step}": variant ${JSON.stringify(inputs.variant)} is not a Transform node type `
214
+ throw new CompileRefusal('TRANSFORM_VARIANT_UNKNOWN', `transform step "${ctx.step}": variant ${JSON.stringify(inputs.variant)} is not a Transform node type `
214
215
  + `(${VARIANTS.join(', ')}), so there is no node to emit. Fix the variant, or omit it for the generic Transform.`);
215
216
  }
216
217
  const base = ctx.definition(typeName, version)?.outputDefinition;
package/dist/raw-node.js CHANGED
@@ -10,6 +10,7 @@ import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
10
10
  import { rawNodeInputProblem, rawNodeTypedFamilyWarning } from './raw-node-advice.js';
11
11
  import { forwardFanOut } from './core/definition-handles.js';
12
12
  import { RAW_NODE_EXITS } from './core/raw-node-exits.js';
13
+ import { CompileRefusal } from './core/compile-refusal.js';
13
14
  /**
14
15
  * Place a node this SDK has no factory for, carrying its definition verbatim
15
16
  * (`uipath.exotic.thing@2.1` and the manifest the platform served for it).
@@ -101,12 +102,12 @@ class RawNode extends FlowAction {
101
102
  // type never reads is refused by the serializer, for every contribution.)
102
103
  const refusal = connectorRawNodeRefusal(this.spec.nodeType);
103
104
  if (refusal)
104
- throw new Error(refusal);
105
+ throw new CompileRefusal('RAW_NODE_CONNECTOR', refusal);
105
106
  const version = context.requestedVersion ?? this.spec.version;
106
107
  if (context.requestedVersion !== undefined && context.requestedVersion !== this.spec.version) {
107
108
  // Versions are exact everywhere in this SDK: a contributor may not
108
109
  // substitute its own for an explicit request (design §2.6).
109
- throw new Error(`rawNode("${this.spec.nodeType}") carries version ${this.spec.version}, but the step requests `
110
+ throw new CompileRefusal('RAW_NODE_VERSION_MISMATCH', `rawNode("${this.spec.nodeType}") carries version ${this.spec.version}, but the step requests `
110
111
  + `${context.requestedVersion}. Pass the manifest for the requested version, or drop { version }.`);
111
112
  }
112
113
  return {