@uipath/maestro-builder-sdk 6.21.2 → 6.23.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 (42) hide show
  1. package/dist/api-index.md +138 -138
  2. package/dist/api-members.md +407 -407
  3. package/dist/check.js +43 -331
  4. package/dist/core/actions.d.ts +0 -287
  5. package/dist/core/actions.js +0 -95
  6. package/dist/core/expr.d.ts +1 -1
  7. package/dist/core/expr.js +2 -2
  8. package/dist/core/nodes/node-type.d.ts +15 -0
  9. package/dist/core/step-ports.js +7 -23
  10. package/dist/core/typed-raw-node.js +0 -4
  11. package/dist/decompile.js +44 -115
  12. package/dist/flow-node-registry.js +3 -1
  13. package/dist/flow-node-type.d.ts +59 -0
  14. package/dist/flow-sdk.d.ts +2 -0
  15. package/dist/flow-sdk.js +2 -0
  16. package/dist/flow-step-exits.d.ts +12 -0
  17. package/dist/flow-step-exits.js +30 -0
  18. package/dist/nodes/ai-patterns/flow/factories.d.ts +83 -0
  19. package/dist/nodes/ai-patterns/flow/factories.js +86 -0
  20. package/dist/nodes/ai-patterns/flow/family.d.ts +3 -0
  21. package/dist/nodes/ai-patterns/flow/family.js +8 -0
  22. package/dist/nodes/ai-patterns/flow/index.d.ts +3 -0
  23. package/dist/nodes/ai-patterns/flow/index.js +3 -0
  24. package/dist/nodes/ai-patterns/flow/node-type.d.ts +11 -0
  25. package/dist/nodes/ai-patterns/flow/node-type.js +106 -0
  26. package/dist/nodes/ai-patterns/flow/spec.d.ts +75 -0
  27. package/dist/nodes/ai-patterns/flow/spec.js +1 -0
  28. package/dist/nodes/http/flow/check.d.ts +58 -0
  29. package/dist/nodes/http/flow/check.js +248 -0
  30. package/dist/nodes/http/flow/factories.d.ts +14 -0
  31. package/dist/nodes/http/flow/factories.js +20 -0
  32. package/dist/nodes/http/flow/family.d.ts +8 -0
  33. package/dist/nodes/http/flow/family.js +13 -0
  34. package/dist/nodes/http/flow/index.d.ts +3 -0
  35. package/dist/nodes/http/flow/index.js +3 -0
  36. package/dist/nodes/http/flow/node-type.d.ts +7 -0
  37. package/dist/nodes/http/flow/node-type.js +411 -0
  38. package/dist/nodes/http/flow/spec.d.ts +151 -0
  39. package/dist/nodes/http/flow/spec.js +1 -0
  40. package/dist/serialize.d.ts +2 -2
  41. package/dist/serialize.js +20 -316
  42. package/package.json +2 -2
