@uipath/maestro-builder-sdk 6.18.9 → 6.19.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.
- package/dist/api-index.md +185 -184
- package/dist/api-members.md +504 -473
- package/dist/bpmn/uipath-moddle.v1.json +5 -0
- package/dist/case/decompile-cli.js +2 -8
- package/dist/check.js +177 -650
- package/dist/core/actions.d.ts +119 -760
- package/dist/core/actions.js +0 -306
- package/dist/core/error-port.d.ts +6 -0
- package/dist/core/error-port.js +9 -0
- package/dist/core/node-classes.d.ts +4 -63
- package/dist/core/node-classes.js +0 -112
- package/dist/core/nodes/family.d.ts +36 -0
- package/dist/core/nodes/family.js +1 -0
- package/dist/core/nodes/node-type.d.ts +136 -0
- package/dist/core/nodes/node-type.js +1 -0
- package/dist/core/nodes/registry.d.ts +57 -0
- package/dist/core/nodes/registry.js +122 -0
- package/dist/core/nodes/versioned.d.ts +50 -0
- package/dist/core/nodes/versioned.js +46 -0
- package/dist/core/typed-raw-node.d.ts +13 -62
- package/dist/core/typed-raw-node.js +40 -154
- package/dist/decompile-cli.js +2 -9
- package/dist/decompile.js +92 -271
- package/dist/flow-definitions.d.ts +10 -0
- package/dist/flow-definitions.js +35 -0
- package/dist/flow-node-registry.d.ts +13 -0
- package/dist/flow-node-registry.js +19 -0
- package/dist/flow-node-type.d.ts +192 -0
- package/dist/flow-node-type.js +1 -0
- package/dist/flow-sdk.d.ts +7 -0
- package/dist/flow-sdk.js +8 -0
- package/dist/nodes/data-fabric/flow/check.d.ts +13 -0
- package/dist/nodes/data-fabric/flow/check.js +164 -0
- package/dist/nodes/data-fabric/flow/factories.d.ts +113 -0
- package/dist/nodes/data-fabric/flow/factories.js +134 -0
- package/dist/nodes/data-fabric/flow/family.d.ts +7 -0
- package/dist/nodes/data-fabric/flow/family.js +9 -0
- package/dist/nodes/data-fabric/flow/index.d.ts +8 -0
- package/dist/nodes/data-fabric/flow/index.js +8 -0
- package/dist/nodes/data-fabric/flow/raw.d.ts +32 -0
- package/dist/nodes/data-fabric/flow/raw.js +100 -0
- package/dist/nodes/data-fabric/flow/shared.d.ts +77 -0
- package/dist/nodes/data-fabric/flow/shared.js +174 -0
- package/dist/nodes/data-fabric/flow/spec.d.ts +238 -0
- package/dist/nodes/data-fabric/flow/spec.js +1 -0
- package/dist/nodes/data-fabric/flow/verbs.d.ts +29 -0
- package/dist/nodes/data-fabric/flow/verbs.js +239 -0
- package/dist/nodes/delay/flow/check.d.ts +16 -0
- package/dist/nodes/delay/flow/check.js +86 -0
- package/dist/nodes/delay/flow/factories.d.ts +28 -0
- package/dist/nodes/delay/flow/factories.js +38 -0
- package/dist/nodes/delay/flow/family.d.ts +3 -0
- package/dist/nodes/delay/flow/family.js +8 -0
- package/dist/nodes/delay/flow/index.d.ts +3 -0
- package/dist/nodes/delay/flow/index.js +3 -0
- package/dist/nodes/delay/flow/node-type.d.ts +13 -0
- package/dist/nodes/delay/flow/node-type.js +71 -0
- package/dist/nodes/delay/flow/spec.d.ts +43 -0
- package/dist/nodes/delay/flow/spec.js +16 -0
- package/dist/nodes/document/flow/factories.d.ts +27 -0
- package/dist/nodes/document/flow/factories.js +30 -0
- package/dist/nodes/document/flow/family.d.ts +3 -0
- package/dist/nodes/document/flow/family.js +7 -0
- package/dist/nodes/document/flow/index.d.ts +3 -0
- package/dist/nodes/document/flow/index.js +3 -0
- package/dist/nodes/document/flow/node-type.d.ts +14 -0
- package/dist/nodes/document/flow/node-type.js +150 -0
- package/dist/nodes/document/flow/spec.d.ts +65 -0
- package/dist/nodes/document/flow/spec.js +1 -0
- package/dist/nodes/mock/flow/factories.d.ts +51 -0
- package/dist/nodes/mock/flow/factories.js +47 -0
- package/dist/nodes/mock/flow/family.d.ts +3 -0
- package/dist/nodes/mock/flow/family.js +7 -0
- package/dist/nodes/mock/flow/index.d.ts +2 -0
- package/dist/nodes/mock/flow/index.js +2 -0
- package/dist/nodes/mock/flow/node-type.d.ts +11 -0
- package/dist/nodes/mock/flow/node-type.js +51 -0
- package/dist/nodes/script/flow/check.d.ts +19 -0
- package/dist/nodes/script/flow/check.js +49 -0
- package/dist/nodes/script/flow/factories.d.ts +14 -0
- package/dist/nodes/script/flow/factories.js +15 -0
- package/dist/nodes/script/flow/family.d.ts +3 -0
- package/dist/nodes/script/flow/family.js +8 -0
- package/dist/nodes/script/flow/index.d.ts +3 -0
- package/dist/nodes/script/flow/index.js +3 -0
- package/dist/nodes/script/flow/node-type.d.ts +7 -0
- package/dist/nodes/script/flow/node-type.js +125 -0
- package/dist/nodes/script/flow/spec.d.ts +52 -0
- package/dist/nodes/script/flow/spec.js +1 -0
- package/dist/nodes/transform/flow/check.d.ts +6 -0
- package/dist/nodes/transform/flow/check.js +229 -0
- package/dist/nodes/transform/flow/factories.d.ts +52 -0
- package/dist/nodes/transform/flow/factories.js +53 -0
- package/dist/nodes/transform/flow/family.d.ts +3 -0
- package/dist/nodes/transform/flow/family.js +8 -0
- package/dist/nodes/transform/flow/index.d.ts +3 -0
- package/dist/nodes/transform/flow/index.js +3 -0
- package/dist/nodes/transform/flow/node-type.d.ts +7 -0
- package/dist/nodes/transform/flow/node-type.js +244 -0
- package/dist/nodes/transform/flow/spec.d.ts +116 -0
- package/dist/nodes/transform/flow/spec.js +10 -0
- package/dist/raw-node-advice.d.ts +16 -0
- package/dist/raw-node-advice.js +36 -0
- package/dist/raw-node.d.ts +71 -0
- package/dist/raw-node.js +116 -0
- package/dist/registry/uip/launch.d.ts +11 -1
- package/dist/registry/uip/launch.js +50 -43
- package/dist/registry/uip/pipeline-front-door.d.ts +2 -0
- package/dist/registry/uip/pipeline-front-door.js +88 -0
- package/dist/serialize.js +138 -492
- package/package.json +2 -2
package/dist/serialize.js
CHANGED
|
@@ -10,12 +10,15 @@ import { readFileSync } from 'node:fs';
|
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { dirname, join } from 'node:path';
|
|
13
|
-
import {
|
|
13
|
+
import { BUNDLED_DEFINITIONS } from './flow-definitions.js';
|
|
14
|
+
import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType, flatNameOf, flatNameSegmentsOf } from './flow-sdk.js';
|
|
14
15
|
import { FlowAction, FlowTrigger } from './core/node-classes.js';
|
|
15
16
|
import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
16
|
-
import {
|
|
17
|
+
import { rawNodeInputProblem, registeredRawType } from './raw-node-advice.js';
|
|
17
18
|
import { planContributionBindings } from './core/contribution-bindings.js';
|
|
18
|
-
import { definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
|
|
19
|
+
import { declaredErrorPortRefusal, definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
|
|
20
|
+
import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
21
|
+
import { resolveVersion } from './core/nodes/versioned.js';
|
|
19
22
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
20
23
|
import { connectionConnectors } from './bindings-v2.js';
|
|
21
24
|
import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
|
|
@@ -45,7 +48,6 @@ export function connectorInputErrors(rc, inputs, resolutions = {}, lookups = {})
|
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
50
|
import { isLookupToken, resolveEventWhere, } from './core/lookups.js';
|
|
48
|
-
import { declaredScriptShape, inferScriptShape } from './core/script-return.js';
|
|
49
51
|
import { migrateToSchemaVersion } from './migrate.js';
|
|
50
52
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
51
53
|
import { resolveSchemaVersion } from './schema-version.js';
|
|
@@ -58,7 +60,7 @@ import { ownEntry } from './core/own-entry.js';
|
|
|
58
60
|
import { jsonClone } from './core/json-file.js';
|
|
59
61
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
60
62
|
// core-definitions.json is an object keyed by "<nodeType>@<version>".
|
|
61
|
-
const DEFS =
|
|
63
|
+
const DEFS = BUNDLED_DEFINITIONS;
|
|
62
64
|
// FIRST occurrence wins: with multiple bundled versions of one family (HITL
|
|
63
65
|
// 1.0 / 1.1 / 1.2), the unversioned lookup must stay the pinned default, not
|
|
64
66
|
// whichever entry happens to be last in the JSON. (`.loop()` always passes its
|
|
@@ -91,8 +93,6 @@ const NODE_TYPE = {
|
|
|
91
93
|
endCall: 'uipath.conversational.voice.end-call',
|
|
92
94
|
http: 'core.action.http',
|
|
93
95
|
httpV2: 'core.action.http.v2',
|
|
94
|
-
script: 'core.action.script',
|
|
95
|
-
transform: 'core.action.transform',
|
|
96
96
|
hitl: 'uipath.human-in-the-loop',
|
|
97
97
|
// The two other spellings of the SAME human task, both resolvable on this
|
|
98
98
|
// tenant. `hitl()` emits the base type unless `variant` names one of these;
|
|
@@ -103,23 +103,15 @@ const NODE_TYPE = {
|
|
|
103
103
|
hitlQuickForm: 'uipath.human-in-the-loop.quick-form',
|
|
104
104
|
hitlActionApp: 'uipath.human-in-the-loop.coded-action-app',
|
|
105
105
|
hitlDocValidation: 'uipath.human-in-the-loop.document-validation',
|
|
106
|
-
delay: 'core.logic.delay',
|
|
107
106
|
// The PLATFORM's placeholder — "a real node goes here later". Not the engine's
|
|
108
107
|
// fixture-returning `kind: 'mock'` test construct, which shares only the name
|
|
109
108
|
// (#125). The definition declares no `inputDefinition` and one output whose
|
|
110
109
|
// `source` is the literal `"null"`.
|
|
111
|
-
mock: 'core.logic.mock',
|
|
112
110
|
decision: 'core.logic.decision',
|
|
113
111
|
switch: 'core.logic.switch',
|
|
114
112
|
merge: 'core.logic.merge',
|
|
115
113
|
loop: 'core.logic.loop',
|
|
116
114
|
doWhile: 'core.logic.dowhile',
|
|
117
|
-
dataFabricRead: 'core.datafabric.read',
|
|
118
|
-
dataFabricCreate: 'core.datafabric.create',
|
|
119
|
-
dataFabricUpdate: 'core.datafabric.update',
|
|
120
|
-
dataFabricDelete: 'core.datafabric.delete',
|
|
121
|
-
documentClassify: 'uipath.document.classify',
|
|
122
|
-
dynamicExtract: 'uipath.ixp.extract-document-builder',
|
|
123
115
|
subflow: 'core.subflow',
|
|
124
116
|
terminate: 'core.logic.terminate',
|
|
125
117
|
queueCreate: 'core.action.queue.create',
|
|
@@ -3048,32 +3040,6 @@ function queueItemBindings(spec, existing) {
|
|
|
3048
3040
|
],
|
|
3049
3041
|
};
|
|
3050
3042
|
}
|
|
3051
|
-
/**
|
|
3052
|
-
* The three single-purpose Transform node types. Same runtime as the generic
|
|
3053
|
-
* (the platform dispatches on the OPERATION's type, not the node's) — the node
|
|
3054
|
-
* type only says what the node is for, and picks its editor in the designer.
|
|
3055
|
-
*/
|
|
3056
|
-
const TRANSFORM_VARIANT_TYPE = {
|
|
3057
|
-
map: 'core.action.transform.map',
|
|
3058
|
-
filter: 'core.action.transform.filter',
|
|
3059
|
-
'group-by': 'core.action.transform.group-by',
|
|
3060
|
-
};
|
|
3061
|
-
/**
|
|
3062
|
-
* Pick the node type for a transform step: the generic unless a variant is named.
|
|
3063
|
-
*
|
|
3064
|
-
* An unrecognized variant is rejected by `check` (TRANSFORM_VARIANT_UNKNOWN)
|
|
3065
|
-
* before serialization, and compile-cli refuses to emit on errors. Reaching
|
|
3066
|
-
* here with one means compile ran without a check — so emit the type the author
|
|
3067
|
-
* asked for, keeping the failure loud downstream rather than quietly falling
|
|
3068
|
-
* back to the generic (which would produce a flow that runs and is the wrong
|
|
3069
|
-
* node) — the same principle as `collectionPath`.
|
|
3070
|
-
*/
|
|
3071
|
-
function transformNodeType(inputs) {
|
|
3072
|
-
const variant = inputs?.variant;
|
|
3073
|
-
if (!variant)
|
|
3074
|
-
return NODE_TYPE.transform;
|
|
3075
|
-
return ownEntry(TRANSFORM_VARIANT_TYPE, variant) ?? `core.action.transform.${String(variant)}`;
|
|
3076
|
-
}
|
|
3077
3043
|
/**
|
|
3078
3044
|
* Encode a scheduled trigger's single `every` interval into the three inputs the
|
|
3079
3045
|
* platform's node carries.
|
|
@@ -3115,93 +3081,6 @@ function scheduledVersion(inputs, explicit) {
|
|
|
3115
3081
|
}
|
|
3116
3082
|
return cron ? '1.2' : undefined;
|
|
3117
3083
|
}
|
|
3118
|
-
/**
|
|
3119
|
-
* Lower filter rows the way the platform's own serializer reads them.
|
|
3120
|
-
*
|
|
3121
|
-
* `_filters` is an ARRAY, and the join is a `join: 'OR'` key on the row that
|
|
3122
|
-
* STARTS a new alternative — `entity-vdo.ts`'s `buildQesFromEntityConfig`
|
|
3123
|
-
* splits the array into runs on that key, ANDs within a run, and ORs the runs
|
|
3124
|
-
* together. So the query model is disjunctive: `alt1 OR alt2 OR …`, each `alt`
|
|
3125
|
-
* an AND of its rows, which is why `[A, B(or), C]` is `A OR (B AND C)` and not
|
|
3126
|
-
* `(A OR B) AND C`.
|
|
3127
|
-
*
|
|
3128
|
-
* The grouped `{ logicalOperator, rows, groups }` object the authoring guide
|
|
3129
|
-
* shows is a NEWER shape: the vendored converter the local engine runs throws
|
|
3130
|
-
* `(filters ?? []).filter is not a function` on it, so emitting it would trade
|
|
3131
|
-
* a working offline rung for a shape only the current platform can read. The
|
|
3132
|
-
* array is the form both accept.
|
|
3133
|
-
*/
|
|
3134
|
-
function dataFabricFilterRows(filters, render) {
|
|
3135
|
-
return filters.map((f, i) => ({
|
|
3136
|
-
field: f.field,
|
|
3137
|
-
operator: f.operator ?? '=',
|
|
3138
|
-
value: render(f.value),
|
|
3139
|
-
// Never on the first row: a leading `join` starts an empty run, which the
|
|
3140
|
-
// platform's own loop guards against, and which means nothing anyway.
|
|
3141
|
-
...(i > 0 && f.or ? { join: 'OR' } : {}),
|
|
3142
|
-
}));
|
|
3143
|
-
}
|
|
3144
|
-
/**
|
|
3145
|
-
* The two `bindings[]` rows a FOLDER-SCOPED entity needs to survive export to
|
|
3146
|
-
* another org, and the `_resourceKey` that ties them to the node.
|
|
3147
|
-
*
|
|
3148
|
-
* Nothing else in this file emits `resource: 'Entity'`: the kind is matched by
|
|
3149
|
-
* `(resourceKey, propertyAttribute)` rather than by name, carries no
|
|
3150
|
-
* `resourceSubType`, spells the folder attribute `folderKey` (never
|
|
3151
|
-
* `folderPath` — a folderPath row receives the Orchestrator FQN, which the
|
|
3152
|
-
* Data Fabric query rejects even in the source org), and puts a DISPLAY label
|
|
3153
|
-
* in `name`. The capital `E` is load-bearing: packaging ignores a lowercase
|
|
3154
|
-
* row, so deploy emits no override at all.
|
|
3155
|
-
*
|
|
3156
|
-
* A tenant-scoped entity needs none of this — it serializes as a dotted
|
|
3157
|
-
* literal — which is why the pair is emitted only when the author supplied
|
|
3158
|
-
* both `folderKey` and `resourceKey`. `check` refuses the half-authored case,
|
|
3159
|
-
* because `_folderKey` with no binding falls back to a source-org literal that
|
|
3160
|
-
* looks portable and breaks on deploy.
|
|
3161
|
-
*/
|
|
3162
|
-
function entityBindings(entity, folderKey, resourceKey) {
|
|
3163
|
-
const base = `b${entity.replace(/[^A-Za-z0-9]/g, '')}Entity`;
|
|
3164
|
-
return [
|
|
3165
|
-
{
|
|
3166
|
-
id: `${base}Name`,
|
|
3167
|
-
name: entity,
|
|
3168
|
-
type: 'string',
|
|
3169
|
-
resource: 'Entity',
|
|
3170
|
-
resourceKey,
|
|
3171
|
-
default: entity,
|
|
3172
|
-
propertyAttribute: 'name',
|
|
3173
|
-
},
|
|
3174
|
-
{
|
|
3175
|
-
id: `${base}Folder`,
|
|
3176
|
-
name: `${entity}Folder`,
|
|
3177
|
-
type: 'string',
|
|
3178
|
-
resource: 'Entity',
|
|
3179
|
-
resourceKey,
|
|
3180
|
-
default: folderKey,
|
|
3181
|
-
propertyAttribute: 'folderKey',
|
|
3182
|
-
},
|
|
3183
|
-
];
|
|
3184
|
-
}
|
|
3185
|
-
/**
|
|
3186
|
-
* Which `core.datafabric.read` definition a step needs.
|
|
3187
|
-
*
|
|
3188
|
-
* The family's read node grew from "Read entity" into "Query entity records" at
|
|
3189
|
-
* 1.4, and `entityConfig.resultMode` is the input that expresses the difference.
|
|
3190
|
-
* The SDK keeps 1.0 pinned as the default, the same way every other family here
|
|
3191
|
-
* keeps its older default — a flow that reads one record must not change
|
|
3192
|
-
* version because the platform moved. So `resultMode` SELECTS 1.4, exactly as a
|
|
3193
|
-
* Quartz `every` selects the scheduled trigger's 1.2 (see `scheduledVersion`).
|
|
3194
|
-
*/
|
|
3195
|
-
function dataFabricReadVersion(inputs, explicit) {
|
|
3196
|
-
if (explicit !== undefined) {
|
|
3197
|
-
if (inputs?.resultMode !== undefined && explicit === '1.0') {
|
|
3198
|
-
throw new Error("dataFabricRead(): { resultMode } needs the node's 1.4 definition, but { version: '1.0' } was "
|
|
3199
|
-
+ 'requested. Drop the version, request 1.4, or drop resultMode.');
|
|
3200
|
-
}
|
|
3201
|
-
return explicit;
|
|
3202
|
-
}
|
|
3203
|
-
return inputs?.resultMode !== undefined ? '1.4' : undefined;
|
|
3204
|
-
}
|
|
3205
3084
|
/**
|
|
3206
3085
|
* The form-trigger schema: one field per flow input, the same derivation the
|
|
3207
3086
|
* designer's form editor applies to a flow's arguments (`argument-fields.ts` /
|
|
@@ -3246,42 +3125,6 @@ function scheduleInputs(inputs, nodeId) {
|
|
|
3246
3125
|
...(isPreset ? {} : { timerValue: every }),
|
|
3247
3126
|
};
|
|
3248
3127
|
}
|
|
3249
|
-
/**
|
|
3250
|
-
* Encode a delay's single `duration` into the two inputs the platform's node
|
|
3251
|
-
* carries — the SAME split as the scheduled trigger above, on the same field
|
|
3252
|
-
* names, because it is the same designer control: `timerPreset` holds the
|
|
3253
|
-
* duration when it is one of the dropdown's twelve, and otherwise holds the
|
|
3254
|
-
* literal `"custom"` with the real duration in `timerValue`. flow-v1's timer
|
|
3255
|
-
* serializer collapses them straight back (`event-definition.ts:31`), so the
|
|
3256
|
-
* two encodings are the same wait.
|
|
3257
|
-
*
|
|
3258
|
-
* Both are shapes the platform actually deploys — 26 of 26 archived
|
|
3259
|
-
* `skill-flow-delay` flows carry the preset form, 35 of 38 archived
|
|
3260
|
-
* `skill-flow-terminate` flows the custom form — so this is a choice of
|
|
3261
|
-
* encoding, not of correctness. Emitting the preset when one fits keeps the
|
|
3262
|
-
* node's Duration dropdown showing a value instead of "Custom".
|
|
3263
|
-
*
|
|
3264
|
-
* `timerType` is `timeDuration` for a `duration` and `timeDate` for `until`,
|
|
3265
|
-
* because the platform's timer serializer resolves a body as
|
|
3266
|
-
* "preset, else timerValue" for EVERY kind and so never reads `timerDate` while
|
|
3267
|
-
* a duration is present (flow-builder-sdk#70).
|
|
3268
|
-
*/
|
|
3269
|
-
function delayInputs(inputs) {
|
|
3270
|
-
// An absolute-date wait (`until`) is the definition's timeDate arm: it
|
|
3271
|
-
// requires timerDate, while the top-level required timerPreset is satisfied
|
|
3272
|
-
// with the literal 'custom' (the timeDuration-only allOf never fires).
|
|
3273
|
-
const until = String(inputs?.until ?? '').trim();
|
|
3274
|
-
if (until) {
|
|
3275
|
-
return { timerType: 'timeDate', timerPreset: 'custom', timerDate: until };
|
|
3276
|
-
}
|
|
3277
|
-
const duration = String(inputs?.duration ?? '').trim();
|
|
3278
|
-
const isPreset = DELAY_PRESETS.includes(duration);
|
|
3279
|
-
return {
|
|
3280
|
-
timerType: 'timeDuration',
|
|
3281
|
-
timerPreset: isPreset ? duration : 'custom',
|
|
3282
|
-
...(isPreset ? {} : { timerValue: duration }),
|
|
3283
|
-
};
|
|
3284
|
-
}
|
|
3285
3128
|
/** JSON-Schema type per form-field type, as flow-v1's form serializer maps them (quick-form escalations and human tasks). */
|
|
3286
3129
|
const FORM_FIELD_JSON_TYPE = {
|
|
3287
3130
|
text: { type: 'string' },
|
|
@@ -3936,22 +3779,12 @@ function describeDeclared(bindings) {
|
|
|
3936
3779
|
export function bundledActionNodeType(spec) {
|
|
3937
3780
|
switch (spec?.kind) {
|
|
3938
3781
|
case 'http': return spec.inputs?.managed ? NODE_TYPE.httpV2 : NODE_TYPE.http;
|
|
3939
|
-
case 'transform': return transformNodeType(spec.inputs);
|
|
3940
3782
|
case 'hitl': return hitlNodeType(spec.inputs);
|
|
3941
3783
|
case 'queueItem': return spec.inputs?.wait ? NODE_TYPE.queueCreateAndWait : NODE_TYPE.queueCreate;
|
|
3942
3784
|
case 'inlineAgent': return INLINE_AGENT_TYPE;
|
|
3943
|
-
case 'delay':
|
|
3944
|
-
case 'script':
|
|
3945
|
-
case 'mock':
|
|
3946
3785
|
case 'subflow':
|
|
3947
3786
|
case 'summarize':
|
|
3948
3787
|
case 'batchTransform':
|
|
3949
|
-
case 'documentClassify':
|
|
3950
|
-
case 'dynamicExtract':
|
|
3951
|
-
case 'dataFabricRead':
|
|
3952
|
-
case 'dataFabricCreate':
|
|
3953
|
-
case 'dataFabricUpdate':
|
|
3954
|
-
case 'dataFabricDelete':
|
|
3955
3788
|
case 'sendMessage':
|
|
3956
3789
|
case 'waitForMessage':
|
|
3957
3790
|
case 'conversationContext':
|
|
@@ -4021,6 +3854,25 @@ export function resolveContributionDefinition(ref, requestedVersion, where) {
|
|
|
4021
3854
|
* definition's handles: the source handle marked `isDefaultForType`, else the
|
|
4022
3855
|
* first non-error source handle, else the conventional `output`.
|
|
4023
3856
|
*/
|
|
3857
|
+
/**
|
|
3858
|
+
* Whether `def` declares `port` as a forward source handle: an exact id, or a
|
|
3859
|
+
* templated one (`outcome-{item.id}` declares every `outcome-<slug>`). `error` is
|
|
3860
|
+
* not a forward port. A definition that declares no handles cannot say, so it
|
|
3861
|
+
* accepts any port.
|
|
3862
|
+
*/
|
|
3863
|
+
function declaresForwardPort(def, port) {
|
|
3864
|
+
const handles = (def?.handleConfiguration ?? []).flatMap((g) => g?.handles ?? []);
|
|
3865
|
+
if (handles.length === 0)
|
|
3866
|
+
return true;
|
|
3867
|
+
return handles.some((h) => {
|
|
3868
|
+
if (h?.type !== 'source' || typeof h.id !== 'string' || h.id === 'error')
|
|
3869
|
+
return false;
|
|
3870
|
+
if (!h.id.includes('{'))
|
|
3871
|
+
return h.id === port;
|
|
3872
|
+
const [head, tail] = [h.id.slice(0, h.id.indexOf('{')), h.id.slice(h.id.lastIndexOf('}') + 1)];
|
|
3873
|
+
return port.length > head.length + tail.length && port.startsWith(head) && port.endsWith(tail);
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
4024
3876
|
function contributionPort(def) {
|
|
4025
3877
|
const handles = (def?.handleConfiguration ?? []).flatMap((g) => g?.handles ?? []);
|
|
4026
3878
|
const sources = handles.filter((h) => h?.type === 'source' && h?.id !== 'error');
|
|
@@ -4726,12 +4578,21 @@ export function serialize(built, opts = {}) {
|
|
|
4726
4578
|
* recorded: `check` answers for those from its per-tenant kind list.
|
|
4727
4579
|
*/
|
|
4728
4580
|
const bundledNodeDefs = new Map();
|
|
4581
|
+
/**
|
|
4582
|
+
* Node id → whether a registered PER-TENANT node type has an error port, as it
|
|
4583
|
+
* declares (its definition is built per call, so there is nothing else to ask).
|
|
4584
|
+
* A registered bundled type is in `bundledNodeDefs` like any typed family.
|
|
4585
|
+
*/
|
|
4586
|
+
const declaredErrorPorts = new Map();
|
|
4729
4587
|
/**
|
|
4730
4588
|
* Refuse an `error` edge from a node whose definition has no error port: a
|
|
4731
4589
|
* contribution-lowered node (#943) or a typed node with a bundled definition
|
|
4732
4590
|
* (#1011). Same predicate as `check`, so `compile --no-check` agrees with it.
|
|
4733
4591
|
*/
|
|
4734
4592
|
function refuseErrorEdgeWithoutPort(nodeId, stepName) {
|
|
4593
|
+
const declared = declaredErrorPorts.get(nodeId);
|
|
4594
|
+
if (declared?.port === false)
|
|
4595
|
+
throw new Error(declaredErrorPortRefusal(stepName, declared.typeName, declared.version));
|
|
4735
4596
|
const contributed = contributionDefs.get(nodeId);
|
|
4736
4597
|
const def = contributed ?? bundledNodeDefs.get(nodeId);
|
|
4737
4598
|
if (def !== undefined && !definitionHasErrorPort(def)) {
|
|
@@ -4914,14 +4775,11 @@ export function serialize(built, opts = {}) {
|
|
|
4914
4775
|
display: { label: label ?? def?.display?.label ?? id, ...(def?.display?.icon ? { icon: def.display.icon } : {}) },
|
|
4915
4776
|
};
|
|
4916
4777
|
col++;
|
|
4917
|
-
// A
|
|
4918
|
-
// (
|
|
4919
|
-
//
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
// `outputs` stay: the canvas persists them (`source: '=response'`), and
|
|
4923
|
-
// `variables.nodes[]` is derived from them.
|
|
4924
|
-
if (def?.model && !type.startsWith('core.datafabric.'))
|
|
4778
|
+
// A registered node type may carry no node-level `model`, whichever path
|
|
4779
|
+
// emits it (its own serialize, or a rawNode through emitContribution) — the
|
|
4780
|
+
// Data Fabric verbs, whose reasons are in their family (`nodeModel`).
|
|
4781
|
+
const omitModel = registeredRawType(type, String(def?.version ?? ''))?.impl.nodeModel === 'omit';
|
|
4782
|
+
if (def?.model && !omitModel)
|
|
4925
4783
|
node.model = subNodeId(jsonClone(def.model), id);
|
|
4926
4784
|
if (def?.outputDefinition)
|
|
4927
4785
|
node.outputs = subNodeId(jsonClone(def.outputDefinition), id);
|
|
@@ -5124,7 +4982,7 @@ export function serialize(built, opts = {}) {
|
|
|
5124
4982
|
function successPort(type) {
|
|
5125
4983
|
if (type === NODE_TYPE.http || type === NODE_TYPE.httpV2)
|
|
5126
4984
|
return 'default';
|
|
5127
|
-
if (type === NODE_TYPE.
|
|
4985
|
+
if (type === NODE_TYPE.loop)
|
|
5128
4986
|
return 'success';
|
|
5129
4987
|
// Both queue definitions declare exactly one source handle, `success` — and
|
|
5130
4988
|
// the shipped `sample-flows/queue-operations.flow` wires its edges to it.
|
|
@@ -5156,9 +5014,6 @@ export function serialize(built, opts = {}) {
|
|
|
5156
5014
|
// as it is in all 151 deployed IxP instances.
|
|
5157
5015
|
if (type.startsWith('uipath.ixp.'))
|
|
5158
5016
|
return 'success';
|
|
5159
|
-
// Classify declares the same single `success` source handle as the extractors.
|
|
5160
|
-
if (type === NODE_TYPE.documentClassify)
|
|
5161
|
-
return 'success';
|
|
5162
5017
|
// connectors, the trigger, the merge join and the mock placeholder all expose
|
|
5163
5018
|
// their forward edge on `output` (merge's definition declares exactly one
|
|
5164
5019
|
// source handle, `output`; so does `core.logic.mock`, and all 74 archived
|
|
@@ -5185,27 +5040,112 @@ export function serialize(built, opts = {}) {
|
|
|
5185
5040
|
const connectorRefusal = connectorRawNodeRefusal(def.nodeType);
|
|
5186
5041
|
if (connectorRefusal)
|
|
5187
5042
|
throw new Error(`${where}: ${connectorRefusal}`);
|
|
5188
|
-
//
|
|
5189
|
-
// every contribution, whichever class or SDK copy made it:
|
|
5190
|
-
// cannot see it, and the node would ignore it at run time.
|
|
5191
|
-
const unknownInput =
|
|
5043
|
+
// An input a registered node type never reads (a Data Fabric `_sortOptions`)
|
|
5044
|
+
// is refused for every contribution, whichever class or SDK copy made it:
|
|
5045
|
+
// `validate` cannot see it, and the node would ignore it at run time.
|
|
5046
|
+
const unknownInput = rawNodeInputProblem(def.nodeType, def.version, contribution.inputs);
|
|
5192
5047
|
if (unknownInput?.severity === 'error')
|
|
5193
5048
|
throw new Error(`${where}: ${unknownInput.message}`);
|
|
5194
5049
|
const id = stepUid(scope, stepName);
|
|
5195
|
-
|
|
5050
|
+
contributionDefs.set(id, def);
|
|
5051
|
+
return placeStepNode({ where, stepName, id, typeName: def.nodeType, requestedVersion, def, contribution, tails, scope, parentId });
|
|
5052
|
+
}
|
|
5053
|
+
/**
|
|
5054
|
+
* Place one step node from a contribution — the one path a custom FlowAction
|
|
5055
|
+
* (emitContribution) and a registered node type (emitRegistered) share
|
|
5056
|
+
* (docs/NODE_TYPE_ARCHITECTURE_DESIGN.md, Phase 3). The serializer owns the
|
|
5057
|
+
* rest: the node envelope from the definition, rendering, bindings, edges, and
|
|
5058
|
+
* the forward port. `def` is undefined only for a registered bundled type,
|
|
5059
|
+
* whose definition is `<typeName>@<version>` from the bundle.
|
|
5060
|
+
*/
|
|
5061
|
+
function placeStepNode(o) {
|
|
5062
|
+
const { id, contribution, scope } = o;
|
|
5063
|
+
const node = makeNode(o.typeName, id, o.stepName, { parentId: o.parentId, def: o.def, requestedVersion: o.requestedVersion });
|
|
5064
|
+
if (contribution.model === 'omit')
|
|
5065
|
+
delete node.model;
|
|
5196
5066
|
node.inputs = renderDeep(contribution.inputs ?? {}, scope.rename);
|
|
5197
5067
|
if (contribution.outputs) {
|
|
5198
5068
|
node.outputs = { ...(node.outputs ?? {}), ...subNodeId(jsonClone(contribution.outputs), id) };
|
|
5199
5069
|
}
|
|
5200
|
-
|
|
5201
|
-
|
|
5070
|
+
// A bundled registered type returns no definition; its bindings still close
|
|
5071
|
+
// against the bundled one (#942), counting what it bound through the context
|
|
5072
|
+
// as supplied (first, in the order it bound them, as when `bind` hoisted directly).
|
|
5073
|
+
const def = o.def ?? bundledDefinition(o.typeName, o.requestedVersion);
|
|
5074
|
+
hoistContributionBindings(def, [...(o.bound ?? []), ...(contribution.bindings ?? [])], o.where);
|
|
5202
5075
|
scope.nodes.push(node);
|
|
5203
|
-
for (const t of tails)
|
|
5076
|
+
for (const t of o.tails)
|
|
5204
5077
|
addEdge(scope, t, id);
|
|
5205
|
-
return [{ node: id, port: contributionPort(def) }];
|
|
5078
|
+
return [{ node: id, port: contribution.port ?? contributionPort(def) }];
|
|
5079
|
+
}
|
|
5080
|
+
/**
|
|
5081
|
+
* Lower a step whose kind is a registered node type (flow-node-registry.ts):
|
|
5082
|
+
* the node type serializes its spec through a context, and this owns the rest —
|
|
5083
|
+
* id, label, `typeVersion`, definition, rendering, bindings, edges — exactly as
|
|
5084
|
+
* for a custom contribution. docs/NODE_TYPE_ARCHITECTURE_DESIGN.md §4.
|
|
5085
|
+
*/
|
|
5086
|
+
function emitRegistered(entry, step, tails, scope, parentId) {
|
|
5087
|
+
const where = `step "${step.name}"`;
|
|
5088
|
+
const spec = step.spec;
|
|
5089
|
+
const impl = resolveVersion(entry.type, spec, step.options?.version, where);
|
|
5090
|
+
const id = stepUid(scope, step.name);
|
|
5091
|
+
// As for every typed family: record the failure-envelope boundary before any
|
|
5092
|
+
// expression renders, so a handler reading this step resolves correctly.
|
|
5093
|
+
const envelope = ownEntry(ERROR_ENVELOPE_VAR, impl.typeName);
|
|
5094
|
+
if (envelope)
|
|
5095
|
+
scope.rename.errorEnvelopes.set(step.name, envelope);
|
|
5096
|
+
const ctx = {
|
|
5097
|
+
step: step.name,
|
|
5098
|
+
version: impl.version,
|
|
5099
|
+
render: (value) => renderValue(value, scope.rename),
|
|
5100
|
+
renameRefs: (js) => renameRefs(js, scope.rename),
|
|
5101
|
+
stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
|
|
5102
|
+
mint: (seed) => stableId(`${built.id}:${id}:${seed}`),
|
|
5103
|
+
bind: (b) => { bound.push(b); },
|
|
5104
|
+
definition: (typeName, version) => bundledDefinition(typeName, version),
|
|
5105
|
+
};
|
|
5106
|
+
const bound = [];
|
|
5107
|
+
const contribution = impl.serialize(spec, ctx);
|
|
5108
|
+
// The definition contract, both halves: a per-tenant type supplies its
|
|
5109
|
+
// definition, and a bundled type does not (its definition is the bundle's, and
|
|
5110
|
+
// makeNode records it, so the error-edge refusal and check ask the same one).
|
|
5111
|
+
let def;
|
|
5112
|
+
if (impl.definition === 'bundled' && contribution.definition !== undefined) {
|
|
5113
|
+
throw new Error(`internal SDK error: ${where}: bundled node type ${impl.typeName}@${impl.version} returned a definition; `
|
|
5114
|
+
+ 'the bundle owns it — omit `definition`, or declare the type per-tenant.');
|
|
5115
|
+
}
|
|
5116
|
+
if (contribution.definition !== undefined) {
|
|
5117
|
+
def = resolveContributionDefinition(contribution.definition, impl.version, where);
|
|
5118
|
+
}
|
|
5119
|
+
else if (impl.definition === 'per-tenant') {
|
|
5120
|
+
throw new Error(`internal SDK error: ${where}: per-tenant node type ${impl.typeName}@${impl.version} returned no definition.`);
|
|
5121
|
+
}
|
|
5122
|
+
if (impl.definition === 'per-tenant') {
|
|
5123
|
+
const port = typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
|
|
5124
|
+
// The declaration answers for check, which has no definition; here the
|
|
5125
|
+
// definition is in hand, and the two must agree (#1011).
|
|
5126
|
+
if (port && def !== undefined && !definitionHasErrorPort(def)) {
|
|
5127
|
+
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${impl.version} declares an error port, `
|
|
5128
|
+
+ 'but the definition it supplied has no error handle and no supportsErrorHandling.');
|
|
5129
|
+
}
|
|
5130
|
+
declaredErrorPorts.set(id, { port, typeName: impl.typeName, version: impl.version });
|
|
5131
|
+
}
|
|
5132
|
+
if (contribution.port !== undefined && !declaresForwardPort(def ?? bundledDefinition(impl.typeName, impl.version), contribution.port)) {
|
|
5133
|
+
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${impl.version} leaves by port "${contribution.port}", `
|
|
5134
|
+
+ 'which its definition declares no source handle for.');
|
|
5135
|
+
}
|
|
5136
|
+
return placeStepNode({
|
|
5137
|
+
where, stepName: step.name, id, typeName: impl.typeName, requestedVersion: impl.version, def, contribution, bound, tails, scope, parentId,
|
|
5138
|
+
});
|
|
5206
5139
|
}
|
|
5207
5140
|
function emitAction(step, tails, scope, parentId) {
|
|
5208
5141
|
const spec = step.spec;
|
|
5142
|
+
// A registered node type serializes itself; every other kind falls through to
|
|
5143
|
+
// the branches below until its family is ported (FB014's pins are the list).
|
|
5144
|
+
const registered = typeof spec.kind === 'string'
|
|
5145
|
+
? FLOW_NODE_TYPES.byKind(spec.kind, spec)
|
|
5146
|
+
: undefined;
|
|
5147
|
+
if (registered)
|
|
5148
|
+
return emitRegistered(registered, step, tails, scope, parentId);
|
|
5209
5149
|
if (spec.kind === 'subflow') {
|
|
5210
5150
|
const id = stepUid(scope, step.name);
|
|
5211
5151
|
// `subflows` is ONE flat map for the whole file, keyed by the calling
|
|
@@ -6310,154 +6250,6 @@ export function serialize(built, opts = {}) {
|
|
|
6310
6250
|
}
|
|
6311
6251
|
return [{ node: id, port: successPort(hitlType) }];
|
|
6312
6252
|
}
|
|
6313
|
-
if (spec.kind === 'mock') {
|
|
6314
|
-
// A placeholder is a pure `makeNode` node: the definition supplies `model`
|
|
6315
|
-
// and the single `output` (`source: "null"`), and there is nothing else to
|
|
6316
|
-
// write. `inputs: {}` rather than an omitted key because that is what the
|
|
6317
|
-
// vendored Studio-Web golden writes (`shared/sample-flows/new.flow`, all
|
|
6318
|
-
// three of its mock nodes) and what 36 of the 74 archived instances carry.
|
|
6319
|
-
//
|
|
6320
|
-
// Nothing here can open a fixture channel: a runner that honours one looks
|
|
6321
|
-
// for `fixture`/`output`/`value` in the node's `inputs`, and the factory
|
|
6322
|
-
// takes no arguments, so `inputs` is always the empty object. That
|
|
6323
|
-
// is the #125 fence, made unrepresentable rather than documented.
|
|
6324
|
-
const id = stepUid(scope, step.name);
|
|
6325
|
-
const node = makeNode(NODE_TYPE.mock, id, step.name, { parentId, requestedVersion: step.options?.version });
|
|
6326
|
-
node.inputs = {};
|
|
6327
|
-
scope.nodes.push(node);
|
|
6328
|
-
for (const t of tails)
|
|
6329
|
-
addEdge(scope, t, id);
|
|
6330
|
-
return [{ node: id, port: successPort(NODE_TYPE.mock) }];
|
|
6331
|
-
}
|
|
6332
|
-
if (spec.kind === 'documentClassify' || spec.kind === 'dynamicExtract') {
|
|
6333
|
-
const type = spec.kind === 'documentClassify' ? NODE_TYPE.documentClassify : NODE_TYPE.dynamicExtract;
|
|
6334
|
-
const id = stepUid(scope, step.name);
|
|
6335
|
-
const node = makeNode(type, id, step.name, { parentId, requestedVersion: step.options?.version });
|
|
6336
|
-
const def = defByType.get(type);
|
|
6337
|
-
const defaults = jsonClone(def?.inputDefaults ?? {});
|
|
6338
|
-
// A `uipath.ixp.*` fileRef must stay a PLAIN `=js:` string — the platform
|
|
6339
|
-
// reads it unwrapped (see migrate's UNWRAPPED_INPUTS); classify follows
|
|
6340
|
-
// the default envelope rules.
|
|
6341
|
-
const fileRef = renderValue(toExpr(spec.inputs.fileRef), scope.rename);
|
|
6342
|
-
if (spec.kind === 'documentClassify') {
|
|
6343
|
-
node.inputs = {
|
|
6344
|
-
...defaults,
|
|
6345
|
-
fileRef,
|
|
6346
|
-
...(spec.inputs.pageRange !== undefined ? { pageRange: spec.inputs.pageRange } : {}),
|
|
6347
|
-
...(spec.inputs.splitPages !== undefined ? { splitPages: spec.inputs.splitPages } : {}),
|
|
6348
|
-
...(spec.inputs.modelConfig !== undefined
|
|
6349
|
-
? { modelConfig: { ...(defaults.modelConfig ?? {}), ...spec.inputs.modelConfig } }
|
|
6350
|
-
: {}),
|
|
6351
|
-
};
|
|
6352
|
-
}
|
|
6353
|
-
else {
|
|
6354
|
-
const m = spec.inputs.model;
|
|
6355
|
-
node.inputs = {
|
|
6356
|
-
...defaults,
|
|
6357
|
-
// The deployment identity Studio Web's panel destructures — both the
|
|
6358
|
-
// nested `model` object and the flat mirrors the definition declares.
|
|
6359
|
-
model: { modelName: m.modelName, folderKey: m.folderKey },
|
|
6360
|
-
modelName: m.modelName,
|
|
6361
|
-
folderKey: m.folderKey,
|
|
6362
|
-
...(m.projectId !== undefined ? { projectId: m.projectId } : {}),
|
|
6363
|
-
...(m.projectName !== undefined ? { projectName: m.projectName } : {}),
|
|
6364
|
-
...(m.folderName !== undefined ? { folderName: m.folderName } : {}),
|
|
6365
|
-
...(m.versionTag !== undefined ? { versionTag: m.versionTag } : {}),
|
|
6366
|
-
fileRef,
|
|
6367
|
-
...(spec.inputs.pageRange !== undefined ? { pageRange: spec.inputs.pageRange } : {}),
|
|
6368
|
-
...(spec.inputs.modelConfig !== undefined
|
|
6369
|
-
? { modelConfig: { ...(defaults.modelConfig ?? {}), ...spec.inputs.modelConfig } }
|
|
6370
|
-
: {}),
|
|
6371
|
-
...(spec.inputs.schema !== undefined ? { schemaDocument: spec.inputs.schema } : {}),
|
|
6372
|
-
};
|
|
6373
|
-
// The definition's model declares `source: true`: the platform mints a
|
|
6374
|
-
// stable storage slot per instance; omit it and the node validates then
|
|
6375
|
-
// faults. Deterministic, like every other minted identity here.
|
|
6376
|
-
node.inputs.source = stableId(`${built.id}:${id}:source`);
|
|
6377
|
-
// IxP instances must NOT carry the manifest's model block — the slim
|
|
6378
|
-
// { type, serviceType } lives only in definitions[] (product validate
|
|
6379
|
-
// rejects a node-level copy).
|
|
6380
|
-
delete node.model;
|
|
6381
|
-
}
|
|
6382
|
-
scope.nodes.push(node);
|
|
6383
|
-
for (const t of tails)
|
|
6384
|
-
addEdge(scope, t, id);
|
|
6385
|
-
return [{ node: id, port: successPort(type) }];
|
|
6386
|
-
}
|
|
6387
|
-
if (spec.kind === 'dataFabricRead' || spec.kind === 'dataFabricCreate'
|
|
6388
|
-
|| spec.kind === 'dataFabricUpdate' || spec.kind === 'dataFabricDelete') {
|
|
6389
|
-
const type = NODE_TYPE[spec.kind];
|
|
6390
|
-
const id = stepUid(scope, step.name);
|
|
6391
|
-
const node = makeNode(type, id, step.name, {
|
|
6392
|
-
parentId,
|
|
6393
|
-
requestedVersion: spec.kind === 'dataFabricRead'
|
|
6394
|
-
? dataFabricReadVersion(spec.inputs, step.options?.version)
|
|
6395
|
-
: step.options?.version,
|
|
6396
|
-
});
|
|
6397
|
-
const cfg = { entityName: spec.inputs.entity };
|
|
6398
|
-
// `recordSource` is how update and delete both name their target; the
|
|
6399
|
-
// read step's emitted node id is rename-aware, for subflow copies.
|
|
6400
|
-
const targetRecord = (record) => {
|
|
6401
|
-
if (typeof record.fromRead === 'string') {
|
|
6402
|
-
cfg.recordSource = 'fromRead';
|
|
6403
|
-
cfg.readEntityNodeId = scope.stepNodeId.get(record.fromRead) ?? record.fromRead;
|
|
6404
|
-
}
|
|
6405
|
-
else {
|
|
6406
|
-
cfg.recordSource = 'byId';
|
|
6407
|
-
cfg.recordId = String(renderValue(record.byId, scope.rename));
|
|
6408
|
-
}
|
|
6409
|
-
};
|
|
6410
|
-
const fieldRows = (values) => Object.entries(values ?? {}).map(([field, value]) => ({
|
|
6411
|
-
field,
|
|
6412
|
-
value: String(renderValue(value, scope.rename)),
|
|
6413
|
-
}));
|
|
6414
|
-
if (spec.kind === 'dataFabricRead') {
|
|
6415
|
-
// Only 1.4 declares it, and only 1.4 is emitted when it is set (see
|
|
6416
|
-
// dataFabricReadVersion) — so writing it can never land on a 1.0 node,
|
|
6417
|
-
// where it would be an input the definition does not know.
|
|
6418
|
-
if (spec.inputs.resultMode !== undefined)
|
|
6419
|
-
cfg.resultMode = spec.inputs.resultMode;
|
|
6420
|
-
if (spec.inputs.limit !== undefined)
|
|
6421
|
-
cfg._recordLimit = spec.inputs.limit;
|
|
6422
|
-
if (spec.inputs.skip !== undefined)
|
|
6423
|
-
cfg._skip = spec.inputs.skip;
|
|
6424
|
-
if (spec.inputs.sort !== undefined) {
|
|
6425
|
-
cfg._sort = { field: spec.inputs.sort.field, direction: spec.inputs.sort.direction ?? 'asc' };
|
|
6426
|
-
}
|
|
6427
|
-
if (spec.inputs.fields?.length)
|
|
6428
|
-
cfg._selectedFieldNames = [...spec.inputs.fields];
|
|
6429
|
-
if (spec.inputs.filters?.length) {
|
|
6430
|
-
cfg._filters = dataFabricFilterRows(spec.inputs.filters, (value) => String(renderValue(value, scope.rename)));
|
|
6431
|
-
}
|
|
6432
|
-
}
|
|
6433
|
-
else if (spec.kind === 'dataFabricCreate') {
|
|
6434
|
-
cfg.fieldValues = fieldRows(spec.inputs.values);
|
|
6435
|
-
}
|
|
6436
|
-
else if (spec.kind === 'dataFabricUpdate') {
|
|
6437
|
-
targetRecord(spec.inputs.record);
|
|
6438
|
-
cfg.fieldUpdates = fieldRows(spec.inputs.set);
|
|
6439
|
-
}
|
|
6440
|
-
else {
|
|
6441
|
-
targetRecord(spec.inputs.record);
|
|
6442
|
-
}
|
|
6443
|
-
if (spec.inputs.folderKey !== undefined)
|
|
6444
|
-
cfg._folderKey = spec.inputs.folderKey;
|
|
6445
|
-
// A folder-scoped entity is portable only with its `Entity` bindings; the
|
|
6446
|
-
// half-authored form is refused by check(), so reaching here with both
|
|
6447
|
-
// means emit both. Same id for the same (entity, resourceKey), so two
|
|
6448
|
-
// steps on one entity share one pair rather than duplicating it.
|
|
6449
|
-
if (spec.inputs.folderKey !== undefined && spec.inputs.resourceKey !== undefined) {
|
|
6450
|
-
cfg._resourceKey = spec.inputs.resourceKey;
|
|
6451
|
-
for (const entry of entityBindings(spec.inputs.entity, spec.inputs.folderKey, spec.inputs.resourceKey)) {
|
|
6452
|
-
resourceBindings.set(entry.id, entry);
|
|
6453
|
-
}
|
|
6454
|
-
}
|
|
6455
|
-
node.inputs = { entityConfig: cfg };
|
|
6456
|
-
scope.nodes.push(node);
|
|
6457
|
-
for (const t of tails)
|
|
6458
|
-
addEdge(scope, t, id);
|
|
6459
|
-
return [{ node: id, port: successPort(type) }];
|
|
6460
|
-
}
|
|
6461
6253
|
// A custom FlowAction subclass carries no `kind`; it compiles through its
|
|
6462
6254
|
// own contribution (design §4). Anything else without a recognized kind is
|
|
6463
6255
|
// an authoring error — it must NOT fall through into the script branch
|
|
@@ -6469,7 +6261,7 @@ export function serialize(built, opts = {}) {
|
|
|
6469
6261
|
}
|
|
6470
6262
|
throw new Error(`step "${step.name}": not an action spec — pass a factory result, subflow(), or a FlowAction subclass instance.`);
|
|
6471
6263
|
}
|
|
6472
|
-
if (
|
|
6264
|
+
if (spec.kind !== 'http') {
|
|
6473
6265
|
throw new Error(`step "${step.name}": unknown action kind "${spec.kind}".`);
|
|
6474
6266
|
}
|
|
6475
6267
|
const type = bundledActionNodeType(spec);
|
|
@@ -6482,6 +6274,7 @@ export function serialize(built, opts = {}) {
|
|
|
6482
6274
|
const envelope = ownEntry(ERROR_ENVELOPE_VAR, type);
|
|
6483
6275
|
if (envelope)
|
|
6484
6276
|
scope.rename.errorEnvelopes.set(step.name, envelope);
|
|
6277
|
+
// The guard above leaves http; registered kinds returned at the top.
|
|
6485
6278
|
node.inputs = actionInputs(spec, scope.rename, opts, step.name);
|
|
6486
6279
|
if (spec.kind === 'http') {
|
|
6487
6280
|
node.outputs = httpOutputs(node.outputs, spec.inputs.returns, spec.inputs.managed);
|
|
@@ -6501,12 +6294,6 @@ export function serialize(built, opts = {}) {
|
|
|
6501
6294
|
resourceBindings.set(entry.id, entry);
|
|
6502
6295
|
}
|
|
6503
6296
|
}
|
|
6504
|
-
if (spec.kind === 'script') {
|
|
6505
|
-
node.outputs = scriptOutputs(node.outputs, spec.inputs.returns, spec.inputs.code);
|
|
6506
|
-
}
|
|
6507
|
-
if (spec.kind === 'transform') {
|
|
6508
|
-
node.outputs = transformOutputs(node.outputs);
|
|
6509
|
-
}
|
|
6510
6297
|
scope.nodes.push(node);
|
|
6511
6298
|
for (const t of tails)
|
|
6512
6299
|
addEdge(scope, t, id);
|
|
@@ -7364,88 +7151,7 @@ export function assertBindingPlaceholdersResolve(flow) {
|
|
|
7364
7151
|
+ `"invalid GUID <bindings.…>") and refuses to publish. This is a serializer defect, not an authoring `
|
|
7365
7152
|
+ `error; report it with the flow source.`);
|
|
7366
7153
|
}
|
|
7367
|
-
/**
|
|
7368
|
-
* Emit a transform's `inputs` in the canonical shape the node's runtime reads:
|
|
7369
|
-
* `collection` as a BARE variable path (not `=js:`-prefixed — it is a path
|
|
7370
|
-
* field, and the runtime turns the string into a lookup), and `operations` as
|
|
7371
|
-
* the array of `{ id, type, config }` descriptors the designer writes.
|
|
7372
|
-
*
|
|
7373
|
-
* Ids are synthesized here rather than asked of the author, the same way switch
|
|
7374
|
-
* case ids are: they identify a row in the designer's editor and carry no
|
|
7375
|
-
* meaning for the author. Defaults are made explicit for the same reason the
|
|
7376
|
-
* HTTP case spells out `method`/`contentType` — the emitted file should say
|
|
7377
|
-
* what it does rather than rely on the runtime's fallbacks.
|
|
7378
|
-
*/
|
|
7379
|
-
function transformInputs(inputs, rename = NO_RENAME) {
|
|
7380
|
-
const operations = (inputs.operations ?? []).map((op, i) => {
|
|
7381
|
-
const id = `op${i + 1}`;
|
|
7382
|
-
if (op.type === 'filter') {
|
|
7383
|
-
return {
|
|
7384
|
-
id,
|
|
7385
|
-
type: 'filter',
|
|
7386
|
-
config: {
|
|
7387
|
-
operation: op.operation ?? 'and',
|
|
7388
|
-
filters: (op.filters ?? []).map((f, j) => ({
|
|
7389
|
-
id: `f${j + 1}`,
|
|
7390
|
-
field: f.field,
|
|
7391
|
-
condition: f.condition,
|
|
7392
|
-
// Value-free conditions (is_null, is_empty, …) legitimately omit it.
|
|
7393
|
-
...(f.value !== undefined ? { value: f.value } : {}),
|
|
7394
|
-
})),
|
|
7395
|
-
},
|
|
7396
|
-
};
|
|
7397
|
-
}
|
|
7398
|
-
if (op.type === 'map') {
|
|
7399
|
-
return {
|
|
7400
|
-
id,
|
|
7401
|
-
type: 'map',
|
|
7402
|
-
config: {
|
|
7403
|
-
keepOriginalFields: op.keepOriginalFields ?? false,
|
|
7404
|
-
mappings: (op.mappings ?? []).map((m, j) => ({
|
|
7405
|
-
id: `m${j + 1}`,
|
|
7406
|
-
field: m.field,
|
|
7407
|
-
transformation: m.transformation ?? 'copy',
|
|
7408
|
-
// '' is the runtime's "keep the original name" sentinel.
|
|
7409
|
-
renameTo: m.renameTo ?? '',
|
|
7410
|
-
})),
|
|
7411
|
-
},
|
|
7412
|
-
};
|
|
7413
|
-
}
|
|
7414
|
-
return {
|
|
7415
|
-
id,
|
|
7416
|
-
type: 'groupBy',
|
|
7417
|
-
config: {
|
|
7418
|
-
groupByField: op.groupByField,
|
|
7419
|
-
aggregations: (op.aggregations ?? []).map((a, j) => ({
|
|
7420
|
-
id: `a${j + 1}`,
|
|
7421
|
-
// `count` needs no field; the runtime expects the key present anyway.
|
|
7422
|
-
field: a.field ?? '',
|
|
7423
|
-
operation: a.operation,
|
|
7424
|
-
alias: a.alias,
|
|
7425
|
-
})),
|
|
7426
|
-
},
|
|
7427
|
-
};
|
|
7428
|
-
});
|
|
7429
|
-
return { collection: renameRefs(collectionPath(inputs.collection), rename), operations };
|
|
7430
|
-
}
|
|
7431
|
-
/**
|
|
7432
|
-
* The `collection` string. A bare reference (`v('x')` / `out('s','p')`) is
|
|
7433
|
-
* already the path the runtime wants, so it is emitted verbatim. Anything else
|
|
7434
|
-
* cannot be expressed as a path — `check` rejects it (TRANSFORM_COLLECTION_*)
|
|
7435
|
-
* before serialization, so reaching here with one means `compile` was called
|
|
7436
|
-
* without a check; emit the raw text so the failure stays visible downstream
|
|
7437
|
-
* rather than becoming a silently empty collection.
|
|
7438
|
-
*/
|
|
7439
|
-
function collectionPath(collection) {
|
|
7440
|
-
if (collection instanceof Expr)
|
|
7441
|
-
return collection.literal ? JSON.stringify(collection.value) : collection.js;
|
|
7442
|
-
return String(collection);
|
|
7443
|
-
}
|
|
7444
7154
|
function actionInputs(spec, rename = NO_RENAME, opts = {}, stepName = '') {
|
|
7445
|
-
if (spec.kind === 'transform')
|
|
7446
|
-
return transformInputs(spec.inputs, rename);
|
|
7447
|
-
if (spec.kind === 'delay')
|
|
7448
|
-
return delayInputs(spec.inputs);
|
|
7449
7155
|
if (spec.kind === 'http') {
|
|
7450
7156
|
const i = spec.inputs;
|
|
7451
7157
|
if (i.managed)
|
|
@@ -7468,10 +7174,7 @@ function actionInputs(spec, rename = NO_RENAME, opts = {}, stepName = '') {
|
|
|
7468
7174
|
...httpBranchRows(i, rename),
|
|
7469
7175
|
};
|
|
7470
7176
|
}
|
|
7471
|
-
|
|
7472
|
-
// same way everything else does — so it needs the rename too, or a reused
|
|
7473
|
-
// child's script would read the first copy's node.
|
|
7474
|
-
return { script: renameRefs(spec.inputs.code, rename) };
|
|
7177
|
+
throw new Error(`internal SDK error: no inputs for action kind "${spec.kind}".`);
|
|
7475
7178
|
}
|
|
7476
7179
|
/**
|
|
7477
7180
|
* `timeout` / `retryCount` — the two request settings BOTH http node types carry.
|
|
@@ -7653,60 +7356,3 @@ function httpOutputs(base, returns, managed) {
|
|
|
7653
7356
|
};
|
|
7654
7357
|
return out;
|
|
7655
7358
|
}
|
|
7656
|
-
/**
|
|
7657
|
-
* A transform node publishes a COLLECTION — always.
|
|
7658
|
-
*
|
|
7659
|
-
* All four transform definitions declare `output: {type: 'object', description:
|
|
7660
|
-
* 'The transformed data result'}`, which makes the designer type
|
|
7661
|
-
* `out('<step>')` as `Record<string, any>` and reject mapping it into an
|
|
7662
|
-
* `array` flow output — the same defect as the script node's, one node type
|
|
7663
|
-
* over, and it hits five of the shipped examples.
|
|
7664
|
-
*
|
|
7665
|
-
* Unlike a script this needs no inference and no author declaration, because it
|
|
7666
|
-
* is not a property of the particular node: `filter` and `map` are array→array,
|
|
7667
|
-
* and `group-by` is too — the engine's own implementation says so in as many
|
|
7668
|
-
* words (`transform-operations.ts`: "Always return an array", returning
|
|
7669
|
-
* `Array.from(groups.entries()).map(…)` rather than an object keyed by group).
|
|
7670
|
-
* So the type is knowable from the node TYPE alone, and is simply written down.
|
|
7671
|
-
*/
|
|
7672
|
-
function transformOutputs(base) {
|
|
7673
|
-
// An unrecognized `variant` resolves to no definition at all, so there are no
|
|
7674
|
-
// outputs to refine — and `jsonClone(undefined)` throws ("undefined" is not valid
|
|
7675
|
-
// JSON) rather than passing the node through.
|
|
7676
|
-
if (!base || typeof base !== 'object')
|
|
7677
|
-
return base;
|
|
7678
|
-
const out = jsonClone(base);
|
|
7679
|
-
if (out.output)
|
|
7680
|
-
out.output.type = 'array';
|
|
7681
|
-
return out;
|
|
7682
|
-
}
|
|
7683
|
-
/**
|
|
7684
|
-
* Declare what a `core.action.script` node actually publishes.
|
|
7685
|
-
*
|
|
7686
|
-
* Same instance-vs-definition split as `httpOutputs` above, for the same reason
|
|
7687
|
-
* one level over: the platform's definition says `output: {type: 'object'}`
|
|
7688
|
-
* because a node TYPE cannot know what a particular body returns, so the
|
|
7689
|
-
* designer types every script read as `Record<string, any>` and rejects mapping
|
|
7690
|
-
* one into a `string` flow output — on a flow that runs. What the body returns
|
|
7691
|
-
* is knowable here, at author time, and this is where it gets written down.
|
|
7692
|
-
*
|
|
7693
|
-
* `returns` wins; otherwise the body is read (see `inferScriptShape`, which
|
|
7694
|
-
* answers only when every `return` agrees). When neither says anything the
|
|
7695
|
-
* definition's open object stands, byte-identical to what this emitted before.
|
|
7696
|
-
*/
|
|
7697
|
-
function scriptOutputs(base, returns, code) {
|
|
7698
|
-
const shape = returns !== undefined ? declaredScriptShape(returns) : inferScriptShape(code);
|
|
7699
|
-
// Nothing knowable, or knowably the open object the definition already
|
|
7700
|
-
// declares — leave the node exactly as it was.
|
|
7701
|
-
if (!shape || (shape.type === 'object' && !shape.properties))
|
|
7702
|
-
return base;
|
|
7703
|
-
if (!base || typeof base !== 'object')
|
|
7704
|
-
return base;
|
|
7705
|
-
const out = jsonClone(base);
|
|
7706
|
-
if (!out.output)
|
|
7707
|
-
return out;
|
|
7708
|
-
out.output.type = shape.type;
|
|
7709
|
-
if (shape.properties)
|
|
7710
|
-
out.output.properties = shape.properties;
|
|
7711
|
-
return out;
|
|
7712
|
-
}
|