@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.
Files changed (111) hide show
  1. package/dist/api-index.md +185 -184
  2. package/dist/api-members.md +504 -473
  3. package/dist/bpmn/uipath-moddle.v1.json +5 -0
  4. package/dist/case/decompile-cli.js +2 -8
  5. package/dist/check.js +177 -650
  6. package/dist/core/actions.d.ts +119 -760
  7. package/dist/core/actions.js +0 -306
  8. package/dist/core/error-port.d.ts +6 -0
  9. package/dist/core/error-port.js +9 -0
  10. package/dist/core/node-classes.d.ts +4 -63
  11. package/dist/core/node-classes.js +0 -112
  12. package/dist/core/nodes/family.d.ts +36 -0
  13. package/dist/core/nodes/family.js +1 -0
  14. package/dist/core/nodes/node-type.d.ts +136 -0
  15. package/dist/core/nodes/node-type.js +1 -0
  16. package/dist/core/nodes/registry.d.ts +57 -0
  17. package/dist/core/nodes/registry.js +122 -0
  18. package/dist/core/nodes/versioned.d.ts +50 -0
  19. package/dist/core/nodes/versioned.js +46 -0
  20. package/dist/core/typed-raw-node.d.ts +13 -62
  21. package/dist/core/typed-raw-node.js +40 -154
  22. package/dist/decompile-cli.js +2 -9
  23. package/dist/decompile.js +92 -271
  24. package/dist/flow-definitions.d.ts +10 -0
  25. package/dist/flow-definitions.js +35 -0
  26. package/dist/flow-node-registry.d.ts +13 -0
  27. package/dist/flow-node-registry.js +19 -0
  28. package/dist/flow-node-type.d.ts +192 -0
  29. package/dist/flow-node-type.js +1 -0
  30. package/dist/flow-sdk.d.ts +7 -0
  31. package/dist/flow-sdk.js +8 -0
  32. package/dist/nodes/data-fabric/flow/check.d.ts +13 -0
  33. package/dist/nodes/data-fabric/flow/check.js +164 -0
  34. package/dist/nodes/data-fabric/flow/factories.d.ts +113 -0
  35. package/dist/nodes/data-fabric/flow/factories.js +134 -0
  36. package/dist/nodes/data-fabric/flow/family.d.ts +7 -0
  37. package/dist/nodes/data-fabric/flow/family.js +9 -0
  38. package/dist/nodes/data-fabric/flow/index.d.ts +8 -0
  39. package/dist/nodes/data-fabric/flow/index.js +8 -0
  40. package/dist/nodes/data-fabric/flow/raw.d.ts +32 -0
  41. package/dist/nodes/data-fabric/flow/raw.js +100 -0
  42. package/dist/nodes/data-fabric/flow/shared.d.ts +77 -0
  43. package/dist/nodes/data-fabric/flow/shared.js +174 -0
  44. package/dist/nodes/data-fabric/flow/spec.d.ts +238 -0
  45. package/dist/nodes/data-fabric/flow/spec.js +1 -0
  46. package/dist/nodes/data-fabric/flow/verbs.d.ts +29 -0
  47. package/dist/nodes/data-fabric/flow/verbs.js +239 -0
  48. package/dist/nodes/delay/flow/check.d.ts +16 -0
  49. package/dist/nodes/delay/flow/check.js +86 -0
  50. package/dist/nodes/delay/flow/factories.d.ts +28 -0
  51. package/dist/nodes/delay/flow/factories.js +38 -0
  52. package/dist/nodes/delay/flow/family.d.ts +3 -0
  53. package/dist/nodes/delay/flow/family.js +8 -0
  54. package/dist/nodes/delay/flow/index.d.ts +3 -0
  55. package/dist/nodes/delay/flow/index.js +3 -0
  56. package/dist/nodes/delay/flow/node-type.d.ts +13 -0
  57. package/dist/nodes/delay/flow/node-type.js +71 -0
  58. package/dist/nodes/delay/flow/spec.d.ts +43 -0
  59. package/dist/nodes/delay/flow/spec.js +16 -0
  60. package/dist/nodes/document/flow/factories.d.ts +27 -0
  61. package/dist/nodes/document/flow/factories.js +30 -0
  62. package/dist/nodes/document/flow/family.d.ts +3 -0
  63. package/dist/nodes/document/flow/family.js +7 -0
  64. package/dist/nodes/document/flow/index.d.ts +3 -0
  65. package/dist/nodes/document/flow/index.js +3 -0
  66. package/dist/nodes/document/flow/node-type.d.ts +14 -0
  67. package/dist/nodes/document/flow/node-type.js +150 -0
  68. package/dist/nodes/document/flow/spec.d.ts +65 -0
  69. package/dist/nodes/document/flow/spec.js +1 -0
  70. package/dist/nodes/mock/flow/factories.d.ts +51 -0
  71. package/dist/nodes/mock/flow/factories.js +47 -0
  72. package/dist/nodes/mock/flow/family.d.ts +3 -0
  73. package/dist/nodes/mock/flow/family.js +7 -0
  74. package/dist/nodes/mock/flow/index.d.ts +2 -0
  75. package/dist/nodes/mock/flow/index.js +2 -0
  76. package/dist/nodes/mock/flow/node-type.d.ts +11 -0
  77. package/dist/nodes/mock/flow/node-type.js +51 -0
  78. package/dist/nodes/script/flow/check.d.ts +19 -0
  79. package/dist/nodes/script/flow/check.js +49 -0
  80. package/dist/nodes/script/flow/factories.d.ts +14 -0
  81. package/dist/nodes/script/flow/factories.js +15 -0
  82. package/dist/nodes/script/flow/family.d.ts +3 -0
  83. package/dist/nodes/script/flow/family.js +8 -0
  84. package/dist/nodes/script/flow/index.d.ts +3 -0
  85. package/dist/nodes/script/flow/index.js +3 -0
  86. package/dist/nodes/script/flow/node-type.d.ts +7 -0
  87. package/dist/nodes/script/flow/node-type.js +125 -0
  88. package/dist/nodes/script/flow/spec.d.ts +52 -0
  89. package/dist/nodes/script/flow/spec.js +1 -0
  90. package/dist/nodes/transform/flow/check.d.ts +6 -0
  91. package/dist/nodes/transform/flow/check.js +229 -0
  92. package/dist/nodes/transform/flow/factories.d.ts +52 -0
  93. package/dist/nodes/transform/flow/factories.js +53 -0
  94. package/dist/nodes/transform/flow/family.d.ts +3 -0
  95. package/dist/nodes/transform/flow/family.js +8 -0
  96. package/dist/nodes/transform/flow/index.d.ts +3 -0
  97. package/dist/nodes/transform/flow/index.js +3 -0
  98. package/dist/nodes/transform/flow/node-type.d.ts +7 -0
  99. package/dist/nodes/transform/flow/node-type.js +244 -0
  100. package/dist/nodes/transform/flow/spec.d.ts +116 -0
  101. package/dist/nodes/transform/flow/spec.js +10 -0
  102. package/dist/raw-node-advice.d.ts +16 -0
  103. package/dist/raw-node-advice.js +36 -0
  104. package/dist/raw-node.d.ts +71 -0
  105. package/dist/raw-node.js +116 -0
  106. package/dist/registry/uip/launch.d.ts +11 -1
  107. package/dist/registry/uip/launch.js +50 -43
  108. package/dist/registry/uip/pipeline-front-door.d.ts +2 -0
  109. package/dist/registry/uip/pipeline-front-door.js +88 -0
  110. package/dist/serialize.js +138 -492
  111. 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 { Expr, toExpr, SCHEDULE_PRESETS, DELAY_PRESETS, parseIxpProjectType, flatNameOf, flatNameSegmentsOf } from './flow-sdk.js';
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 { dataFabricUnknownInputs } from './core/typed-raw-node.js';
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 = JSON.parse(readFileSync(join(HERE, 'core-definitions.json'), 'utf8'));
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 Data Fabric node gets no node-level `model`, whichever path emits it
4918
- // (the typed verbs, or a rawNode through emitContribution). Studio Web
4919
- // never writes one (flow-core node.ts: "`node.model` is never written";
4920
- // canvas `nodeToInstance` omits it), and export seeds it from the
4921
- // definition (services bpmn-to-xml.ts), so a copy here can only go stale.
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.script || type === NODE_TYPE.loop)
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
- // A Data Fabric key the product never reads (`_sortOptions`) is refused for
5189
- // every contribution, whichever class or SDK copy made it: `validate`
5190
- // cannot see it, and the node would ignore it at run time.
5191
- const unknownInput = dataFabricUnknownInputs(def.nodeType, def.version, contribution.inputs);
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
- const node = makeNode(def.nodeType, id, stepName, { parentId, def, requestedVersion });
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
- hoistContributionBindings(def, contribution.bindings, where);
5201
- contributionDefs.set(id, def);
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 (!['http', 'transform', 'delay', 'script'].includes(spec.kind)) {
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
- // A script BODY is a plain string, not an Expr, and it reads step outputs the
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
- }