@uipath/maestro-builder-sdk 6.18.10 → 6.20.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 +506 -473
- package/dist/check.js +358 -695
- package/dist/cli/commands/flow.js +3 -0
- package/dist/compile-cli.js +51 -0
- package/dist/core/actions.d.ts +132 -765
- package/dist/core/actions.js +1 -307
- package/dist/core/binding-identity.d.ts +22 -0
- package/dist/core/binding-identity.js +28 -0
- package/dist/core/context-index.d.ts +34 -0
- package/dist/core/context-index.js +38 -0
- package/dist/core/definitionless-node-types.d.ts +16 -0
- package/dist/core/definitionless-node-types.js +16 -0
- package/dist/core/error-port.d.ts +6 -0
- package/dist/core/error-port.js +9 -0
- package/dist/core/form-field-type.d.ts +33 -0
- package/dist/core/form-field-type.js +30 -0
- package/dist/core/hitl-routing.d.ts +8 -0
- package/dist/core/hitl-routing.js +17 -1
- 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/process-resource-sort-order.d.ts +40 -0
- package/dist/core/process-resource-sort-order.js +44 -0
- package/dist/core/typed-raw-node.d.ts +13 -62
- package/dist/core/typed-raw-node.js +40 -154
- package/dist/core/wire-expr.d.ts +27 -0
- package/dist/core/wire-expr.js +34 -0
- package/dist/decompile-cli.js +11 -1
- package/dist/decompile.d.ts +8 -3
- package/dist/decompile.js +665 -354
- package/dist/flow-definitions.d.ts +10 -0
- package/dist/flow-definitions.js +35 -0
- package/dist/flow-expr-check.js +1 -1
- 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 +40 -1
- package/dist/flow-sdk.js +29 -2
- package/dist/flow-step-tree.js +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/merge-cli.js +25 -8
- package/dist/merge-roundtrip.d.ts +81 -0
- package/dist/merge-roundtrip.js +404 -0
- package/dist/merge.d.ts +35 -6
- package/dist/merge.js +124 -24
- package/dist/node-placement.d.ts +93 -0
- package/dist/node-placement.js +163 -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 +245 -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/pipeline-front-door.js +5 -3
- package/dist/serialize.js +246 -549
- package/package.json +2 -2
package/dist/serialize.js
CHANGED
|
@@ -10,18 +10,24 @@ 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, bundledVersions } 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';
|
|
16
|
+
import { contextIndexType } from './core/context-index.js';
|
|
15
17
|
import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
16
|
-
import {
|
|
18
|
+
import { rawNodeInputProblem, registeredRawType } from './raw-node-advice.js';
|
|
17
19
|
import { planContributionBindings } from './core/contribution-bindings.js';
|
|
18
|
-
import { definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
|
|
20
|
+
import { declaredErrorPortRefusal, definitionHasErrorPort, errorPortRefusal } from './core/error-port.js';
|
|
21
|
+
import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
22
|
+
import { resolveVersion } from './core/nodes/versioned.js';
|
|
19
23
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
20
24
|
import { connectionConnectors } from './bindings-v2.js';
|
|
21
25
|
import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
|
|
22
26
|
import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
|
|
23
27
|
import { genericEventObjectRefusal } from './core/event-checks.js';
|
|
24
|
-
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
|
|
28
|
+
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeIdOf, outcomeSlug } from './core/hitl-routing.js';
|
|
29
|
+
import { STICKY_NOTE_TYPE, STICKY_NOTE_TYPE_VERSION } from './core/definitionless-node-types.js';
|
|
30
|
+
import { placeholderPosition } from './node-placement.js';
|
|
25
31
|
import { declaredExits, hasExits } from './core/step-ports.js';
|
|
26
32
|
import { stopsAtEnd } from './flow-step-tree.js';
|
|
27
33
|
import { assertNodeEnvelope } from './core/node-envelope.js';
|
|
@@ -45,7 +51,6 @@ export function connectorInputErrors(rc, inputs, resolutions = {}, lookups = {})
|
|
|
45
51
|
});
|
|
46
52
|
}
|
|
47
53
|
import { isLookupToken, resolveEventWhere, } from './core/lookups.js';
|
|
48
|
-
import { declaredScriptShape, inferScriptShape } from './core/script-return.js';
|
|
49
54
|
import { migrateToSchemaVersion } from './migrate.js';
|
|
50
55
|
import { FLOW_SCHEMA_VERSIONS } from './format-profile.js';
|
|
51
56
|
import { resolveSchemaVersion } from './schema-version.js';
|
|
@@ -53,12 +58,13 @@ import { planFlowGlobals, planSharedInputs, sharedInputRoots } from './flow-vari
|
|
|
53
58
|
import { prepareCommand } from './core/cli-spelling.js';
|
|
54
59
|
import { QUEUE_ITEM_RECORD_SCHEMA } from './core/queue-record.js';
|
|
55
60
|
import { AGENTIC_PROCESS_FORMS } from './core/agentic-process-forms.js';
|
|
61
|
+
import { resourceNodeSortOrder } from './core/process-resource-sort-order.js';
|
|
56
62
|
import { types } from './core/expr.js';
|
|
57
63
|
import { ownEntry } from './core/own-entry.js';
|
|
58
64
|
import { jsonClone } from './core/json-file.js';
|
|
59
65
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
60
66
|
// core-definitions.json is an object keyed by "<nodeType>@<version>".
|
|
61
|
-
const DEFS =
|
|
67
|
+
const DEFS = BUNDLED_DEFINITIONS;
|
|
62
68
|
// FIRST occurrence wins: with multiple bundled versions of one family (HITL
|
|
63
69
|
// 1.0 / 1.1 / 1.2), the unversioned lookup must stay the pinned default, not
|
|
64
70
|
// whichever entry happens to be last in the JSON. (`.loop()` always passes its
|
|
@@ -91,8 +97,6 @@ const NODE_TYPE = {
|
|
|
91
97
|
endCall: 'uipath.conversational.voice.end-call',
|
|
92
98
|
http: 'core.action.http',
|
|
93
99
|
httpV2: 'core.action.http.v2',
|
|
94
|
-
script: 'core.action.script',
|
|
95
|
-
transform: 'core.action.transform',
|
|
96
100
|
hitl: 'uipath.human-in-the-loop',
|
|
97
101
|
// The two other spellings of the SAME human task, both resolvable on this
|
|
98
102
|
// tenant. `hitl()` emits the base type unless `variant` names one of these;
|
|
@@ -103,23 +107,15 @@ const NODE_TYPE = {
|
|
|
103
107
|
hitlQuickForm: 'uipath.human-in-the-loop.quick-form',
|
|
104
108
|
hitlActionApp: 'uipath.human-in-the-loop.coded-action-app',
|
|
105
109
|
hitlDocValidation: 'uipath.human-in-the-loop.document-validation',
|
|
106
|
-
delay: 'core.logic.delay',
|
|
107
110
|
// The PLATFORM's placeholder — "a real node goes here later". Not the engine's
|
|
108
111
|
// fixture-returning `kind: 'mock'` test construct, which shares only the name
|
|
109
112
|
// (#125). The definition declares no `inputDefinition` and one output whose
|
|
110
113
|
// `source` is the literal `"null"`.
|
|
111
|
-
mock: 'core.logic.mock',
|
|
112
114
|
decision: 'core.logic.decision',
|
|
113
115
|
switch: 'core.logic.switch',
|
|
114
116
|
merge: 'core.logic.merge',
|
|
115
117
|
loop: 'core.logic.loop',
|
|
116
118
|
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
119
|
subflow: 'core.subflow',
|
|
124
120
|
terminate: 'core.logic.terminate',
|
|
125
121
|
queueCreate: 'core.action.queue.create',
|
|
@@ -214,6 +210,9 @@ function processResourceLocation(spec) {
|
|
|
214
210
|
* `icon: 'rpa'`, the `bpmn:ServiceTask` / `Orchestrator.StartJob` / `v2` model,
|
|
215
211
|
* `section: 'Published'` (42/45), `debug.runtime: 'bpmnEngine'`, and the
|
|
216
212
|
* `<bindings.name>` / `<bindings.folderPath>` context placeholders.
|
|
213
|
+
* `sortOrder` is not one of those constants any more: flow-core derives it from
|
|
214
|
+
* the category, and renumbered after 07-29, so it comes from
|
|
215
|
+
* `resourceNodeSortOrder` (#1045).
|
|
217
216
|
*
|
|
218
217
|
* Two fields are derived from the author's call rather than from the tenant, and
|
|
219
218
|
* that limit is real:
|
|
@@ -244,7 +243,7 @@ function rpaWorkflowDef(spec) {
|
|
|
244
243
|
runtimeConstraints: { exclude: ['api-function'] },
|
|
245
244
|
description: loc.description,
|
|
246
245
|
tags: loc.tags,
|
|
247
|
-
sortOrder:
|
|
246
|
+
sortOrder: resourceNodeSortOrder('rpa-workflow'),
|
|
248
247
|
supportsErrorHandling: true,
|
|
249
248
|
display: { label: spec.name, icon: 'rpa' },
|
|
250
249
|
handleConfiguration: [
|
|
@@ -324,7 +323,7 @@ const API_WORKFLOW_PREFIX = 'uipath.core.api-workflow.';
|
|
|
324
323
|
* AGREE on, not invented: 42 `uipath.core.api-workflow.*` instances across 42
|
|
325
324
|
* dated `skill-flow-api-workflow` artifacts (2026-05-18 → 07-29), cross-checked
|
|
326
325
|
* against `registry get` for today's workflow. Every one is unanimous —
|
|
327
|
-
* `category: 'api-workflow'`, `sortOrder: 510`, `supportsErrorHandling`,
|
|
326
|
+
* `category: 'api-workflow'`, `sortOrder: 510` (now 640, see `resourceNodeSortOrder`, #1045), `supportsErrorHandling`,
|
|
328
327
|
* `icon: 'api'`, the `bpmn:ServiceTask` / `Orchestrator.ExecuteApiWorkflowAsync` /
|
|
329
328
|
* `v2` model, `section: 'Published'` (37/42; the 5 without are the in-solution
|
|
330
329
|
* shape, a bare-GUID resourceKey — rpa's 3/45 split again), `resourceSubType:
|
|
@@ -362,7 +361,7 @@ function apiWorkflowDef(spec) {
|
|
|
362
361
|
runtimeConstraints: { exclude: ['api-function'] },
|
|
363
362
|
description: `(${spec.folderPath})`,
|
|
364
363
|
tags: [],
|
|
365
|
-
sortOrder:
|
|
364
|
+
sortOrder: resourceNodeSortOrder('api-workflow'),
|
|
366
365
|
supportsErrorHandling: true,
|
|
367
366
|
display: { label: spec.name, icon: 'api' },
|
|
368
367
|
handleConfiguration: [
|
|
@@ -443,7 +442,7 @@ function publishedFunctionDef(spec) {
|
|
|
443
442
|
category: 'function',
|
|
444
443
|
description: `(${spec.folderPath})`,
|
|
445
444
|
tags: [],
|
|
446
|
-
sortOrder:
|
|
445
|
+
sortOrder: resourceNodeSortOrder('function'),
|
|
447
446
|
supportsErrorHandling: true,
|
|
448
447
|
display: { label: spec.name, icon: 'function' },
|
|
449
448
|
handleConfiguration: [
|
|
@@ -521,7 +520,8 @@ function publishedFunctionDef(spec) {
|
|
|
521
520
|
*
|
|
522
521
|
* - `uip maestro flow registry get uipath.core.agentic-process.<key>` for ALL
|
|
523
522
|
* THREE agentic processes on the tenant (2026-07-29): `category:
|
|
524
|
-
* 'agentic-process'`, `sortOrder: 507
|
|
523
|
+
* 'agentic-process'`, `sortOrder: 507` (now 610 for bpmn, 600 flow, 620 case:
|
|
524
|
+
* `resourceNodeSortOrder`, #1045), `supportsErrorHandling`, `icon:
|
|
525
525
|
* 'agentic-process'`, `version: '1.0.0'`, the `bpmn:ServiceTask` /
|
|
526
526
|
* `Orchestrator.StartAgenticProcess` / `v2` model, `section: 'Published'`,
|
|
527
527
|
* `resourceSubType: 'ProcessOrchestration'`, `orchestratorType:
|
|
@@ -564,7 +564,7 @@ function agenticProcessDef(spec) {
|
|
|
564
564
|
runtimeConstraints: { exclude: ['api-function'] },
|
|
565
565
|
description: loc.description,
|
|
566
566
|
tags: loc.tags,
|
|
567
|
-
sortOrder:
|
|
567
|
+
sortOrder: resourceNodeSortOrder(form.category),
|
|
568
568
|
supportsErrorHandling: true,
|
|
569
569
|
display: { label: spec.name, icon: form.icon },
|
|
570
570
|
handleConfiguration: [
|
|
@@ -645,7 +645,8 @@ const INLINE_AGENT_TYPE = 'uipath.agent.autonomous';
|
|
|
645
645
|
* checkable rather than asserted:
|
|
646
646
|
*
|
|
647
647
|
* - `uip maestro flow registry get uipath.core.agent.<key>` for ALL NINE agents on
|
|
648
|
-
* the tenant (2026-07-29): `category: 'agent'`, `sortOrder: 505
|
|
648
|
+
* the tenant (2026-07-29): `category: 'agent'`, `sortOrder: 505` (510 since flow-core's
|
|
649
|
+
* renumber: the 54 runs from 09-01 below carry 510; `resourceNodeSortOrder`, #1045),
|
|
649
650
|
* `supportsErrorHandling: true`, `version: '1.0.0'`, the `bpmn:ServiceTask` /
|
|
650
651
|
* `Orchestrator.StartAgentJob` / `v2` model, `section: 'Published'`,
|
|
651
652
|
* `resourceSubType: 'Agent'`, `orchestratorType: 'agent'`,
|
|
@@ -699,7 +700,7 @@ function agentDef(spec) {
|
|
|
699
700
|
runtimeConstraints: { exclude: ['api-function'] },
|
|
700
701
|
description: loc.description,
|
|
701
702
|
tags: loc.tags,
|
|
702
|
-
sortOrder:
|
|
703
|
+
sortOrder: resourceNodeSortOrder('agent'),
|
|
703
704
|
supportsErrorHandling: true,
|
|
704
705
|
display: {
|
|
705
706
|
label: spec.name,
|
|
@@ -844,21 +845,6 @@ function contextIndexDef(spec) {
|
|
|
844
845
|
},
|
|
845
846
|
};
|
|
846
847
|
}
|
|
847
|
-
/**
|
|
848
|
-
* The node type for a context index: `…context.index.<slug>.<uuid>`.
|
|
849
|
-
*
|
|
850
|
-
* The slug is the index's display name lower-cased with every run of
|
|
851
|
-
* non-alphanumerics collapsed to a single `-`. Measured against the platform's own
|
|
852
|
-
* type for "Billing Dispute SOP Index" → `billing-dispute-sop-index`, and pinned
|
|
853
|
-
* by a test — because a derived identifier that disagrees with the registry's is a
|
|
854
|
-
* node type the tenant has never heard of.
|
|
855
|
-
*/
|
|
856
|
-
function contextIndexSlug(name) {
|
|
857
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
858
|
-
}
|
|
859
|
-
function contextIndexType(spec) {
|
|
860
|
-
return `uipath.agent.resource.context.index.${contextIndexSlug(spec.name)}.${spec.id}`;
|
|
861
|
-
}
|
|
862
848
|
/**
|
|
863
849
|
* Render an index's retrieval query into the platform's MODE-TAGGED object.
|
|
864
850
|
*
|
|
@@ -2966,9 +2952,14 @@ function connectionResourceBindings(spec, existing) {
|
|
|
2966
2952
|
* create, or the author's `returns` for a create-and-wait. Without a schema a
|
|
2967
2953
|
* downstream read is unresolvable.
|
|
2968
2954
|
*/
|
|
2969
|
-
function queueItemDef(spec, stepName, ids) {
|
|
2955
|
+
function queueItemDef(spec, stepName, ids, version) {
|
|
2970
2956
|
const nodeType = spec.wait ? NODE_TYPE.queueCreateAndWait : NODE_TYPE.queueCreate;
|
|
2971
|
-
|
|
2957
|
+
// The template at the AUTHOR'S version, when the bundle has it. Starting from
|
|
2958
|
+
// the default and handing it to makeNode with the pin refused every other
|
|
2959
|
+
// bundled version — the bug #988 fixed for inline agents (#1128). An
|
|
2960
|
+
// unbundled pin falls back to the default, and makeNode's exact-version
|
|
2961
|
+
// refusal names what is bundled.
|
|
2962
|
+
const base = (version !== undefined ? DEFS[`${nodeType}@${version}`] : undefined) ?? defByType.get(nodeType);
|
|
2972
2963
|
if (!base)
|
|
2973
2964
|
throw new Error(`no bundled definition for "${nodeType}"`);
|
|
2974
2965
|
const def = jsonClone(base);
|
|
@@ -3048,32 +3039,6 @@ function queueItemBindings(spec, existing) {
|
|
|
3048
3039
|
],
|
|
3049
3040
|
};
|
|
3050
3041
|
}
|
|
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
3042
|
/**
|
|
3078
3043
|
* Encode a scheduled trigger's single `every` interval into the three inputs the
|
|
3079
3044
|
* platform's node carries.
|
|
@@ -3115,93 +3080,6 @@ function scheduledVersion(inputs, explicit) {
|
|
|
3115
3080
|
}
|
|
3116
3081
|
return cron ? '1.2' : undefined;
|
|
3117
3082
|
}
|
|
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
3083
|
/**
|
|
3206
3084
|
* The form-trigger schema: one field per flow input, the same derivation the
|
|
3207
3085
|
* designer's form editor applies to a flow's arguments (`argument-fields.ts` /
|
|
@@ -3246,42 +3124,6 @@ function scheduleInputs(inputs, nodeId) {
|
|
|
3246
3124
|
...(isPreset ? {} : { timerValue: every }),
|
|
3247
3125
|
};
|
|
3248
3126
|
}
|
|
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
3127
|
/** JSON-Schema type per form-field type, as flow-v1's form serializer maps them (quick-form escalations and human tasks). */
|
|
3286
3128
|
const FORM_FIELD_JSON_TYPE = {
|
|
3287
3129
|
text: { type: 'string' },
|
|
@@ -3356,7 +3198,7 @@ function hitlInputs(inputs, nodeId, flowId, label, rename) {
|
|
|
3356
3198
|
const outcomes = (inputs?.outcomes ?? []).map((o, i) => {
|
|
3357
3199
|
const name = typeof o === 'string' ? o : o?.name;
|
|
3358
3200
|
const action = typeof o === 'string' ? 'Continue' : (o?.action ?? 'Continue');
|
|
3359
|
-
return { id:
|
|
3201
|
+
return { id: outcomeIdOf(o, i), name, type: 'string', isPrimary: i === 0, action };
|
|
3360
3202
|
});
|
|
3361
3203
|
const properties = {};
|
|
3362
3204
|
for (const f of inputs?.fields ?? []) {
|
|
@@ -3632,8 +3474,6 @@ const NO_RENAME = { nodeIds: new Map(), errorEnvelopes: new Map() };
|
|
|
3632
3474
|
const ERROR_ENVELOPE_VAR = {
|
|
3633
3475
|
[NODE_TYPE.httpV2]: 'output',
|
|
3634
3476
|
};
|
|
3635
|
-
/** The `typeVersion` Studio Web stamps on a sticky note. The type has no manifest to take it from. */
|
|
3636
|
-
const STICKY_NOTE_TYPE_VERSION = '1.0.0';
|
|
3637
3477
|
/**
|
|
3638
3478
|
* Append the flow's `.stickyNotes()` as real `stickyNote` nodes.
|
|
3639
3479
|
*
|
|
@@ -3684,7 +3524,7 @@ function emitStickyNotes(built, scope, claimNoteId) {
|
|
|
3684
3524
|
// `flow validate`, manifest or not (#869).
|
|
3685
3525
|
scope.nodes.push({
|
|
3686
3526
|
id: emittedId,
|
|
3687
|
-
type:
|
|
3527
|
+
type: STICKY_NOTE_TYPE,
|
|
3688
3528
|
typeVersion: STICKY_NOTE_TYPE_VERSION,
|
|
3689
3529
|
ui: {
|
|
3690
3530
|
position: { x: note.x, y: note.y },
|
|
@@ -3936,22 +3776,12 @@ function describeDeclared(bindings) {
|
|
|
3936
3776
|
export function bundledActionNodeType(spec) {
|
|
3937
3777
|
switch (spec?.kind) {
|
|
3938
3778
|
case 'http': return spec.inputs?.managed ? NODE_TYPE.httpV2 : NODE_TYPE.http;
|
|
3939
|
-
case 'transform': return transformNodeType(spec.inputs);
|
|
3940
3779
|
case 'hitl': return hitlNodeType(spec.inputs);
|
|
3941
3780
|
case 'queueItem': return spec.inputs?.wait ? NODE_TYPE.queueCreateAndWait : NODE_TYPE.queueCreate;
|
|
3942
3781
|
case 'inlineAgent': return INLINE_AGENT_TYPE;
|
|
3943
|
-
case 'delay':
|
|
3944
|
-
case 'script':
|
|
3945
|
-
case 'mock':
|
|
3946
3782
|
case 'subflow':
|
|
3947
3783
|
case 'summarize':
|
|
3948
3784
|
case 'batchTransform':
|
|
3949
|
-
case 'documentClassify':
|
|
3950
|
-
case 'dynamicExtract':
|
|
3951
|
-
case 'dataFabricRead':
|
|
3952
|
-
case 'dataFabricCreate':
|
|
3953
|
-
case 'dataFabricUpdate':
|
|
3954
|
-
case 'dataFabricDelete':
|
|
3955
3785
|
case 'sendMessage':
|
|
3956
3786
|
case 'waitForMessage':
|
|
3957
3787
|
case 'conversationContext':
|
|
@@ -4017,14 +3847,35 @@ export function resolveContributionDefinition(ref, requestedVersion, where) {
|
|
|
4017
3847
|
throw new Error(`${where}: unknown DefinitionReference source "${ref.source}".`);
|
|
4018
3848
|
}
|
|
4019
3849
|
/**
|
|
4020
|
-
*
|
|
4021
|
-
*
|
|
4022
|
-
*
|
|
3850
|
+
* Whether `def` declares `port` as a forward source handle: an exact id, or a
|
|
3851
|
+
* templated one (`outcome-{item.id}` declares every `outcome-<slug>`). `error` is
|
|
3852
|
+
* not a forward port. A definition that declares no handles cannot say, so it
|
|
3853
|
+
* accepts any port.
|
|
3854
|
+
*/
|
|
3855
|
+
function declaresForwardPort(def, port) {
|
|
3856
|
+
const handles = (def?.handleConfiguration ?? []).flatMap((g) => g?.handles ?? []);
|
|
3857
|
+
if (handles.length === 0)
|
|
3858
|
+
return true;
|
|
3859
|
+
return handles.some((h) => {
|
|
3860
|
+
if (h?.type !== 'source' || typeof h.id !== 'string' || h.id === 'error')
|
|
3861
|
+
return false;
|
|
3862
|
+
if (!h.id.includes('{'))
|
|
3863
|
+
return h.id === port;
|
|
3864
|
+
const [head, tail] = [h.id.slice(0, h.id.indexOf('{')), h.id.slice(h.id.lastIndexOf('}') + 1)];
|
|
3865
|
+
return port.length > head.length + tail.length && port.startsWith(head) && port.endsWith(tail);
|
|
3866
|
+
});
|
|
3867
|
+
}
|
|
3868
|
+
/**
|
|
3869
|
+
* Forward port: the first concrete source handle (`isDefaultForType` wins), else `output`;
|
|
3870
|
+
* `undefined` when every forward source handle is templated (`case-{item.id}`).
|
|
4023
3871
|
*/
|
|
4024
3872
|
function contributionPort(def) {
|
|
4025
3873
|
const handles = (def?.handleConfiguration ?? []).flatMap((g) => g?.handles ?? []);
|
|
4026
3874
|
const sources = handles.filter((h) => h?.type === 'source' && h?.id !== 'error');
|
|
4027
|
-
|
|
3875
|
+
const concrete = sources.filter((h) => typeof h?.id === 'string' && !h.id.includes('{'));
|
|
3876
|
+
if (concrete.length === 0 && sources.length > 0)
|
|
3877
|
+
return undefined;
|
|
3878
|
+
return concrete.find((h) => h?.isDefaultForType)?.id ?? concrete[0]?.id ?? 'output';
|
|
4028
3879
|
}
|
|
4029
3880
|
/**
|
|
4030
3881
|
* Render a switch case value as a JS literal for the case expression.
|
|
@@ -4726,12 +4577,21 @@ export function serialize(built, opts = {}) {
|
|
|
4726
4577
|
* recorded: `check` answers for those from its per-tenant kind list.
|
|
4727
4578
|
*/
|
|
4728
4579
|
const bundledNodeDefs = new Map();
|
|
4580
|
+
/**
|
|
4581
|
+
* Node id → whether a registered PER-TENANT node type has an error port, as it
|
|
4582
|
+
* declares (its definition is built per call, so there is nothing else to ask).
|
|
4583
|
+
* A registered bundled type is in `bundledNodeDefs` like any typed family.
|
|
4584
|
+
*/
|
|
4585
|
+
const declaredErrorPorts = new Map();
|
|
4729
4586
|
/**
|
|
4730
4587
|
* Refuse an `error` edge from a node whose definition has no error port: a
|
|
4731
4588
|
* contribution-lowered node (#943) or a typed node with a bundled definition
|
|
4732
4589
|
* (#1011). Same predicate as `check`, so `compile --no-check` agrees with it.
|
|
4733
4590
|
*/
|
|
4734
4591
|
function refuseErrorEdgeWithoutPort(nodeId, stepName) {
|
|
4592
|
+
const declared = declaredErrorPorts.get(nodeId);
|
|
4593
|
+
if (declared?.port === false)
|
|
4594
|
+
throw new Error(declaredErrorPortRefusal(stepName, declared.typeName, declared.version));
|
|
4735
4595
|
const contributed = contributionDefs.get(nodeId);
|
|
4736
4596
|
const def = contributed ?? bundledNodeDefs.get(nodeId);
|
|
4737
4597
|
if (def !== undefined && !definitionHasErrorPort(def)) {
|
|
@@ -4865,6 +4725,8 @@ export function serialize(built, opts = {}) {
|
|
|
4865
4725
|
* `.stepToRef` rejoins now do come through here, #938).
|
|
4866
4726
|
*/
|
|
4867
4727
|
const hitlNodes = new Map();
|
|
4728
|
+
/** Step name → node id of a step whose every forward exit is templated (see `contributionPort`). */
|
|
4729
|
+
const routerNodes = new Map();
|
|
4868
4730
|
/**
|
|
4869
4731
|
* Human tasks with an outcome whose path stops at an End while another
|
|
4870
4732
|
* outcome continues — read by `.parallel`, whose join would wait forever for
|
|
@@ -4896,8 +4758,16 @@ export function serialize(built, opts = {}) {
|
|
|
4896
4758
|
if (o.requestedVersion !== undefined && def?.version !== o.requestedVersion) {
|
|
4897
4759
|
const exact = o.def ? undefined : DEFS[`${type}@${o.requestedVersion}`];
|
|
4898
4760
|
if (!exact) {
|
|
4761
|
+
// Name every version the SDK could have resolved: every bundled
|
|
4762
|
+
// version of the type, else (a type built per resource, a connector)
|
|
4763
|
+
// the definition in hand. Naming only the default sent the reader
|
|
4764
|
+
// looking for a version that was shipped all along (#1128:
|
|
4765
|
+
// quick-form bundles 1.0 AND 1.1, and the message said 1.0).
|
|
4766
|
+
const bundled = bundledVersions(type);
|
|
4767
|
+
const resolvable = bundled.length > 0 ? bundled
|
|
4768
|
+
: [def?.version].filter((v) => typeof v === 'string');
|
|
4899
4769
|
throw new Error(`"${label ?? id}" requests ${type}@${o.requestedVersion}, but this SDK resolves only `
|
|
4900
|
-
+ `${type}@${
|
|
4770
|
+
+ `${type}@${resolvable.join(', ') || '<none>'}. Versions are exact — omit { version } to use `
|
|
4901
4771
|
+ `the pinned default, or request a version this SDK ships.`);
|
|
4902
4772
|
}
|
|
4903
4773
|
def = exact;
|
|
@@ -4910,18 +4780,15 @@ export function serialize(built, opts = {}) {
|
|
|
4910
4780
|
id,
|
|
4911
4781
|
type,
|
|
4912
4782
|
typeVersion: def?.version ?? '1.0.0',
|
|
4913
|
-
ui: { position:
|
|
4783
|
+
ui: { position: placeholderPosition(col, Boolean(o.parentId)), size: { width: 96, height: 96 }, collapsed: false },
|
|
4914
4784
|
display: { label: label ?? def?.display?.label ?? id, ...(def?.display?.icon ? { icon: def.display.icon } : {}) },
|
|
4915
4785
|
};
|
|
4916
4786
|
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.'))
|
|
4787
|
+
// A registered node type may carry no node-level `model`, whichever path
|
|
4788
|
+
// emits it (its own serialize, or a rawNode through emitContribution) — the
|
|
4789
|
+
// Data Fabric verbs, whose reasons are in their family (`nodeModel`).
|
|
4790
|
+
const omitModel = registeredRawType(type, String(def?.version ?? ''))?.impl.nodeModel === 'omit';
|
|
4791
|
+
if (def?.model && !omitModel)
|
|
4925
4792
|
node.model = subNodeId(jsonClone(def.model), id);
|
|
4926
4793
|
if (def?.outputDefinition)
|
|
4927
4794
|
node.outputs = subNodeId(jsonClone(def.outputDefinition), id);
|
|
@@ -5124,7 +4991,7 @@ export function serialize(built, opts = {}) {
|
|
|
5124
4991
|
function successPort(type) {
|
|
5125
4992
|
if (type === NODE_TYPE.http || type === NODE_TYPE.httpV2)
|
|
5126
4993
|
return 'default';
|
|
5127
|
-
if (type === NODE_TYPE.
|
|
4994
|
+
if (type === NODE_TYPE.loop)
|
|
5128
4995
|
return 'success';
|
|
5129
4996
|
// Both queue definitions declare exactly one source handle, `success` — and
|
|
5130
4997
|
// the shipped `sample-flows/queue-operations.flow` wires its edges to it.
|
|
@@ -5156,9 +5023,6 @@ export function serialize(built, opts = {}) {
|
|
|
5156
5023
|
// as it is in all 151 deployed IxP instances.
|
|
5157
5024
|
if (type.startsWith('uipath.ixp.'))
|
|
5158
5025
|
return 'success';
|
|
5159
|
-
// Classify declares the same single `success` source handle as the extractors.
|
|
5160
|
-
if (type === NODE_TYPE.documentClassify)
|
|
5161
|
-
return 'success';
|
|
5162
5026
|
// connectors, the trigger, the merge join and the mock placeholder all expose
|
|
5163
5027
|
// their forward edge on `output` (merge's definition declares exactly one
|
|
5164
5028
|
// source handle, `output`; so does `core.logic.mock`, and all 74 archived
|
|
@@ -5185,27 +5049,119 @@ export function serialize(built, opts = {}) {
|
|
|
5185
5049
|
const connectorRefusal = connectorRawNodeRefusal(def.nodeType);
|
|
5186
5050
|
if (connectorRefusal)
|
|
5187
5051
|
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 =
|
|
5052
|
+
// An input a registered node type never reads (a Data Fabric `_sortOptions`)
|
|
5053
|
+
// is refused for every contribution, whichever class or SDK copy made it:
|
|
5054
|
+
// `validate` cannot see it, and the node would ignore it at run time.
|
|
5055
|
+
const unknownInput = rawNodeInputProblem(def.nodeType, def.version, contribution.inputs);
|
|
5192
5056
|
if (unknownInput?.severity === 'error')
|
|
5193
5057
|
throw new Error(`${where}: ${unknownInput.message}`);
|
|
5194
5058
|
const id = stepUid(scope, stepName);
|
|
5195
|
-
|
|
5059
|
+
contributionDefs.set(id, def);
|
|
5060
|
+
return placeStepNode({ where, stepName, id, typeName: def.nodeType, requestedVersion, def, contribution, tails, scope, parentId });
|
|
5061
|
+
}
|
|
5062
|
+
/**
|
|
5063
|
+
* Place one step node from a contribution — the one path a custom FlowAction
|
|
5064
|
+
* (emitContribution) and a registered node type (emitRegistered) share
|
|
5065
|
+
* (docs/NODE_TYPE_ARCHITECTURE_DESIGN.md, Phase 3). The serializer owns the
|
|
5066
|
+
* rest: the node envelope from the definition, rendering, bindings, edges, and
|
|
5067
|
+
* the forward port. `def` is undefined only for a registered bundled type,
|
|
5068
|
+
* whose definition is `<typeName>@<version>` from the bundle.
|
|
5069
|
+
*/
|
|
5070
|
+
function placeStepNode(o) {
|
|
5071
|
+
const { id, contribution, scope } = o;
|
|
5072
|
+
const node = makeNode(o.typeName, id, o.stepName, { parentId: o.parentId, def: o.def, requestedVersion: o.requestedVersion });
|
|
5073
|
+
if (contribution.model === 'omit')
|
|
5074
|
+
delete node.model;
|
|
5196
5075
|
node.inputs = renderDeep(contribution.inputs ?? {}, scope.rename);
|
|
5197
5076
|
if (contribution.outputs) {
|
|
5198
5077
|
node.outputs = { ...(node.outputs ?? {}), ...subNodeId(jsonClone(contribution.outputs), id) };
|
|
5199
5078
|
}
|
|
5200
|
-
|
|
5201
|
-
|
|
5079
|
+
// A bundled registered type returns no definition; its bindings still close
|
|
5080
|
+
// against the bundled one (#942), counting what it bound through the context
|
|
5081
|
+
// as supplied (first, in the order it bound them, as when `bind` hoisted directly).
|
|
5082
|
+
const def = o.def ?? bundledDefinition(o.typeName, o.requestedVersion);
|
|
5083
|
+
hoistContributionBindings(def, [...(o.bound ?? []), ...(contribution.bindings ?? [])], o.where);
|
|
5202
5084
|
scope.nodes.push(node);
|
|
5203
|
-
for (const t of tails)
|
|
5085
|
+
for (const t of o.tails)
|
|
5204
5086
|
addEdge(scope, t, id);
|
|
5205
|
-
|
|
5087
|
+
const port = contribution.port ?? contributionPort(def);
|
|
5088
|
+
if (port === undefined) {
|
|
5089
|
+
// Only templated exits: no main path leaves this node. Remember it so a
|
|
5090
|
+
// following `.stepToList` / side-exit `.stepToRef` can still attach.
|
|
5091
|
+
routerNodes.set(o.stepName, id);
|
|
5092
|
+
return [];
|
|
5093
|
+
}
|
|
5094
|
+
return [{ node: id, port }];
|
|
5095
|
+
}
|
|
5096
|
+
/**
|
|
5097
|
+
* Lower a step whose kind is a registered node type (flow-node-registry.ts):
|
|
5098
|
+
* the node type serializes its spec through a context, and this owns the rest —
|
|
5099
|
+
* id, label, `typeVersion`, definition, rendering, bindings, edges — exactly as
|
|
5100
|
+
* for a custom contribution. docs/NODE_TYPE_ARCHITECTURE_DESIGN.md §4.
|
|
5101
|
+
*/
|
|
5102
|
+
function emitRegistered(entry, step, tails, scope, parentId) {
|
|
5103
|
+
const where = `step "${step.name}"`;
|
|
5104
|
+
const spec = step.spec;
|
|
5105
|
+
const impl = resolveVersion(entry.type, spec, step.options?.version, where);
|
|
5106
|
+
const id = stepUid(scope, step.name);
|
|
5107
|
+
// As for every typed family: record the failure-envelope boundary before any
|
|
5108
|
+
// expression renders, so a handler reading this step resolves correctly.
|
|
5109
|
+
const envelope = ownEntry(ERROR_ENVELOPE_VAR, impl.typeName);
|
|
5110
|
+
if (envelope)
|
|
5111
|
+
scope.rename.errorEnvelopes.set(step.name, envelope);
|
|
5112
|
+
const ctx = {
|
|
5113
|
+
step: step.name,
|
|
5114
|
+
version: impl.version,
|
|
5115
|
+
render: (value) => renderValue(value, scope.rename),
|
|
5116
|
+
renameRefs: (js) => renameRefs(js, scope.rename),
|
|
5117
|
+
stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
|
|
5118
|
+
mint: (seed) => stableId(`${built.id}:${id}:${seed}`),
|
|
5119
|
+
bind: (b) => { bound.push(b); },
|
|
5120
|
+
definition: (typeName, version) => bundledDefinition(typeName, version),
|
|
5121
|
+
};
|
|
5122
|
+
const bound = [];
|
|
5123
|
+
const contribution = impl.serialize(spec, ctx);
|
|
5124
|
+
// The definition contract, both halves: a per-tenant type supplies its
|
|
5125
|
+
// definition, and a bundled type does not (its definition is the bundle's, and
|
|
5126
|
+
// makeNode records it, so the error-edge refusal and check ask the same one).
|
|
5127
|
+
let def;
|
|
5128
|
+
if (impl.definition === 'bundled' && contribution.definition !== undefined) {
|
|
5129
|
+
throw new Error(`internal SDK error: ${where}: bundled node type ${impl.typeName}@${impl.version} returned a definition; `
|
|
5130
|
+
+ 'the bundle owns it — omit `definition`, or declare the type per-tenant.');
|
|
5131
|
+
}
|
|
5132
|
+
if (contribution.definition !== undefined) {
|
|
5133
|
+
def = resolveContributionDefinition(contribution.definition, impl.version, where);
|
|
5134
|
+
}
|
|
5135
|
+
else if (impl.definition === 'per-tenant') {
|
|
5136
|
+
throw new Error(`internal SDK error: ${where}: per-tenant node type ${impl.typeName}@${impl.version} returned no definition.`);
|
|
5137
|
+
}
|
|
5138
|
+
if (impl.definition === 'per-tenant') {
|
|
5139
|
+
const port = typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
|
|
5140
|
+
// The declaration answers for check, which has no definition; here the
|
|
5141
|
+
// definition is in hand, and the two must agree (#1011).
|
|
5142
|
+
if (port && def !== undefined && !definitionHasErrorPort(def)) {
|
|
5143
|
+
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${impl.version} declares an error port, `
|
|
5144
|
+
+ 'but the definition it supplied has no error handle and no supportsErrorHandling.');
|
|
5145
|
+
}
|
|
5146
|
+
declaredErrorPorts.set(id, { port, typeName: impl.typeName, version: impl.version });
|
|
5147
|
+
}
|
|
5148
|
+
if (contribution.port !== undefined && !declaresForwardPort(def ?? bundledDefinition(impl.typeName, impl.version), contribution.port)) {
|
|
5149
|
+
throw new Error(`internal SDK error: ${where}: node type ${impl.typeName}@${impl.version} leaves by port "${contribution.port}", `
|
|
5150
|
+
+ 'which its definition declares no source handle for.');
|
|
5151
|
+
}
|
|
5152
|
+
return placeStepNode({
|
|
5153
|
+
where, stepName: step.name, id, typeName: impl.typeName, requestedVersion: impl.version, def, contribution, bound, tails, scope, parentId,
|
|
5154
|
+
});
|
|
5206
5155
|
}
|
|
5207
5156
|
function emitAction(step, tails, scope, parentId) {
|
|
5208
5157
|
const spec = step.spec;
|
|
5158
|
+
// A registered node type serializes itself; every other kind falls through to
|
|
5159
|
+
// the branches below until its family is ported (FB014's pins are the list).
|
|
5160
|
+
const registered = typeof spec.kind === 'string'
|
|
5161
|
+
? FLOW_NODE_TYPES.byKind(spec.kind, spec)
|
|
5162
|
+
: undefined;
|
|
5163
|
+
if (registered)
|
|
5164
|
+
return emitRegistered(registered, step, tails, scope, parentId);
|
|
5209
5165
|
if (spec.kind === 'subflow') {
|
|
5210
5166
|
const id = stepUid(scope, step.name);
|
|
5211
5167
|
// `subflows` is ONE flat map for the whole file, keyed by the calling
|
|
@@ -5473,6 +5429,13 @@ export function serialize(built, opts = {}) {
|
|
|
5473
5429
|
const contexts = s.context === undefined ? []
|
|
5474
5430
|
: Array.isArray(s.context) ? s.context : [s.context];
|
|
5475
5431
|
for (const cx of contexts) {
|
|
5432
|
+
// `check`'s AGENT_CONTEXT_NO_ID, again for `--no-check`: a blank id builds
|
|
5433
|
+
// the type `…context.index.<slug>.`, which no tenant serves, and nothing
|
|
5434
|
+
// after this point would say so.
|
|
5435
|
+
if (typeof cx?.id !== 'string' || !GUID_RE.test(cx.id)) {
|
|
5436
|
+
throw Object.assign(new Error(`inline agent "${step.name}": context index "${cx?.name ?? ''}" needs its index id (a GUID) as \`id\`; `
|
|
5437
|
+
+ `got ${JSON.stringify(cx?.id)}. \`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`), { code: 'AGENT_CONTEXT_NO_ID' });
|
|
5438
|
+
}
|
|
5476
5439
|
const cdef = contextIndexDef({ name: cx.name, id: cx.id });
|
|
5477
5440
|
// `<step>Context`, deduped by `uid` for a second index on the same agent.
|
|
5478
5441
|
// Not slugged from the index NAME: an id is the author's handle on the node
|
|
@@ -5936,7 +5899,7 @@ export function serialize(built, opts = {}) {
|
|
|
5936
5899
|
const { ids, entries } = queueItemBindings(s, resourceBindings);
|
|
5937
5900
|
for (const b of entries)
|
|
5938
5901
|
resourceBindings.set(b.id, b);
|
|
5939
|
-
const def = queueItemDef(s, step.name, ids);
|
|
5902
|
+
const def = queueItemDef(s, step.name, ids, step.options?.version);
|
|
5940
5903
|
const id = stepUid(scope, step.name);
|
|
5941
5904
|
// The result is read as `$vars.<step>.output`, like every other family: the
|
|
5942
5905
|
// node's own output block maps the runtime `response` onto the variable
|
|
@@ -6310,154 +6273,6 @@ export function serialize(built, opts = {}) {
|
|
|
6310
6273
|
}
|
|
6311
6274
|
return [{ node: id, port: successPort(hitlType) }];
|
|
6312
6275
|
}
|
|
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
6276
|
// A custom FlowAction subclass carries no `kind`; it compiles through its
|
|
6462
6277
|
// own contribution (design §4). Anything else without a recognized kind is
|
|
6463
6278
|
// an authoring error — it must NOT fall through into the script branch
|
|
@@ -6469,7 +6284,7 @@ export function serialize(built, opts = {}) {
|
|
|
6469
6284
|
}
|
|
6470
6285
|
throw new Error(`step "${step.name}": not an action spec — pass a factory result, subflow(), or a FlowAction subclass instance.`);
|
|
6471
6286
|
}
|
|
6472
|
-
if (
|
|
6287
|
+
if (spec.kind !== 'http') {
|
|
6473
6288
|
throw new Error(`step "${step.name}": unknown action kind "${spec.kind}".`);
|
|
6474
6289
|
}
|
|
6475
6290
|
const type = bundledActionNodeType(spec);
|
|
@@ -6482,6 +6297,7 @@ export function serialize(built, opts = {}) {
|
|
|
6482
6297
|
const envelope = ownEntry(ERROR_ENVELOPE_VAR, type);
|
|
6483
6298
|
if (envelope)
|
|
6484
6299
|
scope.rename.errorEnvelopes.set(step.name, envelope);
|
|
6300
|
+
// The guard above leaves http; registered kinds returned at the top.
|
|
6485
6301
|
node.inputs = actionInputs(spec, scope.rename, opts, step.name);
|
|
6486
6302
|
if (spec.kind === 'http') {
|
|
6487
6303
|
node.outputs = httpOutputs(node.outputs, spec.inputs.returns, spec.inputs.managed);
|
|
@@ -6501,12 +6317,6 @@ export function serialize(built, opts = {}) {
|
|
|
6501
6317
|
resourceBindings.set(entry.id, entry);
|
|
6502
6318
|
}
|
|
6503
6319
|
}
|
|
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
6320
|
scope.nodes.push(node);
|
|
6511
6321
|
for (const t of tails)
|
|
6512
6322
|
addEdge(scope, t, id);
|
|
@@ -6572,8 +6382,13 @@ export function serialize(built, opts = {}) {
|
|
|
6572
6382
|
* from a non-default port is a side exit. Only the seed differs from an ordinary
|
|
6573
6383
|
* list — the same node, a different port.
|
|
6574
6384
|
*/
|
|
6575
|
-
function emitPortList(step, source, tails, scope, parentId,
|
|
6385
|
+
function emitPortList(step, source, tails, scope, parentId, loopExits) {
|
|
6576
6386
|
const handler = step.body;
|
|
6387
|
+
// A node whose exits are all templated hands on no tail; its port lists
|
|
6388
|
+
// leave from the node itself.
|
|
6389
|
+
const router = tails.length === 0 ? routerNodes.get(source) : undefined;
|
|
6390
|
+
if (router !== undefined)
|
|
6391
|
+
tails = [{ node: router, port: step.port }];
|
|
6577
6392
|
// `emitAction` returns one tail for every action kind — the node's own
|
|
6578
6393
|
// success port — except a fan-out human task, which returns one per outcome
|
|
6579
6394
|
// from the SAME node. Tails from two nodes mean a new action shape reached
|
|
@@ -6592,7 +6407,7 @@ export function serialize(built, opts = {}) {
|
|
|
6592
6407
|
refuseErrorEdgeWithoutPort(nodeId, source);
|
|
6593
6408
|
node.inputs = { ...(node.inputs ?? {}), errorHandlingEnabled: true };
|
|
6594
6409
|
}
|
|
6595
|
-
const handlerTails = emit(handler, [{ node: nodeId, port: step.port }], scope, parentId,
|
|
6410
|
+
const handlerTails = emit(handler, [{ node: nodeId, port: step.port }], scope, parentId, loopExits);
|
|
6596
6411
|
// A body that runs out of steps ends the run on an End of its own (no
|
|
6597
6412
|
// merge-back is inferred). That End maps the outputs like any other, so a
|
|
6598
6413
|
// body that set them with `{ updates }` still answers (#742).
|
|
@@ -6600,13 +6415,13 @@ export function serialize(built, opts = {}) {
|
|
|
6600
6415
|
const endId = uid(scope, `${source}${step.port === 'error' ? 'Error' : step.port.charAt(0).toUpperCase() + step.port.slice(1)}End`);
|
|
6601
6416
|
emitEnd(scope, endId, handlerTails, { parentId });
|
|
6602
6417
|
}
|
|
6603
|
-
return tails;
|
|
6418
|
+
return router !== undefined ? [] : tails;
|
|
6604
6419
|
}
|
|
6605
6420
|
function emit(steps, incoming, scope, parentId,
|
|
6606
|
-
// The enclosing container's break
|
|
6607
|
-
// Branch/switch/parallel arms and error handlers INHERIT
|
|
6608
|
-
// container replaces
|
|
6609
|
-
|
|
6421
|
+
// The enclosing container's break and continue handles, when inside a
|
|
6422
|
+
// loop/do-while body. Branch/switch/parallel arms and error handlers INHERIT
|
|
6423
|
+
// them; a nested container replaces them for its own body.
|
|
6424
|
+
loopExits) {
|
|
6610
6425
|
let tails = incoming;
|
|
6611
6426
|
// The action a port-scoped edge leaves. `.stepToList('error', …)` and
|
|
6612
6427
|
// `.stepToRef('error', …)` are list MEMBERS that follow the step they attach
|
|
@@ -6650,6 +6465,14 @@ export function serialize(built, opts = {}) {
|
|
|
6650
6465
|
node.display = { ...(node.display ?? {}), label };
|
|
6651
6466
|
};
|
|
6652
6467
|
for (const [stepIndex, step] of steps.entries()) {
|
|
6468
|
+
// After a node whose exits are all templated, only its port lists may
|
|
6469
|
+
// follow: nothing continues from it, so a step here would hang off no edge.
|
|
6470
|
+
const router = lastAction !== undefined && tails.length === 0 ? routerNodes.get(lastAction) : undefined;
|
|
6471
|
+
if (router !== undefined && step.kind !== 'stepToList' && step.kind !== 'stepToRef') {
|
|
6472
|
+
throw new Error(`step "${lastAction}": its definition declares only templated exits (such as case-{item.id}), so no `
|
|
6473
|
+
+ `path continues from it and ${'name' in step ? `"${step.name}"` : `.${step.kind}()`} after it would `
|
|
6474
|
+
+ `have no incoming edge. Route each exit with .stepToList('<port>', …), e.g. .stepToList('case-<id>', …).`);
|
|
6475
|
+
}
|
|
6653
6476
|
if (step.kind === 'action') {
|
|
6654
6477
|
tails = emitAction(step, tails, scope, parentId);
|
|
6655
6478
|
applyUpdates(step);
|
|
@@ -6678,7 +6501,7 @@ export function serialize(built, opts = {}) {
|
|
|
6678
6501
|
}
|
|
6679
6502
|
}
|
|
6680
6503
|
else if (step.kind === 'stepToList') {
|
|
6681
|
-
tails = emitPortList(step, lastAction ?? '?', tails, scope, parentId,
|
|
6504
|
+
tails = emitPortList(step, lastAction ?? '?', tails, scope, parentId, loopExits);
|
|
6682
6505
|
}
|
|
6683
6506
|
else if (step.kind === 'stepSwitch') {
|
|
6684
6507
|
// The action, then EVERY exit it declares as an arm — no tacit tail.
|
|
@@ -6723,7 +6546,7 @@ export function serialize(built, opts = {}) {
|
|
|
6723
6546
|
// from all of them. (`.stepToList` instead gives an arm that runs out
|
|
6724
6547
|
// of steps an End of its own; `emitEnd` maps the outputs on it,
|
|
6725
6548
|
// flow-builder-sdk#742.)
|
|
6726
|
-
const caseTails = emit(c.body, [{ node: nodeId, port: exit.port }], scope, parentId,
|
|
6549
|
+
const caseTails = emit(c.body, [{ node: nodeId, port: exit.port }], scope, parentId, loopExits);
|
|
6727
6550
|
if (caseTails.length === 0 && stopsAtEnd(c.body))
|
|
6728
6551
|
stopped.push(String(c.value));
|
|
6729
6552
|
armTails.push(...(hitlNodes.has(nodeId)
|
|
@@ -6768,8 +6591,8 @@ export function serialize(built, opts = {}) {
|
|
|
6768
6591
|
scope.nodes.push(node);
|
|
6769
6592
|
for (const t of tails)
|
|
6770
6593
|
addEdge(scope, t, id);
|
|
6771
|
-
const thenTails = emit(step.then, [{ node: id, port: 'true' }], scope, parentId,
|
|
6772
|
-
const elseTails = emit(step.otherwise, [{ node: id, port: 'false' }], scope, parentId,
|
|
6594
|
+
const thenTails = emit(step.then, [{ node: id, port: 'true' }], scope, parentId, loopExits);
|
|
6595
|
+
const elseTails = emit(step.otherwise, [{ node: id, port: 'false' }], scope, parentId, loopExits);
|
|
6773
6596
|
tails = [...thenTails, ...elseTails];
|
|
6774
6597
|
}
|
|
6775
6598
|
else if (step.kind === 'switch') {
|
|
@@ -6784,8 +6607,11 @@ export function serialize(built, opts = {}) {
|
|
|
6784
6607
|
// the runtime — so no `=js:` prefix, same as a decision's `expression`.
|
|
6785
6608
|
const id = stepUid(scope, step.name);
|
|
6786
6609
|
const node = makeNode(NODE_TYPE.switch, id, step.options?.label ?? step.label ?? step.name, { parentId, requestedVersion: step.options?.version });
|
|
6610
|
+
// `id` is the author's when given (a decompiled switch keeps the
|
|
6611
|
+
// canvas's `default-1`, …: merge keeps the original node, so the edges
|
|
6612
|
+
// must leave from ITS handles), else positional (#1130).
|
|
6787
6613
|
const cases = step.cases.map((c, i) => ({
|
|
6788
|
-
id: `case_${i + 1}`,
|
|
6614
|
+
id: c.id ?? `case_${i + 1}`,
|
|
6789
6615
|
label: c.label ?? `Case ${caseLiteral(c.value)}`,
|
|
6790
6616
|
expression: `${renameRefs(step.on.js, scope.rename)} === ${caseLiteral(c.value)}`,
|
|
6791
6617
|
}));
|
|
@@ -6798,9 +6624,9 @@ export function serialize(built, opts = {}) {
|
|
|
6798
6624
|
// merge node; the convergence point is found by graph search).
|
|
6799
6625
|
const armTails = [];
|
|
6800
6626
|
for (const [i, c] of step.cases.entries())
|
|
6801
|
-
armTails.push(...emit(c.body, [{ node: id, port: `case-${cases[i].id}` }], scope, parentId,
|
|
6627
|
+
armTails.push(...emit(c.body, [{ node: id, port: `case-${cases[i].id}` }], scope, parentId, loopExits));
|
|
6802
6628
|
if (step.default !== undefined)
|
|
6803
|
-
armTails.push(...emit(step.default, [{ node: id, port: 'default' }], scope, parentId));
|
|
6629
|
+
armTails.push(...emit(step.default, [{ node: id, port: 'default' }], scope, parentId, loopExits));
|
|
6804
6630
|
tails = armTails;
|
|
6805
6631
|
}
|
|
6806
6632
|
else if (step.kind === 'parallel') {
|
|
@@ -6833,7 +6659,7 @@ export function serialize(built, opts = {}) {
|
|
|
6833
6659
|
const armTails = [];
|
|
6834
6660
|
const endsBefore = hitlEndExits.length;
|
|
6835
6661
|
for (const arm of step.arms) {
|
|
6836
|
-
const t = emit(arm, tails, scope, parentId,
|
|
6662
|
+
const t = emit(arm, tails, scope, parentId, loopExits);
|
|
6837
6663
|
// An arm hands the join ONE edge, or several that are alternatives —
|
|
6838
6664
|
// `.branch` / `.switch` / `.stepSwitch` arms, a fan-out task's
|
|
6839
6665
|
// outcomes — of which only one fires. Refused when any of them is a
|
|
@@ -6918,7 +6744,7 @@ export function serialize(built, opts = {}) {
|
|
|
6918
6744
|
addEdge(scope, t, id);
|
|
6919
6745
|
const bodyPort = v24 ? 'start' : 'output';
|
|
6920
6746
|
const backPort = v24 ? 'continue' : 'loopBack';
|
|
6921
|
-
const bodyTails = emit(step.body, [{ node: id, port: bodyPort }], scope, id, v24 ? { node: id,
|
|
6747
|
+
const bodyTails = emit(step.body, [{ node: id, port: bodyPort }], scope, id, v24 ? { node: id, break: 'break', continue: backPort } : { node: id, continue: backPort });
|
|
6922
6748
|
for (const bt of bodyTails)
|
|
6923
6749
|
addEdge(scope, bt, id, backPort);
|
|
6924
6750
|
applyUpdates(step);
|
|
@@ -6937,18 +6763,29 @@ export function serialize(built, opts = {}) {
|
|
|
6937
6763
|
scope.nodes.push(node);
|
|
6938
6764
|
for (const t of tails)
|
|
6939
6765
|
addEdge(scope, t, id);
|
|
6940
|
-
const bodyTails = emit(step.body, [{ node: id, port: 'start' }], scope, id, { node: id,
|
|
6766
|
+
const bodyTails = emit(step.body, [{ node: id, port: 'start' }], scope, id, { node: id, break: 'break', continue: 'continue' });
|
|
6941
6767
|
for (const bt of bodyTails)
|
|
6942
6768
|
addEdge(scope, bt, id, 'continue');
|
|
6943
6769
|
applyUpdates(step);
|
|
6944
6770
|
tails = [{ node: id, port: 'success' }];
|
|
6945
6771
|
}
|
|
6946
6772
|
else if (step.kind === 'break') {
|
|
6947
|
-
if (!
|
|
6773
|
+
if (!loopExits?.break) {
|
|
6948
6774
|
throw new Error('.break() has no enclosing .loop()/.doWhile() body to exit — it is only valid inside a container.');
|
|
6949
6775
|
}
|
|
6950
6776
|
for (const t of tails)
|
|
6951
|
-
addEdge(scope, t,
|
|
6777
|
+
addEdge(scope, t, loopExits.node, loopExits.break);
|
|
6778
|
+
tails = [];
|
|
6779
|
+
}
|
|
6780
|
+
else if (step.kind === 'continue') {
|
|
6781
|
+
// The same edge a body's last step gets, from wherever this path has got
|
|
6782
|
+
// to — so a handler or an arm can go on to the next iteration instead of
|
|
6783
|
+
// ending the run on an End of its own (#1129).
|
|
6784
|
+
if (!loopExits) {
|
|
6785
|
+
throw new Error('.continue() has no enclosing .loop()/.doWhile() body to go back to — it is only valid inside a container.');
|
|
6786
|
+
}
|
|
6787
|
+
for (const t of tails)
|
|
6788
|
+
addEdge(scope, t, loopExits.node, loopExits.continue);
|
|
6952
6789
|
tails = [];
|
|
6953
6790
|
}
|
|
6954
6791
|
else if (step.kind === 'terminate') {
|
|
@@ -6986,7 +6823,8 @@ export function serialize(built, opts = {}) {
|
|
|
6986
6823
|
else {
|
|
6987
6824
|
// Any other port is a SIDE EXIT from the step just emitted. The main
|
|
6988
6825
|
// path is untouched, so `tails` survives and chaining continues.
|
|
6989
|
-
const
|
|
6826
|
+
const router = tails.length === 0 && lastAction !== undefined ? routerNodes.get(lastAction) : undefined;
|
|
6827
|
+
const from = tails[0] ?? (router !== undefined ? { node: router, port: step.port } : undefined);
|
|
6990
6828
|
if (!from)
|
|
6991
6829
|
throw new Error(`.stepToRef('${step.port}', '${step.target}') has no step to leave from.`);
|
|
6992
6830
|
refuseRewire('.stepToRef', step.port, lastAction ?? '?', tails);
|
|
@@ -7364,88 +7202,7 @@ export function assertBindingPlaceholdersResolve(flow) {
|
|
|
7364
7202
|
+ `"invalid GUID <bindings.…>") and refuses to publish. This is a serializer defect, not an authoring `
|
|
7365
7203
|
+ `error; report it with the flow source.`);
|
|
7366
7204
|
}
|
|
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
7205
|
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
7206
|
if (spec.kind === 'http') {
|
|
7450
7207
|
const i = spec.inputs;
|
|
7451
7208
|
if (i.managed)
|
|
@@ -7468,10 +7225,7 @@ function actionInputs(spec, rename = NO_RENAME, opts = {}, stepName = '') {
|
|
|
7468
7225
|
...httpBranchRows(i, rename),
|
|
7469
7226
|
};
|
|
7470
7227
|
}
|
|
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) };
|
|
7228
|
+
throw new Error(`internal SDK error: no inputs for action kind "${spec.kind}".`);
|
|
7475
7229
|
}
|
|
7476
7230
|
/**
|
|
7477
7231
|
* `timeout` / `retryCount` — the two request settings BOTH http node types carry.
|
|
@@ -7653,60 +7407,3 @@ function httpOutputs(base, returns, managed) {
|
|
|
7653
7407
|
};
|
|
7654
7408
|
return out;
|
|
7655
7409
|
}
|
|
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
|
-
}
|