@uipath/maestro-builder-sdk 6.21.1 → 6.22.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 +138 -138
- package/dist/api-members.md +407 -407
- package/dist/case/compile-cli.js +82 -26
- package/dist/check.js +43 -331
- package/dist/core/actions.d.ts +0 -147
- package/dist/core/actions.js +0 -15
- package/dist/core/expr.d.ts +1 -1
- package/dist/core/expr.js +2 -2
- package/dist/core/nodes/node-type.d.ts +15 -0
- package/dist/core/step-ports.js +7 -23
- package/dist/core/typed-raw-node.js +0 -2
- package/dist/decompile.js +44 -88
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +59 -0
- package/dist/flow-sdk.d.ts +1 -0
- package/dist/flow-sdk.js +1 -0
- package/dist/flow-step-exits.d.ts +12 -0
- package/dist/flow-step-exits.js +30 -0
- package/dist/nodes/http/flow/check.d.ts +58 -0
- package/dist/nodes/http/flow/check.js +248 -0
- package/dist/nodes/http/flow/factories.d.ts +14 -0
- package/dist/nodes/http/flow/factories.js +20 -0
- package/dist/nodes/http/flow/family.d.ts +8 -0
- package/dist/nodes/http/flow/family.js +13 -0
- package/dist/nodes/http/flow/index.d.ts +3 -0
- package/dist/nodes/http/flow/index.js +3 -0
- package/dist/nodes/http/flow/node-type.d.ts +7 -0
- package/dist/nodes/http/flow/node-type.js +411 -0
- package/dist/nodes/http/flow/spec.d.ts +151 -0
- package/dist/nodes/http/flow/spec.js +1 -0
- package/dist/serialize.d.ts +2 -2
- package/dist/serialize.js +20 -276
- 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 {};
|
package/dist/serialize.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
99
|
-
*
|
|
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;
|