@uipath/maestro-builder-sdk 6.57.0 → 6.58.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/case/subscription.d.ts +4 -2
- package/dist/case/subscription.js +5 -2
- package/dist/core/actions.js +2 -2
- package/dist/core/binding-messages.d.ts +2 -1
- package/dist/core/binding-messages.js +3 -4
- package/dist/core/byoa.d.ts +3 -2
- package/dist/core/byoa.js +3 -3
- package/dist/core/carried-definitions.d.ts +0 -18
- package/dist/core/carried-definitions.js +3 -2
- package/dist/core/ceql.d.ts +2 -1
- package/dist/core/ceql.js +3 -2
- package/dist/core/compile-refusal.d.ts +21 -0
- package/dist/core/compile-refusal.js +26 -0
- package/dist/core/connector-inputs.js +2 -1
- package/dist/core/connector-resolve-error.d.ts +2 -1
- package/dist/core/connector-resolve-error.js +3 -4
- package/dist/core/connectors.js +2 -1
- package/dist/core/event-filters.js +2 -1
- package/dist/core/hitl-routing.d.ts +7 -82
- package/dist/core/hitl-routing.js +95 -6
- package/dist/core/library.js +4 -3
- package/dist/core/lookups.js +6 -5
- package/dist/core/nodes/registry.js +3 -2
- package/dist/core/nodes/versioned.js +3 -2
- package/dist/core/process-resource-sort-order.js +1 -1
- package/dist/core/read-text.d.ts +0 -28
- package/dist/core/read-text.js +3 -2
- package/dist/core/refusals.d.ts +465 -0
- package/dist/core/refusals.js +504 -0
- package/dist/core/script-function.js +2 -1
- package/dist/core/typescript-module.js +2 -1
- package/dist/edit-scope.d.ts +180 -0
- package/dist/edit-scope.js +251 -0
- package/dist/flow-expr-check.d.ts +2 -1
- package/dist/flow-expr-check.js +4 -2
- package/dist/flow-sdk.js +17 -16
- package/dist/flow-step-tree.js +1 -1
- package/dist/flow-variables.js +3 -2
- package/dist/load.js +7 -6
- package/dist/merge-cli.js +4 -2
- package/dist/merge-compile.d.ts +5 -88
- package/dist/merge-compile.js +8 -138
- package/dist/merge.d.ts +1 -61
- package/dist/merge.js +1 -112
- package/dist/nodes/agent/flow/inline-agent.js +7 -6
- package/dist/nodes/connector/flow/node-type.js +2 -1
- package/dist/nodes/data-fabric/flow/factories.js +2 -1
- package/dist/nodes/data-fabric/flow/verbs.js +2 -1
- package/dist/nodes/delay/flow/factories.js +2 -1
- package/dist/nodes/document/flow/ixp.js +5 -1
- package/dist/nodes/hitl/flow/check.js +4 -2
- package/dist/nodes/hitl/flow/node-type.js +6 -5
- package/dist/nodes/http/flow/factories.js +2 -1
- package/dist/nodes/http/flow/node-type.js +6 -5
- package/dist/nodes/process/flow/factories.js +2 -1
- package/dist/nodes/queue/flow/node-type.js +3 -2
- package/dist/nodes/transform/flow/node-type.js +2 -1
- package/dist/raw-node.js +3 -2
- package/dist/ref-resolve.d.ts +0 -36
- package/dist/ref-resolve.js +9 -8
- package/dist/schema-version.d.ts +0 -38
- package/dist/schema-version.js +3 -2
- package/dist/sdk-resolve.js +2 -1
- package/dist/serialize.d.ts +5 -2
- package/dist/serialize.js +115 -83
- package/package.json +2 -2
package/dist/serialize.js
CHANGED
|
@@ -11,6 +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
15
|
import { rawNodeInputProblem, registeredRawType } from './raw-node-advice.js';
|
|
15
16
|
import { planContributionBindings } from './core/contribution-bindings.js';
|
|
16
17
|
import { declaredErrorPortRefusal, definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
|
|
@@ -219,7 +220,7 @@ function scheduledVersion(inputs, explicit) {
|
|
|
219
220
|
const cron = isQuartz(String(inputs?.every ?? ''));
|
|
220
221
|
if (explicit !== undefined) {
|
|
221
222
|
if (cron && explicit === '1.1') {
|
|
222
|
-
throw new
|
|
223
|
+
throw new CompileRefusal('SCHEDULE_CRON_VERSION', "scheduled(): a Quartz cron expression needs the trigger's 1.2 definition, but { version: '1.1' } "
|
|
223
224
|
+ 'was requested. Drop the version or request 1.2.');
|
|
224
225
|
}
|
|
225
226
|
return explicit;
|
|
@@ -271,6 +272,12 @@ function scheduleInputs(inputs, nodeId) {
|
|
|
271
272
|
};
|
|
272
273
|
}
|
|
273
274
|
export { CONNECTOR_INPUT_MISSING } from './core/connector-inputs.js';
|
|
275
|
+
/**
|
|
276
|
+
* `DraftCode` and the table's `draft` entries are one list: this fails to
|
|
277
|
+
* type-check when a code is added to one and not the other.
|
|
278
|
+
*/
|
|
279
|
+
const DRAFT_CODES_AGREE = true;
|
|
280
|
+
void DRAFT_CODES_AGREE;
|
|
274
281
|
/**
|
|
275
282
|
* `serialize`, plus the extra files the flow needs on disk beside it.
|
|
276
283
|
*
|
|
@@ -356,20 +363,20 @@ function emitStickyNotes(built, scope, claimNoteId) {
|
|
|
356
363
|
const authorId = note.id ?? `stickyNote_${i + 1}`;
|
|
357
364
|
const id = authorId;
|
|
358
365
|
if (taken.has(id)) {
|
|
359
|
-
throw new
|
|
366
|
+
throw new CompileRefusal('STICKY_NOTE_ID_TAKEN', `.stickyNotes(): id "${id}" is already a node in this flow. A sticky note is a real node, `
|
|
360
367
|
+ `so its id has to be unique — name it something else.`);
|
|
361
368
|
}
|
|
362
369
|
if (seen.has(id))
|
|
363
|
-
throw new
|
|
370
|
+
throw new CompileRefusal('STICKY_NOTE_DUP_ID', `.stickyNotes(): duplicate id "${id}".`);
|
|
364
371
|
seen.add(id);
|
|
365
372
|
if (typeof note.content !== 'string') {
|
|
366
|
-
throw new
|
|
373
|
+
throw new CompileRefusal('STICKY_NOTE_NO_CONTENT', `.stickyNotes(): note "${id}" needs a string \`content\`.`);
|
|
367
374
|
}
|
|
368
375
|
if (!Number.isFinite(note.x) || !Number.isFinite(note.y)) {
|
|
369
|
-
throw new
|
|
376
|
+
throw new CompileRefusal('STICKY_NOTE_POSITION', `.stickyNotes(): note "${id}" needs finite x/y coordinates.`);
|
|
370
377
|
}
|
|
371
378
|
if ((note.width === undefined) !== (note.height === undefined)) {
|
|
372
|
-
throw new
|
|
379
|
+
throw new CompileRefusal('STICKY_NOTE_SIZE', `.stickyNotes(): note "${id}" needs both width and height, or neither.`);
|
|
373
380
|
}
|
|
374
381
|
// Node ids are one namespace over the whole file (#938): pack merges every
|
|
375
382
|
// subflow into one BPMN document. The author's id is kept unless another
|
|
@@ -409,7 +416,7 @@ function applyAuthorLayout(layout, scope, where) {
|
|
|
409
416
|
?? scope.noteNodeId?.get(key)
|
|
410
417
|
?? (scope.nodes.some((n) => n.id === key) ? key : undefined);
|
|
411
418
|
if (!id) {
|
|
412
|
-
throw new
|
|
419
|
+
throw new CompileRefusal('LAYOUT_UNKNOWN_KEY', `${where}.layout: "${key}" names no step or node in this flow. Layout keys are the flow's own step names (plus the trigger id and "end").`);
|
|
413
420
|
}
|
|
414
421
|
return id;
|
|
415
422
|
};
|
|
@@ -428,11 +435,11 @@ function applyAuthorLayout(layout, scope, where) {
|
|
|
428
435
|
for (const [key, route] of Object.entries(layout.edges ?? {})) {
|
|
429
436
|
const m = /^(.+?)->(.+)$/.exec(key);
|
|
430
437
|
if (!m)
|
|
431
|
-
throw new
|
|
438
|
+
throw new CompileRefusal('LAYOUT_EDGE_KEY_FORM', `${where}.layout: edge key "${key}" must be "<fromStep>-><toStep>".`);
|
|
432
439
|
const [src, tgt] = [resolveId(m[1].trim()), resolveId(m[2].trim())];
|
|
433
440
|
const matches = scope.edges.filter((e) => e.sourceNodeId === src && e.targetNodeId === tgt);
|
|
434
441
|
if (matches.length !== 1) {
|
|
435
|
-
throw new
|
|
442
|
+
throw new CompileRefusal('LAYOUT_EDGE_AMBIGUOUS', `${where}.layout: edge "${key}" matches ${matches.length} emitted edges; expected exactly 1.`);
|
|
436
443
|
}
|
|
437
444
|
edgesOut[matches[0].id] = {
|
|
438
445
|
...(route.waypoints ? { waypoints: withIds(route.waypoints) } : {}),
|
|
@@ -668,16 +675,16 @@ export function bundledContainerDefinition(kind, requestedVersion) {
|
|
|
668
675
|
*/
|
|
669
676
|
export function resolveContributionDefinition(ref, requestedVersion, where) {
|
|
670
677
|
if (!ref || typeof ref.nodeType !== 'string' || !ref.nodeType || typeof ref.version !== 'string' || !ref.version) {
|
|
671
|
-
throw new
|
|
678
|
+
throw new CompileRefusal('CONTRIBUTION_INVALID', `${where}: contribute() returned a DefinitionReference without an exact nodeType + version.`);
|
|
672
679
|
}
|
|
673
680
|
if (requestedVersion !== undefined && ref.version !== requestedVersion) {
|
|
674
|
-
throw new
|
|
681
|
+
throw new CompileRefusal('CONTRIBUTION_INVALID', `${where}: the author requested version ${requestedVersion}, but contribute() returned `
|
|
675
682
|
+ `${ref.nodeType}@${ref.version}. A contributor must honor the requested version or throw.`);
|
|
676
683
|
}
|
|
677
684
|
if (ref.source === 'bundled') {
|
|
678
685
|
const def = DEFS[`${ref.nodeType}@${ref.version}`];
|
|
679
686
|
if (!def) {
|
|
680
|
-
throw new
|
|
687
|
+
throw new CompileRefusal('CONTRIBUTION_INVALID', `${where}: no bundled definition ${ref.nodeType}@${ref.version}. Bundled references resolve `
|
|
681
688
|
+ `from core-definitions.json only; use a snapshot reference for definitions the SDK does not ship.`);
|
|
682
689
|
}
|
|
683
690
|
return def;
|
|
@@ -685,22 +692,22 @@ export function resolveContributionDefinition(ref, requestedVersion, where) {
|
|
|
685
692
|
if (ref.source === 'snapshot') {
|
|
686
693
|
const m = ref.manifest;
|
|
687
694
|
if (!m || typeof m !== 'object') {
|
|
688
|
-
throw new
|
|
695
|
+
throw new CompileRefusal('CONTRIBUTION_INVALID', `${where}: snapshot reference carries no manifest object.`);
|
|
689
696
|
}
|
|
690
697
|
if (m.nodeType !== ref.nodeType || m.version !== ref.version) {
|
|
691
|
-
throw new
|
|
698
|
+
throw new CompileRefusal('CONTRIBUTION_INVALID', `${where}: snapshot manifest says ${m.nodeType}@${m.version}, but the reference says `
|
|
692
699
|
+ `${ref.nodeType}@${ref.version}. A snapshot is an exact copy of a real definition.`);
|
|
693
700
|
}
|
|
694
701
|
if (typeof m.display?.label !== 'string' || !m.display.label) {
|
|
695
|
-
throw new
|
|
702
|
+
throw new CompileRefusal('CONTRIBUTION_INVALID', `${where}: snapshot manifest for ${ref.nodeType} lacks display.label — not a valid node manifest.`);
|
|
696
703
|
}
|
|
697
704
|
return m;
|
|
698
705
|
}
|
|
699
706
|
if (ref.source === 'library') {
|
|
700
|
-
throw new
|
|
707
|
+
throw new CompileRefusal('CONTRIBUTION_INVALID', `${where}: library-sourced DefinitionReference is not wired yet — it lands with the `
|
|
701
708
|
+ `connector-tool base classes (coverage design, Phase 5). Use 'bundled' or 'snapshot'.`);
|
|
702
709
|
}
|
|
703
|
-
throw new
|
|
710
|
+
throw new CompileRefusal('CONTRIBUTION_INVALID', `${where}: unknown DefinitionReference source "${ref.source}".`);
|
|
704
711
|
}
|
|
705
712
|
/**
|
|
706
713
|
* Whether `def` declares `port` as a forward source handle: an exact id, or a
|
|
@@ -811,7 +818,7 @@ function eventDetail(rc, sub, nodeId, opts, site) {
|
|
|
811
818
|
const objectName = String(op.objectName ?? '');
|
|
812
819
|
const noObject = genericEventObjectRefusal(rc, sub, (o) => `onEvent(…, { object: '${o}' })`);
|
|
813
820
|
if (noObject)
|
|
814
|
-
throw new
|
|
821
|
+
throw new CompileRefusal('EVENT_GENERIC_NO_OBJECT', `event node "${nodeId}": ${noObject}`);
|
|
815
822
|
// An unresolved `where` lookup is swapped for its recorded id — the id
|
|
816
823
|
// `prepare --resolve` stored under the EVENT-form node type — or refused.
|
|
817
824
|
// Same rule as connector inputs: emitting a placeholder for a value nobody
|
|
@@ -859,7 +866,7 @@ function eventDetail(rc, sub, nodeId, opts, site) {
|
|
|
859
866
|
.map(([k, v]) => {
|
|
860
867
|
const problem = jmesStringProblem(String(v));
|
|
861
868
|
if (problem)
|
|
862
|
-
throw new
|
|
869
|
+
throw new CompileRefusal('EVENT_FILTER_BACKSLASH', `event node "${nodeId}": \`where.${k}\`: ${problem}`);
|
|
863
870
|
return `${k} == ${jmesString(String(v))}`;
|
|
864
871
|
});
|
|
865
872
|
// Each authored filter is read structurally (which operator, which value) and
|
|
@@ -867,15 +874,22 @@ function eventDetail(rc, sub, nodeId, opts, site) {
|
|
|
867
874
|
// measured table. `check` has already refused a malformed leaf; a leaf that
|
|
868
875
|
// still cannot be read here is an author error, not something to drop silently.
|
|
869
876
|
const conditions = filters.map((f, i) => {
|
|
870
|
-
const { read } = readEventFilter(f);
|
|
877
|
+
const { keys, read } = readEventFilter(f);
|
|
871
878
|
if (!read) {
|
|
872
|
-
|
|
879
|
+
// The code `check` gives the same leaf (`core/event-checks.ts`).
|
|
880
|
+
const field = f?.field;
|
|
881
|
+
throw new CompileRefusal(typeof field !== 'string' || field === '' ? 'EVENT_FILTER_NO_FIELD'
|
|
882
|
+
: keys.length > 1 ? 'EVENT_FILTER_MANY_OPERATORS' : 'EVENT_FILTER_NO_VALUE', `event node "${nodeId}": filters[${i}] must carry a field and exactly ONE operator `
|
|
873
883
|
+ `(contains, startsWith, endsWith, equals, notEquals, lessThan, lessThanOrEqual, greaterThan, `
|
|
874
884
|
+ `greaterThanOrEqual) — got ${JSON.stringify(f)}.`);
|
|
875
885
|
}
|
|
886
|
+
// An ambiguous backslash first, under the code `check` gives it
|
|
887
|
+
// (`core/event-checks.ts`); `eventFilterProblem` would report it too.
|
|
876
888
|
const problem = eventFilterProblem(read);
|
|
877
|
-
|
|
878
|
-
|
|
889
|
+
const backslash = typeof read.value === 'string' && jmesStringProblem(read.value) !== undefined;
|
|
890
|
+
if (problem) {
|
|
891
|
+
throw new CompileRefusal(backslash ? 'EVENT_FILTER_BACKSLASH' : 'EVENT_FILTER_BAD_VALUE', `event node "${nodeId}": filters[${i}] on \`${read.field}\`: ${problem}`);
|
|
892
|
+
}
|
|
879
893
|
return read;
|
|
880
894
|
});
|
|
881
895
|
const filterClauses = conditions.map((c) => `(${eventFilterJmes(c)})`);
|
|
@@ -986,7 +1000,7 @@ function eventDetail(rc, sub, nodeId, opts, site) {
|
|
|
986
1000
|
*/
|
|
987
1001
|
function renderDeep(val, rename = NO_RENAME, rendered) {
|
|
988
1002
|
if (isLookupToken(val))
|
|
989
|
-
throw new
|
|
1003
|
+
throw new CompileRefusal('LOOKUP_MISPLACED', misplacedLookupMessage(val));
|
|
990
1004
|
if (val instanceof Expr)
|
|
991
1005
|
return renderValue(val, rename);
|
|
992
1006
|
// A value `ctx.render` already rendered is the file's: a `lit`'s object is
|
|
@@ -1032,14 +1046,14 @@ function emittableGlobals(built, where, kind = 'root') {
|
|
|
1032
1046
|
const shared = sharedInputsOf(built, where, kind);
|
|
1033
1047
|
const plan = planFlowGlobals({ inputs, outputs: built.outputs, vars: [...built.vars, ...shared.autoVars] });
|
|
1034
1048
|
if (plan.problems.length) {
|
|
1035
|
-
throw new
|
|
1049
|
+
throw new CompileRefusal(plan.problems[0].code, `${where}: ${plan.problems.map((p) => `[${p.code}] ${p.message}`).join(' ')}`);
|
|
1036
1050
|
}
|
|
1037
1051
|
// An output the flow sets from inside gets an `inout` Variable to hold the
|
|
1038
1052
|
// value; the End copies it into the Output (#1092, `flow-output-backing.ts`).
|
|
1039
1053
|
const sharedOutputs = new Set(shared.targets.filter((t) => t.declaredAs === 'output').map((t) => t.name));
|
|
1040
1054
|
const backing = planOutputBacking(built, plan.outputs, plan.vars, sharedOutputs);
|
|
1041
1055
|
if (backing.problems.length) {
|
|
1042
|
-
throw new
|
|
1056
|
+
throw new CompileRefusal(backing.problems[0].code, `${where}: ${backing.problems.map((p) => `[${p.code}] ${p.message}`).join(' ')}`);
|
|
1043
1057
|
}
|
|
1044
1058
|
return { ...plan, backing: backing.backing, backingReads: backing.reads };
|
|
1045
1059
|
}
|
|
@@ -1052,14 +1066,14 @@ function sharedInputsOf(built, where, kind) {
|
|
|
1052
1066
|
if (kind === 'subflow') {
|
|
1053
1067
|
const marked = built.inputs.filter((i) => i.shared !== undefined);
|
|
1054
1068
|
if (marked.length) {
|
|
1055
|
-
throw new
|
|
1069
|
+
throw new CompileRefusal('SHARED_INPUT_IN_SUBFLOW', `${where}: [SHARED_INPUT_IN_SUBFLOW] input "${marked[0].name}" has { shared: "${marked[0].shared}" }, but a subflow `
|
|
1056
1070
|
+ `has one root — its caller — so there is nothing to merge. Read the input with input('${marked[0].name}').`);
|
|
1057
1071
|
}
|
|
1058
1072
|
}
|
|
1059
1073
|
const plan = planSharedInputs({ roots: sharedInputRoots(built), outputs: built.outputs, vars: built.vars });
|
|
1060
1074
|
const errors = plan.problems.filter((p) => p.level === 'error');
|
|
1061
1075
|
if (errors.length)
|
|
1062
|
-
throw new
|
|
1076
|
+
throw new CompileRefusal(errors[0].code, `${where}: ${errors.map((p) => `[${p.code}] ${p.message}`).join(' ')}`);
|
|
1063
1077
|
return plan;
|
|
1064
1078
|
}
|
|
1065
1079
|
function newScope(outputs, vars = [], backing = new Map(), backingReads = backing) {
|
|
@@ -1138,7 +1152,7 @@ function resolveRejoins(scope, addEdge) {
|
|
|
1138
1152
|
const id = scope.stepNodeId.get(target) ?? scope.endNodeId.get(target);
|
|
1139
1153
|
if (!id) {
|
|
1140
1154
|
const ends = [...scope.endNodeId.keys()];
|
|
1141
|
-
throw new
|
|
1155
|
+
throw new CompileRefusal('REF_UNKNOWN_TARGET', `.stepToRef('${target}') names a step this flow does not have. Known steps: `
|
|
1142
1156
|
+ `${[...scope.stepNodeId.keys()].join(', ') || '(none)'}`
|
|
1143
1157
|
+ `${ends.length ? `; named Ends: ${ends.join(', ')}` : ''}.`);
|
|
1144
1158
|
}
|
|
@@ -1309,7 +1323,7 @@ function jsonSchemaForType(type) {
|
|
|
1309
1323
|
case 'jsonSchema': return {};
|
|
1310
1324
|
default: {
|
|
1311
1325
|
const unmapped = type;
|
|
1312
|
-
throw new Error(`jsonSchemaForType: no agent schema mapping for type '${String(unmapped)}'`);
|
|
1326
|
+
throw new Error(`internal SDK error: jsonSchemaForType: no agent schema mapping for type '${String(unmapped)}'`);
|
|
1313
1327
|
}
|
|
1314
1328
|
}
|
|
1315
1329
|
}
|
|
@@ -1522,6 +1536,11 @@ export function serialize(built, opts = {}) {
|
|
|
1522
1536
|
* "Node type `core.datafabric.read:1.4` has no matching definition".
|
|
1523
1537
|
*/
|
|
1524
1538
|
const emittedDefs = new Map();
|
|
1539
|
+
/**
|
|
1540
|
+
* The nodes a draft compile emitted unbound (their `BINDING_MISSING` finding):
|
|
1541
|
+
* they write no binding rows by design, so the placeholder check exempts them.
|
|
1542
|
+
*/
|
|
1543
|
+
const unboundDrafts = new Set();
|
|
1525
1544
|
/**
|
|
1526
1545
|
* RESOURCE bindings collected while emitting, keyed by binding id — `resource:
|
|
1527
1546
|
* 'process'` for the rpa family, `resource: 'queue'` for the queue family.
|
|
@@ -1558,11 +1577,11 @@ export function serialize(built, opts = {}) {
|
|
|
1558
1577
|
function refuseErrorEdgeWithoutPort(nodeId, stepName) {
|
|
1559
1578
|
const declared = declaredErrorPorts.get(nodeId);
|
|
1560
1579
|
if (declared?.port === false)
|
|
1561
|
-
throw new
|
|
1580
|
+
throw new CompileRefusal('ONERROR_NO_ERROR_PORT', declaredErrorPortRefusal(stepName, declared.typeName, declared.version));
|
|
1562
1581
|
const contributed = contributionDefs.get(nodeId);
|
|
1563
1582
|
const def = contributed ?? bundledNodeDefs.get(nodeId);
|
|
1564
1583
|
if (def !== undefined && !definitionHasErrorPort(def)) {
|
|
1565
|
-
throw new
|
|
1584
|
+
throw new CompileRefusal('ONERROR_NO_ERROR_PORT', errorPortRefusal(stepName, def, { bundled: contributed === undefined }));
|
|
1566
1585
|
}
|
|
1567
1586
|
}
|
|
1568
1587
|
/**
|
|
@@ -1639,8 +1658,8 @@ export function serialize(built, opts = {}) {
|
|
|
1639
1658
|
const earlier = sidecars.find((f) => f.path === file.path);
|
|
1640
1659
|
if (earlier !== undefined && JSON.stringify(earlier.json) !== JSON.stringify(file.json)
|
|
1641
1660
|
&& (explicitSource || explicitSidecars.has(file.path))) {
|
|
1642
|
-
throw
|
|
1643
|
-
+ 'Give each resource its own `source`, or omit `source` to derive one.')
|
|
1661
|
+
throw new CompileRefusal('INLINE_AGENT_RESOURCE_SOURCE_DUP', `two resources would both write "${file.path}" with different content, so the second would replace the first. `
|
|
1662
|
+
+ 'Give each resource its own `source`, or omit `source` to derive one.');
|
|
1644
1663
|
}
|
|
1645
1664
|
if (explicitSource)
|
|
1646
1665
|
explicitSidecars.add(file.path);
|
|
@@ -1735,7 +1754,7 @@ export function serialize(built, opts = {}) {
|
|
|
1735
1754
|
const id = safeNodeId(name);
|
|
1736
1755
|
const end = endIds.get(id);
|
|
1737
1756
|
if (end !== undefined) {
|
|
1738
|
-
throw new
|
|
1757
|
+
throw new CompileRefusal('DUP_END_NAME', end === name
|
|
1739
1758
|
? `${where}: [DUP_END_NAME] Two .return({}, { name: '${name}' }) calls ask for the same End with different `
|
|
1740
1759
|
+ `values, label, version or updates. To end several `
|
|
1741
1760
|
+ `paths at one End, keep one .return and end the others with .stepToRef('${name}'); to have separate Ends, `
|
|
@@ -1745,7 +1764,7 @@ export function serialize(built, opts = {}) {
|
|
|
1745
1764
|
}
|
|
1746
1765
|
const step = stepIds.get(id);
|
|
1747
1766
|
if (step !== undefined || reserved.includes(id)) {
|
|
1748
|
-
throw new
|
|
1767
|
+
throw new CompileRefusal('STEP_ID_COLLISION', `${where}: [STEP_ID_COLLISION] End "${name}" (.return({}, { name: '${name}' })) has the node id "${id}" of `
|
|
1749
1768
|
+ `${step !== undefined ? `step "${step}"` : 'the trigger or an entry point'}, and compile will not rename it "${id}2". `
|
|
1750
1769
|
+ 'Give the End a name no step or trigger uses.');
|
|
1751
1770
|
}
|
|
@@ -1753,7 +1772,7 @@ export function serialize(built, opts = {}) {
|
|
|
1753
1772
|
}
|
|
1754
1773
|
for (const [id, name] of endIds) {
|
|
1755
1774
|
if (!copyPrefix && takenIds.has(id)) {
|
|
1756
|
-
throw new Error(
|
|
1775
|
+
throw new Error(`internal SDK error: ${where}: End "${name}" has node id "${id}", which another node already holds (claimEndIds ran late).`);
|
|
1757
1776
|
}
|
|
1758
1777
|
scope.endNodeId.set(name, claimId(copyPrefix + id));
|
|
1759
1778
|
}
|
|
@@ -1789,7 +1808,7 @@ export function serialize(built, opts = {}) {
|
|
|
1789
1808
|
// two Terminates -> end the other paths at one; a step and a Terminate
|
|
1790
1809
|
// -> rename one.
|
|
1791
1810
|
const t = first === n ? (terminates.get(n) ?? 0) : 0;
|
|
1792
|
-
throw new
|
|
1811
|
+
throw new CompileRefusal(first !== n ? 'STEP_ID_COLLISION' : 'DUP_STEP', first !== n
|
|
1793
1812
|
? `${where}: [STEP_ID_COLLISION] steps "${first}" and "${n}" would both get node id "${safe}". Give them `
|
|
1794
1813
|
+ 'names that differ in letters, digits or "_".'
|
|
1795
1814
|
: t >= 2 ? `${where}: [DUP_STEP] ${terminateTwiceMessage(n)}`
|
|
@@ -1804,7 +1823,7 @@ export function serialize(built, opts = {}) {
|
|
|
1804
1823
|
const prefix = copyPrefix !== undefined && [...seen.keys(), ...alsoIds].some((id) => takenIds.has(id)) ? copyPrefix : '';
|
|
1805
1824
|
for (const [safe, n] of seen) {
|
|
1806
1825
|
if (!prefix && takenIds.has(safe)) {
|
|
1807
|
-
throw new
|
|
1826
|
+
throw new CompileRefusal('STEP_ID_COLLISION', `${where}: [STEP_ID_COLLISION] step "${n}" has the trigger's node id "${safe}", and compile will not `
|
|
1808
1827
|
+ `rename it "${safe}2". Rename the step.`);
|
|
1809
1828
|
}
|
|
1810
1829
|
const id = claimId(prefix + safe);
|
|
@@ -1825,9 +1844,9 @@ export function serialize(built, opts = {}) {
|
|
|
1825
1844
|
if (nodeId === undefined)
|
|
1826
1845
|
return uid(scope, derivedBase);
|
|
1827
1846
|
if (nodeId.trim() === '' || takenIds.has(nodeId)) {
|
|
1828
|
-
throw
|
|
1847
|
+
throw new CompileRefusal('INLINE_AGENT_RESOURCE_NODE_ID', `${where}: \`nodeId\` ${JSON.stringify(nodeId)} `
|
|
1829
1848
|
+ (nodeId.trim() === '' ? 'is blank.' : 'is already the id of another node in this flow.')
|
|
1830
|
-
+ ' Give the resource node an id no other node has, or omit `nodeId` to derive one.')
|
|
1849
|
+
+ ' Give the resource node an id no other node has, or omit `nodeId` to derive one.');
|
|
1831
1850
|
}
|
|
1832
1851
|
takenIds.add(nodeId);
|
|
1833
1852
|
return nodeId;
|
|
@@ -1845,11 +1864,11 @@ export function serialize(built, opts = {}) {
|
|
|
1845
1864
|
// Unreachable after `claimStepIds`, which refuses a repeated name first;
|
|
1846
1865
|
// kept so no future emission path can bring back the silent `<name>2`.
|
|
1847
1866
|
if (scope.stepNodeId.has(stepName)) {
|
|
1848
|
-
throw new Error(`step "${stepName}" is emitted twice in one scope (
|
|
1867
|
+
throw new Error(`internal SDK error: step "${stepName}" is emitted twice in one scope (DUP_STEP reached stepUid).`);
|
|
1849
1868
|
}
|
|
1850
1869
|
const id = scope.pending.get(stepName);
|
|
1851
1870
|
if (id === undefined) {
|
|
1852
|
-
throw new Error(`step "${stepName}" has no claimed node id (
|
|
1871
|
+
throw new Error(`internal SDK error: step "${stepName}" has no claimed node id (stepNamesOf missed a step kind).`);
|
|
1853
1872
|
}
|
|
1854
1873
|
scope.pending.delete(stepName);
|
|
1855
1874
|
scope.stepNodeId.set(stepName, id);
|
|
@@ -1886,7 +1905,7 @@ export function serialize(built, opts = {}) {
|
|
|
1886
1905
|
const hitlNode = hitlNodes.get(from.node);
|
|
1887
1906
|
const refused = hitlNode ? hitlExitViolation(hitlNode, from.port, legacyCompletedNodes.has(from.node)) : undefined;
|
|
1888
1907
|
if (refused)
|
|
1889
|
-
throw new
|
|
1908
|
+
throw new CompileRefusal(refused.code, `serialize: ${refused.message}`);
|
|
1890
1909
|
scope.edges.push({ id: `edge_${edgeSeq++}`, sourceNodeId: from.node, sourcePort: from.port, targetNodeId: toNode, targetPort: toPort });
|
|
1891
1910
|
}
|
|
1892
1911
|
let col = 0;
|
|
@@ -1914,7 +1933,7 @@ export function serialize(built, opts = {}) {
|
|
|
1914
1933
|
const known = o.def ? [] : [...bundledVersions(type), ...carriedVersions];
|
|
1915
1934
|
const resolvable = known.length > 0 ? known
|
|
1916
1935
|
: [def?.version].filter((v) => typeof v === 'string');
|
|
1917
|
-
throw new
|
|
1936
|
+
throw new CompileRefusal('NODE_VERSION_UNREGISTERED', `"${label ?? id}" requests ${type}@${o.requestedVersion}, but this SDK resolves only `
|
|
1918
1937
|
+ `${type}@${resolvable.join(', ') || '<none>'}. Versions are exact — omit { version } to use `
|
|
1919
1938
|
+ `the pinned default, request a version this SDK ships, or carry that version's definition `
|
|
1920
1939
|
+ `(the .flow file's definitions[] entry) with .definitions([...]).`);
|
|
@@ -2088,7 +2107,7 @@ export function serialize(built, opts = {}) {
|
|
|
2088
2107
|
const eventTriggerRc = built.trigger?.kind === 'event'
|
|
2089
2108
|
? (() => {
|
|
2090
2109
|
if (!opts.library)
|
|
2091
|
-
throw new
|
|
2110
|
+
throw new CompileRefusal('LIBRARY_REQUIRED', `onEvent() needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
|
|
2092
2111
|
const sub = built.trigger.subscription;
|
|
2093
2112
|
const rc = opts.library.resolveEvent('trigger', sub.connector, sub.event, sub.version, sub.object);
|
|
2094
2113
|
refuseUnverifiedByoa(opts, 'trigger', sub);
|
|
@@ -2207,13 +2226,13 @@ export function serialize(built, opts = {}) {
|
|
|
2207
2226
|
// own import), whose RawNode predates the check.
|
|
2208
2227
|
const connectorRefusal = connectorRawNodeRefusal(def.nodeType);
|
|
2209
2228
|
if (connectorRefusal)
|
|
2210
|
-
throw new
|
|
2229
|
+
throw new CompileRefusal('RAW_NODE_CONNECTOR', `${where}: ${connectorRefusal}`);
|
|
2211
2230
|
// An input a registered node type never reads (a Data Fabric `_sortOptions`)
|
|
2212
2231
|
// is refused for every contribution, whichever class or SDK copy made it:
|
|
2213
2232
|
// `validate` cannot see it, and the node would ignore it at run time.
|
|
2214
2233
|
const unknownInput = rawNodeInputProblem(def.nodeType, def.version, contribution.inputs);
|
|
2215
2234
|
if (unknownInput?.severity === 'error')
|
|
2216
|
-
throw new
|
|
2235
|
+
throw new CompileRefusal('RAW_NODE_UNKNOWN_INPUT', `${where}: ${unknownInput.message}`);
|
|
2217
2236
|
const id = stepUid(scope, stepName);
|
|
2218
2237
|
contributionDefs.set(id, def);
|
|
2219
2238
|
return placeStepNode({ where, stepName, id, typeName: def.nodeType, requestedVersion, def, contribution, tails, scope, parentId });
|
|
@@ -2362,7 +2381,11 @@ export function serialize(built, opts = {}) {
|
|
|
2362
2381
|
return out !== null && typeof out === 'object' ? fileCopy(out) : out;
|
|
2363
2382
|
},
|
|
2364
2383
|
drafting: opts.draft !== undefined,
|
|
2365
|
-
draft: (code, message) =>
|
|
2384
|
+
draft: (code, message) => {
|
|
2385
|
+
if (code === 'BINDING_MISSING')
|
|
2386
|
+
unboundDrafts.add(id);
|
|
2387
|
+
opts.draft?.({ nodeId: id, code: code, message });
|
|
2388
|
+
},
|
|
2366
2389
|
};
|
|
2367
2390
|
const bound = [];
|
|
2368
2391
|
const contribution = impl.serialize(spec, ctx);
|
|
@@ -2478,7 +2501,7 @@ export function serialize(built, opts = {}) {
|
|
|
2478
2501
|
// keys and matching expression rewrites. Keep the collision check as an
|
|
2479
2502
|
// invariant backstop: an overwrite here would drop a complete body.
|
|
2480
2503
|
if (subflows[id]) {
|
|
2481
|
-
throw new Error(`subflow "${id}":
|
|
2504
|
+
throw new Error(`internal SDK error: subflow "${id}": id allocation collision; two subflow steps claimed the same body slot.`);
|
|
2482
2505
|
}
|
|
2483
2506
|
const node = makeNode(NODE_TYPE.subflow, id, step.name, { parentId, requestedVersion: step.options?.version });
|
|
2484
2507
|
// Inputs are keyed by the CHILD's input names — that is the whole
|
|
@@ -2497,7 +2520,7 @@ export function serialize(built, opts = {}) {
|
|
|
2497
2520
|
}
|
|
2498
2521
|
if (spec.kind === 'waitEvent') {
|
|
2499
2522
|
if (!opts.library)
|
|
2500
|
-
throw new
|
|
2523
|
+
throw new CompileRefusal('LIBRARY_REQUIRED', `waitForEvent "${step.name}" needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
|
|
2501
2524
|
const sub = spec.subscription;
|
|
2502
2525
|
const rc = opts.library.resolveEvent('event', sub.connector, sub.event, sub.version, sub.object);
|
|
2503
2526
|
refuseUnverifiedByoa(opts, `waitForEvent "${step.name}"`, sub);
|
|
@@ -2527,9 +2550,9 @@ export function serialize(built, opts = {}) {
|
|
|
2527
2550
|
if (maybeCustom instanceof FlowAction) {
|
|
2528
2551
|
return emitContribution(maybeCustom, step.name, step.options?.version, tails, scope, parentId);
|
|
2529
2552
|
}
|
|
2530
|
-
throw new
|
|
2553
|
+
throw new CompileRefusal('STEP_NOT_ACTION', `step "${step.name}": not an action spec — pass a factory result, subflow(), or a FlowAction subclass instance.`);
|
|
2531
2554
|
}
|
|
2532
|
-
throw new
|
|
2555
|
+
throw new CompileRefusal('STEP_NOT_ACTION', `step "${step.name}": unknown action kind "${spec.kind}".`);
|
|
2533
2556
|
}
|
|
2534
2557
|
/**
|
|
2535
2558
|
* Wire a step's `.onError(...)` handler — the edge out of the node's `error`
|
|
@@ -2580,7 +2603,11 @@ export function serialize(built, opts = {}) {
|
|
|
2580
2603
|
const node = tails[0]?.node ?? '';
|
|
2581
2604
|
if (!(hitlNodes.has(node) || legacyCompletedNodes.has(node)) || !tails.some((t) => t.port === port))
|
|
2582
2605
|
return;
|
|
2583
|
-
|
|
2606
|
+
// Two conditions, two codes. Several tails: the task does not route per
|
|
2607
|
+
// outcome, which is check's HITL_OUTCOME_PORTS_OFF. One tail: that port
|
|
2608
|
+
// already continues the main path (a routed task's primary outcome, or a
|
|
2609
|
+
// legacyCompleted task's `completed`), and outcomePorts would not help.
|
|
2610
|
+
throw new CompileRefusal(tails.length > 1 ? 'HITL_OUTCOME_PORTS_OFF' : 'HITL_PORT_ALREADY_WIRED', tails.length > 1
|
|
2584
2611
|
? `${method}('${port}', …) after human task "${source}": every outcome already continues to the next `
|
|
2585
2612
|
+ `step, '${port}' included, because this task does not route per outcome. Add outcomePorts: true `
|
|
2586
2613
|
+ `(the first outcome then continues and the others are routed), or route every outcome with .stepSwitch.`
|
|
@@ -2606,13 +2633,13 @@ export function serialize(built, opts = {}) {
|
|
|
2606
2633
|
// from the SAME node. Tails from two nodes mean a new action shape reached
|
|
2607
2634
|
// here without being taught about side exits; say so rather than guess.
|
|
2608
2635
|
if (tails.length === 0 || tails.some((t) => t.node !== tails[0].node)) {
|
|
2609
|
-
throw new Error(
|
|
2636
|
+
throw new Error(`internal SDK error: .stepToList('${step.port}', …) after step "${source}": expected the step to leave from exactly one node, got ${tails.length} tail(s).`);
|
|
2610
2637
|
}
|
|
2611
2638
|
refuseRewire('.stepToList', step.port, source, tails);
|
|
2612
2639
|
const nodeId = tails[0].node;
|
|
2613
2640
|
const node = scope.nodes.find((n) => n.id === nodeId);
|
|
2614
2641
|
if (!node)
|
|
2615
|
-
throw new Error(
|
|
2642
|
+
throw new Error(`internal SDK error: .stepToList('${step.port}', …) after step "${source}": emitted node "${nodeId}" not found in scope`);
|
|
2616
2643
|
// Only the error port carries this: without it the platform routes but Studio
|
|
2617
2644
|
// Web HIDES the handle, so no human can see the handler.
|
|
2618
2645
|
if (step.port === 'error') {
|
|
@@ -2646,7 +2673,7 @@ export function serialize(built, opts = {}) {
|
|
|
2646
2673
|
return;
|
|
2647
2674
|
const nodeId = scope.stepNodeId.get(step.name);
|
|
2648
2675
|
if (!nodeId)
|
|
2649
|
-
throw new Error(`updates on "${step.name}": emitted node not found.`);
|
|
2676
|
+
throw new Error(`internal SDK error: updates on "${step.name}": emitted node not found.`);
|
|
2650
2677
|
scope.variableUpdates[nodeId] = [
|
|
2651
2678
|
...(scope.variableUpdates[nodeId] ?? []),
|
|
2652
2679
|
...renderUpdates(updates, scope.rename),
|
|
@@ -2674,7 +2701,7 @@ export function serialize(built, opts = {}) {
|
|
|
2674
2701
|
const nodeId = scope.stepNodeId.get(step.name);
|
|
2675
2702
|
const node = nodeId ? scope.nodes.find((n) => n.id === nodeId) : undefined;
|
|
2676
2703
|
if (!node)
|
|
2677
|
-
throw new Error(`label on "${step.name}": emitted node not found.`);
|
|
2704
|
+
throw new Error(`internal SDK error: label on "${step.name}": emitted node not found.`);
|
|
2678
2705
|
node.display = { ...(node.display ?? {}), label };
|
|
2679
2706
|
};
|
|
2680
2707
|
for (const [stepIndex, step] of steps.entries()) {
|
|
@@ -2682,7 +2709,7 @@ export function serialize(built, opts = {}) {
|
|
|
2682
2709
|
// follow: nothing continues from it, so a step here would hang off no edge.
|
|
2683
2710
|
const router = lastAction !== undefined && tails.length === 0 ? routerNodes.get(lastAction) : undefined;
|
|
2684
2711
|
if (router !== undefined && step.kind !== 'stepToList' && step.kind !== 'stepToRef') {
|
|
2685
|
-
throw new
|
|
2712
|
+
throw new CompileRefusal('TEMPLATED_EXITS_ONLY', `step "${lastAction}": its definition declares only templated exits (such as case-{item.id}), so no `
|
|
2686
2713
|
+ `path continues from it and ${'name' in step ? `"${step.name}"` : `.${step.kind}()`} after it would `
|
|
2687
2714
|
+ `have no incoming edge. Route each exit with .stepToList('<port>', …), e.g. .stepToList('case-<id>', …).`);
|
|
2688
2715
|
}
|
|
@@ -2725,7 +2752,7 @@ export function serialize(built, opts = {}) {
|
|
|
2725
2752
|
// discover it has one port. `stepExits` carries the reason.
|
|
2726
2753
|
const exits = stepExits(step.spec, step.options?.version);
|
|
2727
2754
|
if (!hasExits(exits)) {
|
|
2728
|
-
throw new
|
|
2755
|
+
throw new CompileRefusal('STEP_SWITCH_NO_EXITS', `.stepSwitch("${step.name}"): ${exits.reason}, so there are no exits to route.`
|
|
2729
2756
|
+ (exits.suggestion ? ` ${exits.suggestion}` : ''));
|
|
2730
2757
|
}
|
|
2731
2758
|
const action = {
|
|
@@ -2741,7 +2768,7 @@ export function serialize(built, opts = {}) {
|
|
|
2741
2768
|
applyLabel(action);
|
|
2742
2769
|
const nodeId = scope.stepNodeId.get(step.name);
|
|
2743
2770
|
if (!nodeId)
|
|
2744
|
-
throw new Error(
|
|
2771
|
+
throw new Error(`internal SDK error: .stepSwitch("${step.name}"): emitted node not found.`);
|
|
2745
2772
|
const armTails = [];
|
|
2746
2773
|
const routed = new Set();
|
|
2747
2774
|
// Outcomes of a human task whose path stops at an End: an arm that ends
|
|
@@ -2750,7 +2777,7 @@ export function serialize(built, opts = {}) {
|
|
|
2750
2777
|
for (const c of step.cases) {
|
|
2751
2778
|
const exit = exits.find((e) => e.value === c.value);
|
|
2752
2779
|
if (!exit) {
|
|
2753
|
-
throw new
|
|
2780
|
+
throw new CompileRefusal('STEP_SWITCH_EXIT_UNKNOWN', `.stepSwitch("${step.name}"): no exit named ${JSON.stringify(c.value)}. `
|
|
2754
2781
|
+ `It declares ${exits.map((e) => JSON.stringify(e.value)).join(', ')}.`);
|
|
2755
2782
|
}
|
|
2756
2783
|
routed.add(exit.port);
|
|
@@ -2886,7 +2913,7 @@ export function serialize(built, opts = {}) {
|
|
|
2886
2913
|
// fan-out task's outcomes — must not both enter it: only one fires
|
|
2887
2914
|
// and the arms never start. Fails closed, as the join does.
|
|
2888
2915
|
if (tails.length > 1) {
|
|
2889
|
-
throw new
|
|
2916
|
+
throw new CompileRefusal('PARALLEL_FORK_ALTERNATIVES', `.parallel("${step.name}"): its fork "${fork.name}" would receive ${tails.length} alternative edges `
|
|
2890
2917
|
+ `(${tails.map((t) => `${t.node}@${t.port}`).join(', ')}), and only one of them fires while the gateway `
|
|
2891
2918
|
+ `waits for all of them. Add a step before the .parallel so the paths meet there first, or drop { fork }.`);
|
|
2892
2919
|
}
|
|
@@ -2915,13 +2942,13 @@ export function serialize(built, opts = {}) {
|
|
|
2915
2942
|
// edge per arm, and still only one arm runs.
|
|
2916
2943
|
const switchTail = outcomeTail ? undefined : t.find((x) => x.humanSwitch !== undefined);
|
|
2917
2944
|
if (t.length >= 2 && switchTail) {
|
|
2918
|
-
throw new
|
|
2945
|
+
throw new CompileRefusal('HITL_FAN_OUT_INTO_JOIN', `.parallel("${step.name}"): an arm sends ${t.length} alternative edges into join "${step.name}" — `
|
|
2919
2946
|
+ `among them the end of an arm of human task "${switchTail.humanSwitch}"'s .stepSwitch — and only one `
|
|
2920
2947
|
+ `of them fires, while the join waits for all of them. Add a step at the end of the arm, after the `
|
|
2921
2948
|
+ `.stepSwitch, so its arms meet there first, or move the task out of the .parallel.`);
|
|
2922
2949
|
}
|
|
2923
2950
|
if (t.length >= 2 && outcomeTail) {
|
|
2924
|
-
throw new
|
|
2951
|
+
throw new CompileRefusal('HITL_FAN_OUT_INTO_JOIN', `.parallel("${step.name}"): an arm sends ${t.length} alternative edges into join "${step.name}" — `
|
|
2925
2952
|
+ `among them outcome edge "${outcomeTail.port}" of human task "${outcomeTail.node}" — and only one `
|
|
2926
2953
|
+ `of them fires, while the join waits for all of them. Add a step at the end of the arm so its paths `
|
|
2927
2954
|
+ `meet there first, or move the task out of the .parallel.`);
|
|
@@ -2930,7 +2957,7 @@ export function serialize(built, opts = {}) {
|
|
|
2930
2957
|
}
|
|
2931
2958
|
const starved = hitlEndExits.slice(endsBefore).find((x) => x.scope === scope && x.parentId === parentId);
|
|
2932
2959
|
if (starved && armTails.length > 0) {
|
|
2933
|
-
throw new
|
|
2960
|
+
throw new CompileRefusal('HITL_END_OUTCOME_IN_PARALLEL', `.parallel("${step.name}"): human task "${starved.step}" ${starved.why} inside an arm, and join `
|
|
2934
2961
|
+ `"${step.name}" would wait forever for the path that continues. Give every outcome a path that `
|
|
2935
2962
|
+ `continues to the end of the arm (drop action: 'End', add the missing .stepSwitch arm, move the `
|
|
2936
2963
|
+ `.return() after the .parallel), or move the task out of the .parallel.`);
|
|
@@ -2974,7 +3001,7 @@ export function serialize(built, opts = {}) {
|
|
|
2974
3001
|
const loopDef = definitionFor(NODE_TYPE.loop, version);
|
|
2975
3002
|
const refusal = loopVersionRefusal(step.name, o, usesBreak, loopDef);
|
|
2976
3003
|
if (refusal)
|
|
2977
|
-
throw new
|
|
3004
|
+
throw new CompileRefusal('LOOP_VERSION_UNSUPPORTED', refusal);
|
|
2978
3005
|
const v24 = loopHasInnerHandles(version, loopDef);
|
|
2979
3006
|
const declared = loopDeclaredInputs(loopDef);
|
|
2980
3007
|
const id = stepUid(scope, step.name);
|
|
@@ -3019,7 +3046,7 @@ export function serialize(built, opts = {}) {
|
|
|
3019
3046
|
}
|
|
3020
3047
|
else if (step.kind === 'break') {
|
|
3021
3048
|
if (!loopExits?.break) {
|
|
3022
|
-
throw new
|
|
3049
|
+
throw new CompileRefusal('BREAK_OUTSIDE_LOOP', '.break() has no enclosing .loop()/.doWhile() body to exit — it is only valid inside a container.');
|
|
3023
3050
|
}
|
|
3024
3051
|
for (const t of tails)
|
|
3025
3052
|
addEdge(scope, t, loopExits.node, loopExits.break);
|
|
@@ -3030,7 +3057,7 @@ export function serialize(built, opts = {}) {
|
|
|
3030
3057
|
// to — so a handler or an arm can go on to the next iteration instead of
|
|
3031
3058
|
// ending the run on an End of its own (#1129).
|
|
3032
3059
|
if (!loopExits) {
|
|
3033
|
-
throw new
|
|
3060
|
+
throw new CompileRefusal('CONTINUE_OUTSIDE_LOOP', '.continue() has no enclosing .loop()/.doWhile() body to go back to — it is only valid inside a container.');
|
|
3034
3061
|
}
|
|
3035
3062
|
for (const t of tails)
|
|
3036
3063
|
addEdge(scope, t, loopExits.node, loopExits.continue);
|
|
@@ -3051,7 +3078,7 @@ export function serialize(built, opts = {}) {
|
|
|
3051
3078
|
// The remedy depends on what holds the name: another Terminate (end the
|
|
3052
3079
|
// other paths at it) or a step of another kind (rename one).
|
|
3053
3080
|
if (scope.stepNodeId.has(step.name)) {
|
|
3054
|
-
throw new
|
|
3081
|
+
throw new CompileRefusal('DUP_STEP', terminateNames.get(scope)?.has(step.name)
|
|
3055
3082
|
? terminateTwiceMessage(step.name)
|
|
3056
3083
|
: terminateStepClashMessage(step.name));
|
|
3057
3084
|
}
|
|
@@ -3088,7 +3115,7 @@ export function serialize(built, opts = {}) {
|
|
|
3088
3115
|
const router = tails.length === 0 && lastAction !== undefined ? routerNodes.get(lastAction) : undefined;
|
|
3089
3116
|
const from = tails[0] ?? (router !== undefined ? { node: router, port: step.port } : undefined);
|
|
3090
3117
|
if (!from)
|
|
3091
|
-
throw new Error(
|
|
3118
|
+
throw new Error(`internal SDK error: .stepToRef('${step.port}', '${step.target}') has no step to leave from.`);
|
|
3092
3119
|
refuseRewire('.stepToRef', step.port, lastAction ?? '?', tails);
|
|
3093
3120
|
if (step.port === 'error') {
|
|
3094
3121
|
refuseErrorEdgeWithoutPort(from.node, lastAction ?? from.node);
|
|
@@ -3121,7 +3148,7 @@ export function serialize(built, opts = {}) {
|
|
|
3121
3148
|
const extra = step.options?.updates ?? {};
|
|
3122
3149
|
for (const name of Object.keys(extra)) {
|
|
3123
3150
|
if (name in step.values) {
|
|
3124
|
-
throw new
|
|
3151
|
+
throw new CompileRefusal('RETURN_UPDATES_SAME_VAR', `.return(): "${name}" appears both as a returned value and in { updates } — one write per variable.`);
|
|
3125
3152
|
}
|
|
3126
3153
|
}
|
|
3127
3154
|
updates.push(...renderUpdates(extra, scope.rename));
|
|
@@ -3142,7 +3169,7 @@ export function serialize(built, opts = {}) {
|
|
|
3142
3169
|
// (#1231). `check` reports it first as DUP_END_NAME; this is the
|
|
3143
3170
|
// backstop for `--no-check`.
|
|
3144
3171
|
if (prior) {
|
|
3145
|
-
throw new
|
|
3172
|
+
throw new CompileRefusal('DUP_END_NAME', `Two .return({}, { name: '${name}' }) calls ask for the same End with different values, label, version, updates or scope. `
|
|
3146
3173
|
+ `To end several paths at one End, keep one .return and end the others with .stepToRef('${name}'); `
|
|
3147
3174
|
+ `to have separate Ends, give each its own name.`);
|
|
3148
3175
|
}
|
|
@@ -3150,7 +3177,7 @@ export function serialize(built, opts = {}) {
|
|
|
3150
3177
|
// `claimEndIds` (#1253); only an unnamed End's id is the SDK's to make up.
|
|
3151
3178
|
const id = name === undefined ? uid(scope, 'end') : scope.endNodeId.get(name);
|
|
3152
3179
|
if (id === undefined) {
|
|
3153
|
-
throw new Error(`End "${name}" has no claimed node id (
|
|
3180
|
+
throw new Error(`internal SDK error: End "${name}" has no claimed node id (namedNodesOf missed a step kind).`);
|
|
3154
3181
|
}
|
|
3155
3182
|
emitEnd(scope, id, tails, { label: step.options?.label, parentId, requestedVersion: step.options?.version, explicit });
|
|
3156
3183
|
if (updates.length)
|
|
@@ -3205,7 +3232,7 @@ export function serialize(built, opts = {}) {
|
|
|
3205
3232
|
const sharedTarget = firstShared && 'name' in firstShared ? root.stepNodeId.get(firstShared.name) : undefined;
|
|
3206
3233
|
for (const ep of entryPoints) {
|
|
3207
3234
|
if (takenIds.has(ep.id)) {
|
|
3208
|
-
throw new
|
|
3235
|
+
throw new CompileRefusal('ENTRY_POINT_DUP_ID', `.entryPoint("${ep.id}"): the id collides with a step, the trigger, or another entry point.`);
|
|
3209
3236
|
}
|
|
3210
3237
|
takenIds.add(ep.id);
|
|
3211
3238
|
const spec = ep.trigger;
|
|
@@ -3227,7 +3254,7 @@ export function serialize(built, opts = {}) {
|
|
|
3227
3254
|
}
|
|
3228
3255
|
else if (epSpec.kind === 'event') {
|
|
3229
3256
|
if (!opts.library)
|
|
3230
|
-
throw new
|
|
3257
|
+
throw new CompileRefusal('LIBRARY_REQUIRED', `onEvent() needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
|
|
3231
3258
|
const sub = epSpec.subscription;
|
|
3232
3259
|
const rc = opts.library.resolveEvent('trigger', sub.connector, sub.event, sub.version, sub.object);
|
|
3233
3260
|
refuseUnverifiedByoa(opts, 'trigger', sub);
|
|
@@ -3274,13 +3301,13 @@ export function serialize(built, opts = {}) {
|
|
|
3274
3301
|
node.inputs = { entryPointId: stableId(`${built.id}:${ep.id}:entryPoint`) };
|
|
3275
3302
|
}
|
|
3276
3303
|
else {
|
|
3277
|
-
throw new
|
|
3304
|
+
throw new CompileRefusal('TRIGGER_UNKNOWN_KIND', `.entryPoint("${ep.id}"): unsupported trigger kind ${JSON.stringify(epSpec.kind)}.`);
|
|
3278
3305
|
}
|
|
3279
3306
|
root.nodes.push(node);
|
|
3280
3307
|
const prefixTails = emit(ep.steps, [{ node: ep.id, port: 'output' }], root);
|
|
3281
3308
|
for (const t of prefixTails) {
|
|
3282
3309
|
if (!sharedTarget) {
|
|
3283
|
-
throw new
|
|
3310
|
+
throw new CompileRefusal('ENTRY_POINT_NO_SHARED_BODY', `.entryPoint("${ep.id}") runs into the shared body, but the flow has no shared step to join. `
|
|
3284
3311
|
+ `End the prefix terminally (.return()/.terminate()) or add a shared step.`);
|
|
3285
3312
|
}
|
|
3286
3313
|
addEdge(root, t, sharedTarget);
|
|
@@ -3366,7 +3393,7 @@ export function serialize(built, opts = {}) {
|
|
|
3366
3393
|
// Emitted-document invariants over connections, checked on the FINISHED file
|
|
3367
3394
|
// so every family and every emission order is covered by one rule, not by
|
|
3368
3395
|
// each call site remembering it (flow-builder-sdk#865/#866).
|
|
3369
|
-
assertBindingPlaceholdersResolve(flow);
|
|
3396
|
+
assertBindingPlaceholdersResolve(flow, unboundDrafts);
|
|
3370
3397
|
assertFileWideIdsUnique(flow);
|
|
3371
3398
|
assertConnectorConfigurationRestorable(flow);
|
|
3372
3399
|
connectionConnectors(flow);
|
|
@@ -3413,7 +3440,7 @@ export function assertConnectorConfigurationRestorable(flow) {
|
|
|
3413
3440
|
broken.push(node.id);
|
|
3414
3441
|
}
|
|
3415
3442
|
if (broken.length) {
|
|
3416
|
-
throw new Error(`serialize: connector configuration on ${broken.map((id) => `"${id}"`).join(', ')} has a non-empty `
|
|
3443
|
+
throw new Error(`internal SDK error: serialize: connector configuration on ${broken.map((id) => `"${id}"`).join(', ')} has a non-empty `
|
|
3417
3444
|
+ `optionalConfiguration without a fieldsContainer. Studio Web shows such a node's fields empty and a `
|
|
3418
3445
|
+ `save drops its bodyParameters (flow-builder-sdk#936). Emit { essentialConfiguration } only.`);
|
|
3419
3446
|
}
|
|
@@ -3449,7 +3476,7 @@ export function assertFileWideIdsUnique(flow) {
|
|
|
3449
3476
|
}
|
|
3450
3477
|
}
|
|
3451
3478
|
if (dupes.length) {
|
|
3452
|
-
throw new Error(`serialize: ids must be unique across the root and all subflows; duplicated: ${dupes.join('; ')}.`);
|
|
3479
|
+
throw new Error(`internal SDK error: serialize: ids must be unique across the root and all subflows; duplicated: ${dupes.join('; ')}.`);
|
|
3453
3480
|
}
|
|
3454
3481
|
}
|
|
3455
3482
|
/**
|
|
@@ -3474,12 +3501,15 @@ export function assertFileWideIdsUnique(flow) {
|
|
|
3474
3501
|
* It holds for real definitions: of the 5,102 in the local registry index
|
|
3475
3502
|
* (2026-09-30), 4,999 reference a `<bindings.…>` name and every one of them
|
|
3476
3503
|
* declares that name in `model.bindings.values`, which is what the emitters
|
|
3477
|
-
* close. Agent resource definitions are
|
|
3504
|
+
* close. Agent resource definitions are one exemption (see below); `unbound`,
|
|
3505
|
+
* the nodes a draft compile reported as `BINDING_MISSING`, is the other: such a
|
|
3506
|
+
* node names no connection, so it writes no binding rows, and the draft finding
|
|
3507
|
+
* already says so.
|
|
3478
3508
|
*
|
|
3479
3509
|
* @throws BindingRefusal `BINDING_PLACEHOLDER_UNRESOLVED`, naming each dangling
|
|
3480
3510
|
* placeholder and the step that carries it.
|
|
3481
3511
|
*/
|
|
3482
|
-
export function assertBindingPlaceholdersResolve(flow) {
|
|
3512
|
+
export function assertBindingPlaceholdersResolve(flow, unbound = new Set()) {
|
|
3483
3513
|
const bindings = Array.isArray(flow?.bindings) ? flow.bindings : [];
|
|
3484
3514
|
const defs = new Map();
|
|
3485
3515
|
for (const def of Array.isArray(flow?.definitions) ? flow.definitions : []) {
|
|
@@ -3498,6 +3528,8 @@ export function assertBindingPlaceholdersResolve(flow) {
|
|
|
3498
3528
|
// deployed corpus carries no flow `bindings[]` entry for them.
|
|
3499
3529
|
if (typeof node?.type !== 'string' || node.type.startsWith('uipath.agent.resource.'))
|
|
3500
3530
|
continue;
|
|
3531
|
+
if (unbound.has(node.id))
|
|
3532
|
+
continue;
|
|
3501
3533
|
const model = defs.get(`${node.type}@${node.typeVersion}`)?.model;
|
|
3502
3534
|
if (!Array.isArray(model?.context))
|
|
3503
3535
|
continue;
|