@@ -0,0 +1,411 @@
1
+ /**
2
+ * The two HTTP node types: the standalone `core.action.http` (bundled at 1.0.0)
3
+ * and the managed `core.action.http.v2` (bundled at 2.3), routed through
4
+ * Integration Service's HTTP connector. They share the `http` kind; `managed`
5
+ * picks one (`matches`). Moved from serialize.ts / check.ts / decompile.ts in
6
+ * Phase 4 of docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; serialized output, codes and
7
+ * messages unchanged. Decompile now also recovers `returns` (see `liftReturns`).
8
+ */
9
+ import { Expr } from '../../../core/expr.js';
10
+ import { jsonClone } from '../../../core/json-file.js';
11
+ import { wireExprBody } from '../../../core/wire-expr.js';
12
+ import { checkHttpRead, checkHttpStep, plainHttpOnErrorMessage } from './check.js';
13
+ const PLAIN = 'core.action.http';
14
+ const MANAGED = 'core.action.http.v2';
15
+ /** The definition's `inputDefaults` at the version being emitted. */
16
+ function defaults(ctx, typeName) {
17
+ return jsonClone(ctx.definition(typeName, ctx.version)?.inputDefaults ?? {});
18
+ }
19
+ /** A `{ name: value }` input map, each value rendered as the file stores it. */
20
+ function renderMap(ctx, m) {
21
+ const out = {};
22
+ if (m)
23
+ for (const [k, v] of Object.entries(m))
24
+ out[k] = ctx.render(v);
25
+ return out;
26
+ }
27
+ /**
28
+ * `timeout` / `retryCount` — the two request settings BOTH http node types carry.
29
+ *
30
+ * **These fields were never MISSING from the emitted node.** Both definitions
31
+ * declare them in `inputDefaults` (`timeout: 'PT15M'`, `retryCount: 0`) and the
32
+ * `...defaults` spread has always carried them through, so every http node this
33
+ * SDK ever emitted already said fifteen minutes and no retries. What the author
34
+ * had no way to do was say anything ELSE — which is the actual gap, and why this
35
+ * spreads AFTER the defaults rather than instead of them.
36
+ *
37
+ * Emitting only-when-set therefore does two things at once: an author's value
38
+ * overrides the definition's, and an author who says nothing gets byte-for-byte
39
+ * what they got before. (~99% of the 808 deployed http nodes are on the
40
+ * defaults, so that is also the faithful shape.)
41
+ *
42
+ * `check` has already validated the FORM of both (`HTTP_TIMEOUT_NOT_DURATION`,
43
+ * `HTTP_RETRY_COUNT_INVALID`), so this writes them through verbatim.
44
+ */
45
+ function httpRequestSettings(i) {
46
+ return {
47
+ ...(i.timeout !== undefined ? { timeout: i.timeout } : {}),
48
+ ...(i.retryCount !== undefined ? { retryCount: i.retryCount } : {}),
49
+ };
50
+ }
51
+ /** The `branches` rows both HTTP definitions declare: id/name from the author's name, conditions rendered like a decision's. */
52
+ function httpBranchRows(i, ctx) {
53
+ if (!i.branches?.length)
54
+ return {};
55
+ return {
56
+ branches: i.branches.map((b) => ({
57
+ id: b.name,
58
+ name: b.name,
59
+ conditionExpression: ctx.renameRefs(b.condition.js),
60
+ })),
61
+ };
62
+ }
63
+ /** The standalone node's inputs: the request at the top level, beside the definition's defaults. */
64
+ function plainHttpInputs(i, ctx) {
65
+ if (i.connection !== undefined
66
+ || i.folder !== undefined
67
+ || i.targetConnector !== undefined) {
68
+ throw new Error('standalone http cannot use Integration Service connection settings; set managed: true');
69
+ }
70
+ return {
71
+ ...defaults(ctx, PLAIN),
72
+ method: i.method ?? 'GET',
73
+ url: ctx.render(i.url),
74
+ headers: renderMap(ctx, i.headers),
75
+ queryParams: renderMap(ctx, i.query),
76
+ body: i.body !== undefined ? ctx.render(i.body) : '',
77
+ contentType: i.contentType ?? 'application/json',
78
+ ...httpRequestSettings(i),
79
+ ...httpBranchRows(i, ctx),
80
+ };
81
+ }
82
+ /**
83
+ * A managed step's Integration Service connection, resolved through bindings.json:
84
+ * undefined for the platform's manual/implicit mode (neither label given).
85
+ */
86
+ function managedHttpBinding(i, ctx) {
87
+ const configured = i.connection !== undefined || i.folder !== undefined;
88
+ if (!configured)
89
+ return undefined;
90
+ if (typeof i.connection !== 'string' || i.connection.trim() === ''
91
+ || typeof i.folder !== 'string' || i.folder.trim() === '') {
92
+ throw new Error('managed http connection mode requires both a non-empty connection and folder');
93
+ }
94
+ // The same resolution a connector step gets: a self-referential binding is an
95
+ // error, a stub is a warning.
96
+ const where = `managed http "${ctx.step}"`;
97
+ return {
98
+ connectionId: ctx.resolveBinding(i.connection, 'connection', where),
99
+ folderKey: ctx.resolveBinding(i.folder, 'folder', where),
100
+ };
101
+ }
102
+ /**
103
+ * The MANAGED HTTP node's inputs (`core.action.http.v2`).
104
+ *
105
+ * The platform routes this one through Integration Service's own HTTP connector,
106
+ * so the request rides inside a connector `detail` envelope instead of sitting at
107
+ * the top level. Every constant below is read off the deployed corpus (384
108
+ * instances, 2026-05-11 → 07-29), not invented:
109
+ *
110
+ * - `uiPathActivityTypeId` — ONE value across all 331 instances that carry it;
111
+ * - `detail.method: 'POST'` + `endpoint: '/http-request'` — 352 of 352. That is
112
+ * how the flow calls the CONNECTOR; the HTTP verb the connector performs
113
+ * lives in `bodyParameters.method` (GET on 309). Two `method`s, one value
114
+ * each, and swapping them silently turns every GET into a POST;
115
+ * - `connectionId: 'ImplicitConnection'` — 290 of 352 (the rest bind an
116
+ * explicit Integration Service connection);
117
+ * - the `configuration` blob, whose `=jsonString:` envelope the platform reads
118
+ * back as the activity's instance parameters.
119
+ */
120
+ function managedHttpInputs(i, binding, ctx) {
121
+ if (i.targetConnector !== undefined && !binding) {
122
+ throw new Error('managed http targetConnector requires both a non-empty connection and folder');
123
+ }
124
+ if (i.targetConnector !== undefined
125
+ && (typeof i.targetConnector !== 'string' || i.targetConnector.trim() === '')) {
126
+ throw new Error('managed http targetConnector must be a non-empty connector key');
127
+ }
128
+ const url = ctx.render(i.url);
129
+ const bodyParameters = {
130
+ authentication: binding ? 'connector' : 'manual',
131
+ method: i.method ?? 'GET',
132
+ ...(binding ? {
133
+ targetConnector: i.targetConnector ?? 'uipath-uipath-http',
134
+ connection: binding.connectionId,
135
+ path: url,
136
+ } : {}),
137
+ url,
138
+ };
139
+ const headers = renderMap(ctx, i.headers);
140
+ if (Object.keys(headers).length > 0)
141
+ bodyParameters.headers = headers;
142
+ const query = renderMap(ctx, i.query);
143
+ if (Object.keys(query).length > 0)
144
+ bodyParameters.query = query;
145
+ if (i.body !== undefined)
146
+ bodyParameters.body = ctx.render(i.body);
147
+ return {
148
+ ...defaults(ctx, MANAGED),
149
+ ...httpBranchRows(i, ctx),
150
+ // TOP-LEVEL on the managed node too, NOT inside `detail` — the platform reads
151
+ // the request settings off the node, while `detail` is the connector call it
152
+ // wraps.
153
+ ...httpRequestSettings(i),
154
+ detail: {
155
+ connector: 'uipath-uipath-http',
156
+ connectionId: binding?.connectionId ?? 'ImplicitConnection',
157
+ connectionResourceId: binding?.connectionId ?? null,
158
+ connectionFolderKey: binding?.folderKey ?? null,
159
+ method: 'POST',
160
+ endpoint: '/http-request',
161
+ bodyParameters,
162
+ configuration: '=jsonString:' + JSON.stringify({
163
+ essentialConfiguration: {
164
+ instanceParameters: {
165
+ connectorKey: 'uipath-uipath-http',
166
+ objectName: 'http-request',
167
+ httpMethod: 'POST',
168
+ activityType: 'Curated',
169
+ version: '1.0.0',
170
+ },
171
+ objectName: 'http-request',
172
+ operation: 'create',
173
+ packageVersion: '1.0.0',
174
+ httpMethod: 'POST',
175
+ path: '/http-request',
176
+ unifiedTypesCompatible: true,
177
+ savedJitInputFieldId: 'in_http-request',
178
+ },
179
+ }),
180
+ uiPathActivityTypeId: '5c4cc855-b42a-37e6-b910-de8588998fce',
181
+ errorState: { issues: [] },
182
+ telemetryData: {
183
+ connectorKey: 'uipath-uipath-http',
184
+ connectorName: 'HTTP',
185
+ operationType: 'create',
186
+ objectName: 'http-request',
187
+ objectDisplayName: 'HTTP Request',
188
+ primaryKeyName: '',
189
+ },
190
+ },
191
+ };
192
+ }
193
+ /**
194
+ * An HTTP node's declared parsed-JSON response shape, from the author's `returns`.
195
+ *
196
+ * The platform's own definition declares `output` as a bare object — only the API
197
+ * being called knows its body — so a `body.<field>` read is unresolvable until the
198
+ * author says what is in there. Same rule, same word, as `rpaWorkflow.returns`.
199
+ * Undefined when there is nothing to declare: the definition's outputs stand.
200
+ */
201
+ function httpOutputs(base, returns, managed) {
202
+ if (!returns || Object.keys(returns).length === 0)
203
+ return undefined;
204
+ const out = jsonClone(base);
205
+ if (!out?.output)
206
+ return undefined;
207
+ // The whole `output` shape lives on the INSTANCE, not in the bundled
208
+ // definition — deliberately. An instance schema may refine a GENERIC definition
209
+ // object but never override one that already declares a
210
+ // shape, so declaring `body`/`statusCode` in the def would lock the author's
211
+ // `returns` out of the very read it exists to license. The definition stays
212
+ // byte-faithful to the platform's (a bare object, because only the API being
213
+ // called knows its body) and the author's declaration is the only source of
214
+ // shape — which is also what makes refusing a read with no `returns` correct.
215
+ out.output.properties = {
216
+ body: {
217
+ type: 'object',
218
+ description: managed
219
+ ? 'Response body, deserialized by Integration Service'
220
+ : 'JSON response body, deserialized by the standalone HTTP runtime',
221
+ properties: Object.fromEntries(Object.entries(returns).map(([name, type]) => [name, { type }])),
222
+ },
223
+ statusCode: { type: 'number', description: 'HTTP status code of the response' },
224
+ };
225
+ return out;
226
+ }
227
+ /** The vocabulary `returns` takes (`ReturnFieldType`). */
228
+ const RETURN_FIELD_TYPES = new Set(['string', 'number', 'integer', 'boolean', 'object', 'array']);
229
+ /**
230
+ * The `returns` a step was authored with, read back off the body schema
231
+ * `httpOutputs` writes onto the instance (`outputs.output.properties.body`).
232
+ * Without it a decompiled flow that reads a body field no longer compiles
233
+ * (`HTTP_READ_WITHOUT_RETURNS`). Undefined when the node declares no body fields.
234
+ */
235
+ function liftReturns(node) {
236
+ const props = node
237
+ ?.outputs?.output?.properties?.body?.properties;
238
+ if (!props || typeof props !== 'object')
239
+ return undefined;
240
+ // Only the types `returns` can name: a hand-edited schema's `null` or
241
+ // `date-time` would print source that no longer typechecks.
242
+ const entries = Object.entries(props)
243
+ .filter(([, v]) => typeof v?.type === 'string' && RETURN_FIELD_TYPES.has(v.type))
244
+ .map(([k, v]) => [k, v.type]);
245
+ return entries.length ? Object.fromEntries(entries) : undefined;
246
+ }
247
+ /**
248
+ * Invert an HTTP node. The plain node keeps its request at the top level
249
+ * (`url` / `method` / `headers` / `queryParams` / `body` / `contentType`); the
250
+ * managed node nests it inside `detail.bodyParameters` and routes through IS.
251
+ * Request settings (`timeout` / `retryCount`) sit at the top level on both, and
252
+ * are kept only when off their definition defaults, exactly as serialize writes
253
+ * them, so the round-trip stays minimal.
254
+ */
255
+ function liftHttp(node, ctx, managed) {
256
+ const inputs = (node.inputs ?? {});
257
+ const detail = managed ? (inputs.detail ?? {}) : {};
258
+ const req = managed ? (detail.bodyParameters ?? {}) : inputs;
259
+ // A value as the author wrote it: an expression back to an Expr, a literal
260
+ // string as itself, any other JSON as is.
261
+ const value = (raw) => {
262
+ const text = Array.isArray(raw) ? undefined : ctx.sourceText(raw);
263
+ if (text === undefined)
264
+ return raw;
265
+ return text.startsWith('=js:') ? ctx.expr(text) : text;
266
+ };
267
+ const map = (m) => m && typeof m === 'object' && Object.keys(m).length
268
+ ? Object.fromEntries(Object.entries(m).map(([k, v]) => [k, value(v)]))
269
+ : undefined;
270
+ const text = (v) => (typeof v === 'string' ? v : undefined);
271
+ const connectionValue = detail.connectionId ?? detail.connectionResourceId ?? req.connection;
272
+ const retry = inputs.retryCount !== undefined && Number(inputs.retryCount) !== 0 ? Number(inputs.retryCount) : undefined;
273
+ if (retry !== undefined && !Number.isFinite(retry)) {
274
+ throw new Error(`decompile: expected a number, got ${JSON.stringify(inputs.retryCount)}.`);
275
+ }
276
+ const branchRows = Array.isArray(inputs.branches) ? inputs.branches : [];
277
+ const returns = liftReturns(node);
278
+ const spec = {
279
+ url: value(req.url ?? ''),
280
+ ...(text(req.method) !== undefined && req.method !== 'GET' ? { method: req.method } : {}),
281
+ ...(map(managed ? req.headers : inputs.headers) ? { headers: map(managed ? req.headers : inputs.headers) } : {}),
282
+ ...(map(managed ? req.query : inputs.queryParams) ? { query: map(managed ? req.query : inputs.queryParams) } : {}),
283
+ ...(req.body !== undefined && req.body !== '' ? { body: value(req.body) } : {}),
284
+ ...(!managed && text(inputs.contentType) !== undefined && inputs.contentType !== 'application/json'
285
+ ? { contentType: inputs.contentType } : {}),
286
+ managed,
287
+ ...(managed && typeof connectionValue === 'string' && connectionValue !== 'ImplicitConnection'
288
+ ? { connection: connectionValue } : {}),
289
+ ...(managed && typeof detail.connectionFolderKey === 'string' && detail.connectionFolderKey.length > 0
290
+ ? { folder: detail.connectionFolderKey } : {}),
291
+ ...(managed && typeof req.targetConnector === 'string' && req.targetConnector !== 'uipath-uipath-http'
292
+ ? { targetConnector: req.targetConnector } : {}),
293
+ ...(typeof inputs.timeout === 'string' && inputs.timeout !== 'PT15M' ? { timeout: inputs.timeout } : {}),
294
+ ...(retry !== undefined ? { retryCount: retry } : {}),
295
+ ...(branchRows.length ? {
296
+ branches: branchRows.map((b) => ({
297
+ name: String(b.name ?? b.id ?? ''),
298
+ condition: new Expr(wireExprBody(ctx.sourceText(b.conditionExpression) ?? '')),
299
+ })),
300
+ } : {}),
301
+ ...(returns ? { returns } : {}),
302
+ };
303
+ return { kind: 'http', inputs: spec };
304
+ }
305
+ /** The factory call decompile prints, options in the order the hand-written printer used. */
306
+ function printHttp({ inputs: i }, ctx) {
307
+ // An expression as the helper an author writes; any other value as the data the node stores.
308
+ const val = (v) => (v instanceof Expr ? ctx.value(v) : ctx.json(v));
309
+ const map = (m) => m && Object.keys(m).length ? `{ ${Object.entries(m).map(([k, v]) => `${ctx.key(k)}: ${val(v)}`).join(', ')} }` : undefined;
310
+ const managed = i.managed === true;
311
+ const m = i;
312
+ return {
313
+ factory: 'http',
314
+ options: [
315
+ ['url', val(i.url)],
316
+ ['method', i.method !== undefined && i.method !== 'GET' ? ctx.string(i.method) : undefined],
317
+ ['headers', map(i.headers)],
318
+ ['query', map(i.query)],
319
+ ['body', i.body !== undefined ? val(i.body) : undefined],
320
+ ['contentType', i.contentType !== undefined ? ctx.string(i.contentType) : undefined],
321
+ ['managed', managed ? 'true' : 'false'],
322
+ ['connection', m.connection !== undefined ? ctx.string(m.connection) : undefined],
323
+ ['folder', m.folder !== undefined ? ctx.string(m.folder) : undefined],
324
+ ['targetConnector', m.targetConnector !== undefined ? ctx.string(m.targetConnector) : undefined],
325
+ ['timeout', i.timeout !== undefined ? ctx.string(i.timeout) : undefined],
326
+ ['retryCount', i.retryCount !== undefined ? ctx.json(i.retryCount) : undefined],
327
+ ['branches', i.branches?.length
328
+ ? `[${i.branches.map((b) => `{ name: ${ctx.string(b.name)}, condition: ${ctx.value(b.condition)} }`).join(', ')}]`
329
+ : undefined],
330
+ ['returns', i.returns && Object.keys(i.returns).length
331
+ ? `{ ${Object.entries(i.returns).map(([k, t]) => `${ctx.key(k)}: ${ctx.string(String(t))}`).join(', ')} }`
332
+ : undefined],
333
+ ],
334
+ };
335
+ }
336
+ /**
337
+ * The response branches the author declared, each on its `branch-<name>` port,
338
+ * and the fall-through `default`. The fall-through is an exit like any other
339
+ * here — it is what `.switch()` spells as its default arm, and leaving it out is
340
+ * the same omission as leaving out a branch.
341
+ */
342
+ function httpExits({ inputs }) {
343
+ const branches = (inputs?.branches ?? []);
344
+ if (branches.length === 0) {
345
+ return {
346
+ reason: 'it declares no response branches',
347
+ suggestion: `branches: [{ name: 'rateLimited', condition: … }]`,
348
+ };
349
+ }
350
+ return [
351
+ ...branches.map((b) => ({ value: String(b?.name ?? ''), port: `branch-${b?.name ?? ''}` })),
352
+ { value: 'default', port: 'default', isDefault: true },
353
+ ];
354
+ }
355
+ function httpAt(typeName, version, managed) {
356
+ return {
357
+ kind: 'http', typeName, version, factory: 'http', definition: 'bundled',
358
+ rawNode: { factory: managed ? 'http({ managed: true })' : 'http({ managed: false })' },
359
+ // The plain node is not one: it publishes no envelope anywhere (`<step>.error`
360
+ // is `true`, `<step>.output` null), so check refuses a handler on it instead.
361
+ ...(managed ? { errorEnvelope: 'output' } : {}),
362
+ serialize: ({ inputs }, ctx) => {
363
+ const binding = managed ? managedHttpBinding(inputs, ctx) : undefined;
364
+ const nodeInputs = managed
365
+ ? managedHttpInputs(inputs, binding, ctx)
366
+ : plainHttpInputs(inputs, ctx);
367
+ const def = ctx.definition(typeName, ctx.version);
368
+ if (managed) {
369
+ // ImplicitConnection is the binding's default, not a remote resource key.
370
+ // Keeping the key empty matches product-authored manual HTTP nodes and
371
+ // lets the runtime supply the debug folder.
372
+ ctx.bindConnection({
373
+ model: def?.model,
374
+ connectorKey: 'uipath-uipath-http',
375
+ connectionId: binding?.connectionId ?? 'ImplicitConnection',
376
+ ...(binding ? { folderKey: binding.folderKey } : { resourceKey: '' }),
377
+ });
378
+ }
379
+ const outputs = httpOutputs(def?.outputDefinition, inputs.returns, managed);
380
+ return { inputs: nodeInputs, ...(outputs ? { outputs } : {}), port: 'default' };
381
+ },
382
+ deserialize: (node, ctx) => liftHttp(node, ctx, managed),
383
+ print: printHttp,
384
+ check: ({ inputs }, ctx) => checkHttpStep(inputs, ctx),
385
+ // The plain node's port exists (its definition declares it) but catches
386
+ // nothing a handler can read; see plainHttpOnErrorMessage.
387
+ ...(managed ? {} : {
388
+ checkErrorHandler: (_spec, ctx) => {
389
+ const { message, suggestion } = plainHttpOnErrorMessage(ctx.step);
390
+ ctx.report('HTTP_ONERROR_V1', message, suggestion);
391
+ },
392
+ }),
393
+ expressions: ({ inputs }) => [
394
+ { value: inputs.url },
395
+ { value: inputs.body },
396
+ ...Object.values(inputs.headers ?? {}).map((v) => ({ value: v })),
397
+ ...Object.values(inputs.query ?? {}).map((v) => ({ value: v })),
398
+ ...(inputs.branches ?? []).map((b) => ({ value: b.condition })),
399
+ ],
400
+ output: ({ inputs }) => ({ kind: 'reader', read: (read, report) => checkHttpRead(read, inputs, report) }),
401
+ exits: httpExits,
402
+ };
403
+ }
404
+ function httpType(typeName, version, managed) {
405
+ return {
406
+ kind: 'http', typeName, factory: 'http', defaultVersion: version,
407
+ versions: { [version]: httpAt(typeName, version, managed) },
408
+ matches: ({ inputs }) => Boolean(inputs?.managed) === managed,
409
+ };
410
+ }
411
+ export const httpNodeTypes = [httpType(PLAIN, '1.0.0', false), httpType(MANAGED, '2.3', true)];
@@ -0,0 +1,151 @@
1
+ /**
2
+ * The HTTP family's authoring types (`core.action.http`, `core.action.http.v2`),
3
+ * and its kind's entry in `FlowActionSpecs`. Moved from core/actions.ts in
4
+ * Phase 4 of docs/NODE_TYPE_ARCHITECTURE_DESIGN.md.
5
+ */
6
+ import type { Expr } from '../../../core/expr.js';
7
+ import type { ReturnFieldType } from '../../../core/actions.js';
8
+ /** One conditional response branch on an HTTP node — a designer-visible side exit. */
9
+ export interface HttpBranch {
10
+ /**
11
+ * The branch's name — it becomes the node's `branch-<name>` source port, so
12
+ * keep it port-safe (letters, digits, `_`, `-`). Route it with
13
+ * `.stepToList('branch-<name>', ...)` after the step; the main path continues
14
+ * from the default port as always.
15
+ */
16
+ name: string;
17
+ /**
18
+ * When to take this exit, evaluated against the response — e.g.
19
+ * js-tagged: `$vars.fetch.output.statusCode === 429`. Branches are checked in
20
+ * order; the first truthy one wins, else the default port fires.
21
+ */
22
+ condition: Expr;
23
+ }
24
+ interface HttpInputsBase {
25
+ /**
26
+ * Conditional response branches — extra source ports (`branch-<name>`)
27
+ * beside the default. See {@link HttpBranch}; both HTTP node families
28
+ * declare them.
29
+ */
30
+ branches?: HttpBranch[];
31
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
32
+ url: string | Expr;
33
+ headers?: Record<string, string | Expr>;
34
+ query?: Record<string, string | Expr>;
35
+ body?: string | Expr;
36
+ contentType?: string;
37
+ /**
38
+ * Choose the HTTP node explicitly. `false` emits the standalone
39
+ * `core.action.http`; `true` emits the managed `core.action.http.v2`, routed
40
+ * through Integration Service's HTTP connector.
41
+ *
42
+ * This has no default because these are different platform nodes, not two
43
+ * modes of one node. A silent `false` made an omitted field compile and
44
+ * validate as the wrong node while changing failure routing and connection
45
+ * behavior.
46
+ *
47
+ * It is not a version bump, it changes two things you can see:
48
+ *
49
+ * - **a 4xx/5xx FAILS the step**, so `.onError(…)` fires on it. On the plain
50
+ * node the same 404 comes back on the SUCCESS path as
51
+ * `out('fetch','statusCode')` and no error handler ever runs — which is why
52
+ * an error port only makes sense here (`check` says so: HTTP_ONERROR_V1).
53
+ * The handler can answer the flow (`.return`), stop the run
54
+ * (`.terminate`), or compensate and continue at a step the success path
55
+ * also reaches (`h.stepToRef('<step>')` — see `references/error-handling.md`);
56
+ * - both nodes expose JSON responses as parsed values in the live product;
57
+ * use `returns` to declare fields before reading them. The node choice does
58
+ * not decide whether application/json is parsed.
59
+ *
60
+ * With `managed: true`, omit `connection` and `folder` for the platform's
61
+ * manual/implicit mode, or provide both symbolic names to reuse an Integration
62
+ * Service HTTP connection. A local run remains unauthenticated.
63
+ *
64
+ * @enforcedBy HTTP_ONERROR_V1 `.onError()` on an http step needs `managed: true`; on
65
+ * the standalone node a 4xx arrives on the SUCCESS path and no handler runs.
66
+ */
67
+ managed: boolean;
68
+ /**
69
+ * The response BODY's top-level fields and their types — e.g.
70
+ * `{ items: 'array' }`. Required for either HTTP node if anything reads a
71
+ * field off a parsed JSON body.
72
+ *
73
+ * Why you have to say: the platform's own definition declares the response as
74
+ * a bare object, because only the API being called knows its shape. Without
75
+ * this, `out('fetch','body','items')` reads from an object with no declared
76
+ * fields, and nothing downstream can tell a real field from a typo. Same rule,
77
+ * and the same word, as
78
+ * `rpaWorkflow`'s `returns`.
79
+ *
80
+ * **Where a declared field is read:** under `body`, never beside it. The
81
+ * response is an envelope — `{ body, statusCode, headers }` — and these are the
82
+ * body's fields, so `returns: { items: 'array' }` is read as
83
+ * `out('fetch', 'body.items')`, which is `$vars.fetch.output.body.items`.
84
+ * `$vars.fetch.output.items` is undefined at run time.
85
+ *
86
+ * @enforcedBy HTTP_READ_UNKNOWN_FIELD A read must be `output.body.<declared field>`,
87
+ * `output.statusCode` or `output.headers`.
88
+ * @enforcedBy HTTP_READ_WITHOUT_RETURNS Reading a body field requires declaring it here.
89
+ */
90
+ returns?: Record<string, ReturnFieldType>;
91
+ /**
92
+ * How long to wait for the response before giving up, as an **ISO-8601
93
+ * duration** — `'PT30S'` is "give up after 30 seconds", `'PT1M'` one minute,
94
+ * `'PT15M'` fifteen.
95
+ *
96
+ * Both http node types carry this, and both definitions declare the same
97
+ * default (`PT15M`), so leaving it unset is not "no timeout" — it is fifteen
98
+ * minutes. Omitted from the emitted node unless you set it, which keeps a flow
99
+ * that doesn't care byte-identical to the platform's own default shape (~99%
100
+ * of the 808 deployed http nodes are on the defaults).
101
+ *
102
+ * `check` rejects a string the platform cannot parse as a duration —
103
+ * `'30s'`, `'PT'` and `'30'` are all errors that name the expected form.
104
+ *
105
+ * @defaultValue `'PT15M'`
106
+ * @enforcedBy HTTP_TIMEOUT_NOT_DURATION Must be an ISO-8601 duration.
107
+ */
108
+ timeout?: string;
109
+ /**
110
+ * How many times to RETRY a retryable failure (5xx, 429, a network error) —
111
+ * `0`, the definitions' default, means "try once, don't retry". `2` means up
112
+ * to three attempts in total.
113
+ *
114
+ * Must be a non-negative integer. Above **5** `check` warns rather than
115
+ * errors: the deployed corpus's
116
+ * largest author-set value is 3, and the platform's own ceiling is not
117
+ * measured — so a bigger number may well work, and refusing it outright would
118
+ * fence off something we have no evidence is wrong.
119
+ *
120
+ * @defaultValue `0`
121
+ * @enforcedBy HTTP_RETRY_COUNT_INVALID Must be a non-negative integer.
122
+ */
123
+ retryCount?: number;
124
+ }
125
+ export type HttpInputs = HttpInputsBase & ({
126
+ managed: false;
127
+ connection?: never;
128
+ folder?: never;
129
+ } | {
130
+ managed: true;
131
+ /** Symbolic HTTP connection name declared in bindings.json. */
132
+ connection?: string;
133
+ /** Symbolic folder name declared in bindings.json. */
134
+ folder?: string;
135
+ /**
136
+ * Connector key whose authentication the HTTP proxy should reuse.
137
+ * Defaults to `uipath-uipath-http`. Set this for a connector-specific
138
+ * fallback endpoint, for example `uipath-salesforce-slack`.
139
+ * Requires both `connection` and `folder`.
140
+ */
141
+ targetConnector?: string;
142
+ });
143
+ declare module '../../../core/actions.js' {
144
+ interface FlowActionSpecs {
145
+ /** The spec {@link http} builds. */
146
+ http: {
147
+ inputs: HttpInputs;
148
+ };
149
+ }
150
+ }
151
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -95,8 +95,8 @@ export declare function serializeWithSidecars(built: BuiltFlow, opts?: Serialize
95
95
  * agenticProcess, publishedFunction, agent, ixpExtract) and for a spec this SDK
96
96
  * does not lower by kind.
97
97
  *
98
- * The http / transform / delay / script emit path reads its type from here too,
99
- * so the answer `check` gets is the type `serialize` writes.
98
+ * A registered kind (flow-node-registry.ts) is not listed: its type is the
99
+ * registry's.
100
100
  */
101
101
  export declare function bundledActionNodeType(spec: {
102
102
  kind?: string;