@uipath/maestro-builder-sdk 6.17.5 → 6.18.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/bindings-v2.js +15 -17
- package/dist/bpmn/bpmn-sdk.js +3 -2
- package/dist/bpmn/check.js +3 -2
- package/dist/bpmn/decompile.js +123 -137
- package/dist/bpmn/intsvc.js +7 -2
- package/dist/bpmn/serialize.js +1 -1
- package/dist/bpmn/typed-node.js +5 -4
- package/dist/case/compile-cli.js +1 -3
- package/dist/case/decompile.js +103 -109
- package/dist/case/entry-points.js +2 -1
- package/dist/case/merge.js +3 -5
- package/dist/case/serialize.js +1 -1
- package/dist/check.js +26 -10
- package/dist/core/ceql.js +5 -2
- package/dist/core/connector-inputs.js +4 -4
- package/dist/core/event-checks.js +17 -2
- package/dist/core/event-filters.d.ts +38 -4
- package/dist/core/event-filters.js +63 -7
- package/dist/core/expr.d.ts +1 -1
- package/dist/core/expr.js +3 -3
- package/dist/core/json-file.d.ts +2 -0
- package/dist/core/json-file.js +4 -0
- package/dist/core/library.js +2 -1
- package/dist/core/node-envelope.d.ts +1 -0
- package/dist/core/node-envelope.js +1 -1
- package/dist/core/own-entry.d.ts +9 -0
- package/dist/core/own-entry.js +11 -0
- package/dist/core/ts-literal.d.ts +22 -0
- package/dist/core/ts-literal.js +37 -0
- package/dist/core/typed-raw-node.js +4 -3
- package/dist/decompile.js +272 -300
- package/dist/merge.js +10 -12
- package/dist/registry/compat/pystr.d.ts +2 -0
- package/dist/registry/compat/pystr.js +35 -0
- package/dist/registry/compat/pyvalue.d.ts +16 -0
- package/dist/registry/compat/pyvalue.js +35 -0
- package/dist/registry/generators/markdown/pyvalue.d.ts +1 -4
- package/dist/registry/generators/markdown/pyvalue.js +2 -30
- package/dist/registry/generators/markdown/sections/inputs.js +3 -2
- package/dist/registry/generators/type-tree.d.ts +1 -7
- package/dist/registry/generators/type-tree.js +5 -22
- package/dist/registry/library-build/build.js +6 -6
- package/dist/registry/library-build/canonical.js +25 -25
- package/dist/registry/library-build/context.js +5 -5
- package/dist/registry/library-build/enrich.js +18 -18
- package/dist/registry/library-build/events.js +25 -24
- package/dist/registry/library-build/generic-expand.js +21 -20
- package/dist/registry/library-build/plan.js +8 -8
- package/dist/registry/library-build/py.d.ts +3 -10
- package/dist/registry/library-build/py.js +9 -52
- package/dist/registry/library-build/schema-action.js +6 -6
- package/dist/registry/prepare/event.js +2 -2
- package/dist/registry/prepare/source-import.js +2 -9
- package/dist/registry/prepare/source-labels.js +2 -9
- package/dist/registry/prepare/support.d.ts +2 -6
- package/dist/registry/prepare/support.js +3 -58
- package/dist/registry/pull-cli.js +6 -14
- package/dist/registry/search-cli.js +2 -2
- package/dist/schema-version.js +0 -1
- package/dist/serialize.js +86 -76
- package/package.json +2 -2
|
@@ -17,7 +17,7 @@ import { mapConcurrent } from "../concurrency.js";
|
|
|
17
17
|
import { messageOf } from "../errors.js";
|
|
18
18
|
import { normalizeFieldKeys, pick } from "./canonical.js";
|
|
19
19
|
import { isDescribeObject, isDescribeOperation } from "./context.js";
|
|
20
|
-
import { get, item, pyEquals, pyIter, pyOr,
|
|
20
|
+
import { get, item, pyEquals, pyIter, pyOr, pyTruthy } from "./py.js";
|
|
21
21
|
/**
|
|
22
22
|
* Registry `httpMethod` (e.g. `GETBYID`) → a guess at the `is resources`
|
|
23
23
|
* operation name, for an entry whose canonical `operation.name` is empty.
|
|
@@ -40,10 +40,10 @@ export async function discoverOperationName(ctx, entry) {
|
|
|
40
40
|
const connectorKey = item(item(entry, "connector"), "key");
|
|
41
41
|
const objectName = item(operation, "objectName");
|
|
42
42
|
const httpMethod = item(operation, "httpMethod");
|
|
43
|
-
if (!
|
|
43
|
+
if (!pyTruthy(connectorKey) || !pyTruthy(objectName))
|
|
44
44
|
return null;
|
|
45
45
|
const objData = await isDescribeObject(ctx, connectorKey, objectName);
|
|
46
|
-
if (!
|
|
46
|
+
if (!pyTruthy(objData))
|
|
47
47
|
return null;
|
|
48
48
|
for (const op of pyIter(pyOr(pick(objData, "AvailableOperations", "availableOperations"), []))) {
|
|
49
49
|
if (pyEquals(pick(op, "Method", "method"), httpMethod))
|
|
@@ -64,31 +64,31 @@ export function applyDescribe(entry, data) {
|
|
|
64
64
|
const opData = pyOr(pick(data, "Operation", "operation"), {});
|
|
65
65
|
// If the name was discovered via fallback, persist it now.
|
|
66
66
|
const opResolvedName = pick(opData, "Name", "name");
|
|
67
|
-
if (!
|
|
67
|
+
if (!pyTruthy(item(operation, "name")) && pyTruthy(opResolvedName))
|
|
68
68
|
operation.name = opResolvedName;
|
|
69
69
|
// Path with placeholders (supersedes the simple `path`).
|
|
70
70
|
const opPath = pick(opData, "Path", "path");
|
|
71
|
-
if (
|
|
71
|
+
if (pyTruthy(opPath))
|
|
72
72
|
operation.pathTemplate = opPath;
|
|
73
|
-
if (!
|
|
73
|
+
if (!pyTruthy(get(operation, "objectDisplayName"))) {
|
|
74
74
|
const displayName = pick(data, "DisplayName", "displayName");
|
|
75
|
-
if (
|
|
75
|
+
if (pyTruthy(displayName))
|
|
76
76
|
operation.objectDisplayName = displayName;
|
|
77
77
|
}
|
|
78
78
|
const opCurated = pick(opData, "Curated", "curated");
|
|
79
|
-
if (
|
|
79
|
+
if (pyTruthy(opCurated) && !pyTruthy(get(display, "operationLabel")))
|
|
80
80
|
display.operationLabel = opCurated;
|
|
81
81
|
// Parameters (path / query) — raw shape, camelCase keys.
|
|
82
82
|
const params = pyOr(pick(data, "Parameters", "parameters"), []);
|
|
83
|
-
if (
|
|
83
|
+
if (pyTruthy(params))
|
|
84
84
|
operation.parameters = normalizeFieldKeys(params);
|
|
85
85
|
// Do not infer schema discovery from empty field lists: delete and file
|
|
86
86
|
// operations legitimately expose neither. Connector metadata owns that flag.
|
|
87
87
|
const requestFields = pyOr(pick(data, "RequestFields", "requestFields"), []);
|
|
88
88
|
const responseFields = pyOr(pick(data, "ResponseFields", "responseFields"), []);
|
|
89
|
-
if (
|
|
89
|
+
if (pyTruthy(requestFields))
|
|
90
90
|
e.inputSchema = { fields: normalizeFieldKeys(requestFields) };
|
|
91
|
-
if (
|
|
91
|
+
if (pyTruthy(responseFields))
|
|
92
92
|
e.outputSchema = { fields: normalizeFieldKeys(responseFields) };
|
|
93
93
|
return entry;
|
|
94
94
|
}
|
|
@@ -111,16 +111,16 @@ export async function enrichEntry(ctx, entry) {
|
|
|
111
111
|
const objectName = item(operation, "objectName");
|
|
112
112
|
let opName = item(operation, "name");
|
|
113
113
|
// Fallback: no operation name from the registry — discover via httpMethod.
|
|
114
|
-
if (!
|
|
114
|
+
if (!pyTruthy(opName)) {
|
|
115
115
|
opName = pyOr(await discoverOperationName(ctx, entry), HTTP_METHOD_TO_OPERATION.get(item(operation, "httpMethod")) ?? null);
|
|
116
116
|
}
|
|
117
|
-
if (!
|
|
117
|
+
if (!pyTruthy(opName))
|
|
118
118
|
return entry; // nothing to do
|
|
119
119
|
const failuresBefore = ctx.failedFieldDescribes.length;
|
|
120
120
|
let data = await isDescribeOperation(ctx, connectorKey, objectName, opName);
|
|
121
|
-
if (!
|
|
121
|
+
if (!pyTruthy(data)) {
|
|
122
122
|
const alt = await discoverOperationName(ctx, entry);
|
|
123
|
-
if (
|
|
123
|
+
if (pyTruthy(alt) && alt !== opName) {
|
|
124
124
|
// The registry's verb did not name an operation the object has, so its
|
|
125
125
|
// `-f` rejection is about a request that is now superseded: the CLI maps
|
|
126
126
|
// `List` to GET, and Data Service `QueryEntityRecordsCurated` exposes
|
|
@@ -134,7 +134,7 @@ export async function enrichEntry(ctx, entry) {
|
|
|
134
134
|
data = await isDescribeOperation(ctx, connectorKey, objectName, alt);
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
if (!
|
|
137
|
+
if (!pyTruthy(data))
|
|
138
138
|
return entry; // connector might be a stub or recently removed
|
|
139
139
|
return applyDescribe(entry, data);
|
|
140
140
|
}
|
|
@@ -159,9 +159,9 @@ export async function enrichAll(ctx, entries) {
|
|
|
159
159
|
let pathTemplateAdded = 0;
|
|
160
160
|
let schemaAdded = 0;
|
|
161
161
|
for (const e of enriched) {
|
|
162
|
-
if (
|
|
162
|
+
if (pyTruthy(get(get(e, "operation", {}), "pathTemplate")))
|
|
163
163
|
pathTemplateAdded += 1;
|
|
164
|
-
if (
|
|
164
|
+
if (pyTruthy(get(get(e, "inputSchema", {}), "fields")) || pyTruthy(get(get(e, "outputSchema", {}), "fields")))
|
|
165
165
|
schemaAdded += 1;
|
|
166
166
|
}
|
|
167
167
|
ctx.reporter.info(` pathTemplate added on ${pathTemplateAdded}/${entries.length}`);
|
|
@@ -22,7 +22,8 @@ import { failureReason } from "../uip/envelope.js";
|
|
|
22
22
|
import { safeCacheName } from "../uip/response-cache.js";
|
|
23
23
|
import { schemaCacheScope, writeCache } from "./context.js";
|
|
24
24
|
import { objectsComeFromTheConnection } from "./generic-expand.js";
|
|
25
|
-
import { codePointSlice,
|
|
25
|
+
import { codePointSlice, get, isPyDict, item, pyIter, pyOr, pyTruthy } from "./py.js";
|
|
26
|
+
import { jsonClone } from "../../core/json-file.js";
|
|
26
27
|
/**
|
|
27
28
|
* `Parameters[].type` on a `triggers objects` row routes the value into one of
|
|
28
29
|
* three `--detail` buckets at emit time. Everything unrecognized is an event
|
|
@@ -87,7 +88,7 @@ async function runJson(client, argv, timeoutMs) {
|
|
|
87
88
|
export async function triggersObjects(client, key, operation, connectionId, timeoutMs) {
|
|
88
89
|
const payload = await runJson(client, isTriggersObjects(key, operation, connectionId), timeoutMs);
|
|
89
90
|
let data = get(payload, "Data");
|
|
90
|
-
if (
|
|
91
|
+
if (isPyDict(data))
|
|
91
92
|
data = get(data, "items");
|
|
92
93
|
return Array.isArray(data) ? data : [];
|
|
93
94
|
}
|
|
@@ -102,7 +103,7 @@ function lowerKey(k) {
|
|
|
102
103
|
}
|
|
103
104
|
/** `_camelize`: lower-case the first letter of every key, recursively. */
|
|
104
105
|
export function camelize(value) {
|
|
105
|
-
if (
|
|
106
|
+
if (isPyDict(value)) {
|
|
106
107
|
const out = {};
|
|
107
108
|
for (const [k, v] of Object.entries(value))
|
|
108
109
|
out[lowerKey(k)] = camelize(v);
|
|
@@ -122,23 +123,23 @@ function lowerTop(f) {
|
|
|
122
123
|
/** `_is_hidden`: a hidden parameter that is safe to DROP — hidden and optional. */
|
|
123
124
|
export function isHidden(low) {
|
|
124
125
|
const raw = pyOr(get(low, "design"), {});
|
|
125
|
-
if (!
|
|
126
|
+
if (!isPyDict(raw))
|
|
126
127
|
return false;
|
|
127
128
|
const design = camelize(raw);
|
|
128
|
-
const hidden =
|
|
129
|
-
return hidden && !
|
|
129
|
+
const hidden = pyTruthy(pyOr(get(design, "isHidden"), get(design, "hidden")));
|
|
130
|
+
return hidden && !pyTruthy(get(low, "required"));
|
|
130
131
|
}
|
|
131
132
|
/** `_keep_hidden_marker`: flag a kept parameter the designer hides (necessarily a required one). */
|
|
132
133
|
export function keepHiddenMarker(low, field) {
|
|
133
134
|
const design = camelize(pyOr(get(low, "design"), {}));
|
|
134
|
-
if (
|
|
135
|
+
if (isPyDict(design) && pyTruthy(pyOr(get(design, "isHidden"), get(design, "hidden"))))
|
|
135
136
|
field.hidden = true;
|
|
136
137
|
return field;
|
|
137
138
|
}
|
|
138
139
|
/** `(f.get("sortOrder") is None, f.get("sortOrder") or 0)`. */
|
|
139
140
|
function sortOrderKey(f) {
|
|
140
141
|
const so = get(f, "sortOrder");
|
|
141
|
-
return [so === null ? 1 : 0,
|
|
142
|
+
return [so === null ? 1 : 0, pyTruthy(so) ? so : 0];
|
|
142
143
|
}
|
|
143
144
|
/**
|
|
144
145
|
* `_with_cascade`: mark each parameter that another one has to resolve before
|
|
@@ -152,7 +153,7 @@ export function withCascade(fields) {
|
|
|
152
153
|
const explicit = pyIter(pyOr(get(reference, "dependsOn"), []));
|
|
153
154
|
const own = get(field, "name");
|
|
154
155
|
const depends = sortedStrings([...names]
|
|
155
|
-
.filter((other) =>
|
|
156
|
+
.filter((other) => pyTruthy(other) && other !== own && (path.includes(`{${pyStr(other)}}`) || explicit.includes(other)))
|
|
156
157
|
.map((other) => pyStr(other)));
|
|
157
158
|
if (depends.length > 0) {
|
|
158
159
|
field.dependsOn = depends;
|
|
@@ -175,11 +176,11 @@ export function withCascade(fields) {
|
|
|
175
176
|
export function camelFields(fields) {
|
|
176
177
|
const out = [];
|
|
177
178
|
for (const f of pyIter(pyOr(fields, []))) {
|
|
178
|
-
if (!
|
|
179
|
+
if (!isPyDict(f))
|
|
179
180
|
continue;
|
|
180
181
|
const low = lowerTop(f);
|
|
181
182
|
const name = get(low, "name");
|
|
182
|
-
if (!
|
|
183
|
+
if (!pyTruthy(name))
|
|
183
184
|
continue;
|
|
184
185
|
if (isHidden(low))
|
|
185
186
|
continue;
|
|
@@ -208,11 +209,11 @@ export function camelFields(fields) {
|
|
|
208
209
|
export function normalizeBucketFields(parameters) {
|
|
209
210
|
const out = [];
|
|
210
211
|
for (const f of pyIter(pyOr(parameters, []))) {
|
|
211
|
-
if (!
|
|
212
|
+
if (!isPyDict(f))
|
|
212
213
|
continue;
|
|
213
214
|
const low = lowerTop(f);
|
|
214
215
|
const name = get(low, "name");
|
|
215
|
-
if (!
|
|
216
|
+
if (!pyTruthy(name))
|
|
216
217
|
continue;
|
|
217
218
|
if (isHidden(low))
|
|
218
219
|
continue;
|
|
@@ -252,7 +253,7 @@ export function unionEventParameters(declared, objectParameters) {
|
|
|
252
253
|
merged.set(field.name, field);
|
|
253
254
|
continue;
|
|
254
255
|
}
|
|
255
|
-
existing.required =
|
|
256
|
+
existing.required = pyTruthy(get(existing, "required")) || pyTruthy(get(field, "required"));
|
|
256
257
|
existing.bucket = field.bucket;
|
|
257
258
|
for (const k of ["displayName", "description", "reference", "sortOrder"]) {
|
|
258
259
|
if (get(field, k) !== null && get(existing, k) === null)
|
|
@@ -306,7 +307,7 @@ export function mergeDescribeIntoNode(node, describe, objectParameters = null, o
|
|
|
306
307
|
const out = { ...node };
|
|
307
308
|
out.eventParameters = { fields: unionEventParameters(get(describe, "EventParameters"), objectParameters) };
|
|
308
309
|
out.filterFields = { fields: camelFields(get(describe, "FilterFields")) };
|
|
309
|
-
if (
|
|
310
|
+
if (pyTruthy(get(describe, "OutputFields"))) {
|
|
310
311
|
out.outputResponseDefinition = { fields: camelFields(get(describe, "OutputFields")) };
|
|
311
312
|
}
|
|
312
313
|
Object.assign(out, resolveEventFacts(objectRow, describe));
|
|
@@ -322,8 +323,8 @@ export function eventFacts(node, objectRow) {
|
|
|
322
323
|
const textBlocks = pyOr(get(pyOr(get(row, "Design"), {}), "TextBlocks"), get(pyOr(get(row, "design"), {}), "textBlocks"), []);
|
|
323
324
|
return {
|
|
324
325
|
eventMode: pyStr(pyOr(stated.eventMode ?? null, "polling")),
|
|
325
|
-
byoaConnection:
|
|
326
|
-
isWebhookUrlVisible:
|
|
326
|
+
byoaConnection: pyTruthy(stated.byoaConnection ?? false),
|
|
327
|
+
isWebhookUrlVisible: pyTruthy(stated.isWebhookUrlVisible ?? false),
|
|
327
328
|
textBlocks: pyIter(textBlocks).map(textBlockText),
|
|
328
329
|
};
|
|
329
330
|
}
|
|
@@ -338,7 +339,7 @@ export function eventFacts(node, objectRow) {
|
|
|
338
339
|
* something plausible.
|
|
339
340
|
*/
|
|
340
341
|
function textBlockText(block) {
|
|
341
|
-
if (
|
|
342
|
+
if (isPyDict(block)) {
|
|
342
343
|
const text = pyOr(get(block, "Text"), get(block, "text"));
|
|
343
344
|
if (typeof text === "string")
|
|
344
345
|
return text;
|
|
@@ -432,7 +433,7 @@ export async function describeCuratedEvents(ctx, entries) {
|
|
|
432
433
|
const operation = item(entry, "operation");
|
|
433
434
|
const eventOperation = pyOr(get(operation, "eventOperation"), "");
|
|
434
435
|
const objectName = pyOr(get(operation, "objectName"), "");
|
|
435
|
-
if (!
|
|
436
|
+
if (!pyTruthy(eventOperation) || !pyTruthy(objectName))
|
|
436
437
|
return [entry, null, null];
|
|
437
438
|
const rows = await triggersObjectsCached(ctx, key, eventOperation);
|
|
438
439
|
const obj = rows.find((r) => get(r, "Name") === objectName) ?? null;
|
|
@@ -440,7 +441,7 @@ export async function describeCuratedEvents(ctx, entries) {
|
|
|
440
441
|
});
|
|
441
442
|
let described = 0;
|
|
442
443
|
for (const [entry, describe, obj] of results) {
|
|
443
|
-
if (!
|
|
444
|
+
if (!pyTruthy(describe))
|
|
444
445
|
continue;
|
|
445
446
|
applyEventSchema(entry, describe, obj);
|
|
446
447
|
described += 1;
|
|
@@ -453,7 +454,7 @@ export async function describeCuratedEvents(ctx, entries) {
|
|
|
453
454
|
* nodeType deliberately; the object rides on `operation.objectName`.
|
|
454
455
|
*/
|
|
455
456
|
export function makeExpandedEventEntry(template, obj, describe) {
|
|
456
|
-
const entry =
|
|
457
|
+
const entry = jsonClone(template);
|
|
457
458
|
const objectName = pyOr(get(obj, "Name"), "");
|
|
458
459
|
const objectDisplay = pyOr(get(obj, "DisplayName"), objectName);
|
|
459
460
|
const operation = item(entry, "operation");
|
|
@@ -477,10 +478,10 @@ export async function expandGenericEvents(ctx, templates) {
|
|
|
477
478
|
for (const template of templates) {
|
|
478
479
|
const key = item(item(template, "connector"), "key");
|
|
479
480
|
const operation = pyOr(get(item(template, "operation"), "eventOperation"), "");
|
|
480
|
-
if (!
|
|
481
|
+
if (!pyTruthy(operation))
|
|
481
482
|
continue;
|
|
482
483
|
for (const obj of await triggersObjectsCached(ctx, key, operation)) {
|
|
483
|
-
if (
|
|
484
|
+
if (pyTruthy(get(obj, "Name")))
|
|
484
485
|
work.push([template, key, operation, obj]);
|
|
485
486
|
}
|
|
486
487
|
}
|
|
@@ -508,7 +509,7 @@ export async function resolveConnectorEvents(ctx, entries) {
|
|
|
508
509
|
// UNKNOWN is not "static": expand only where the connector positively
|
|
509
510
|
// says its objects do not come from the connection.
|
|
510
511
|
const discovery = get(pyOr(get(e, "connector"), {}), "discovery");
|
|
511
|
-
if (!
|
|
512
|
+
if (!pyTruthy(discovery)) {
|
|
512
513
|
ctx.reporter.warn(` WARN: ${item(e, "nodeType")} not expanded — no connector discovery metadata, so whether its ` +
|
|
513
514
|
"objects come from the connection is unknown");
|
|
514
515
|
}
|
|
@@ -20,7 +20,8 @@ import { mapConcurrent } from "../concurrency.js";
|
|
|
20
20
|
import { messageOf } from "../errors.js";
|
|
21
21
|
import { isHttpPassthrough, normalizeFieldKeys, pick } from "./canonical.js";
|
|
22
22
|
import { isDescribeObject, isDescribeOperation, isResourcesList } from "./context.js";
|
|
23
|
-
import {
|
|
23
|
+
import { get, item, pyIter, pyOr, pyTruthy } from "./py.js";
|
|
24
|
+
import { jsonClone } from "../../core/json-file.js";
|
|
24
25
|
/**
|
|
25
26
|
* `_generic_templates_by_op`: bucket Generic CRUD entries by IS operation
|
|
26
27
|
* name per connector. Generic CRUD actions carry an `operation` verb
|
|
@@ -37,7 +38,7 @@ export function genericTemplatesByOp(genericEntries) {
|
|
|
37
38
|
if (!opName)
|
|
38
39
|
continue;
|
|
39
40
|
const key = item(item(e, "connector"), "key");
|
|
40
|
-
if (!
|
|
41
|
+
if (!pyTruthy(key))
|
|
41
42
|
continue;
|
|
42
43
|
let byOp = out.get(key);
|
|
43
44
|
if (!byOp) {
|
|
@@ -56,7 +57,7 @@ export function genericTemplatesByOp(genericEntries) {
|
|
|
56
57
|
* discovery flag is copied, never inferred from field counts.
|
|
57
58
|
*/
|
|
58
59
|
export function makeExpandedEntry(template, obj, op, opData) {
|
|
59
|
-
const entry =
|
|
60
|
+
const entry = jsonClone(template);
|
|
60
61
|
const templateOperation = item(template, "operation");
|
|
61
62
|
const objectName = pyOr(get(obj, "Name"), "");
|
|
62
63
|
const objectDisplay = pyOr(get(obj, "DisplayName"), objectName);
|
|
@@ -75,16 +76,16 @@ export function makeExpandedEntry(template, obj, op, opData) {
|
|
|
75
76
|
operation.sourceNodeType = item(template, "nodeType");
|
|
76
77
|
const display = item(entry, "display");
|
|
77
78
|
display.label = pyStrip(`${pyStr(opName)} ${pyStr(objectDisplay)}`);
|
|
78
|
-
if (
|
|
79
|
+
if (pyTruthy(opDescription))
|
|
79
80
|
display.description = opDescription;
|
|
80
81
|
// Pull schema + path template from the per-operation describe payload.
|
|
81
|
-
if (
|
|
82
|
+
if (pyTruthy(opData)) {
|
|
82
83
|
const opInner = pyOr(pick(opData, "Operation", "operation"), {});
|
|
83
84
|
const opInnerPath = pick(opInner, "Path", "path");
|
|
84
|
-
if (
|
|
85
|
+
if (pyTruthy(opInnerPath))
|
|
85
86
|
operation.pathTemplate = opInnerPath;
|
|
86
87
|
const params = pyOr(pick(opData, "Parameters", "parameters"), []);
|
|
87
|
-
if (
|
|
88
|
+
if (pyTruthy(params))
|
|
88
89
|
operation.parameters = normalizeFieldKeys(params);
|
|
89
90
|
const requestFields = pyOr(pick(opData, "RequestFields", "requestFields"), []);
|
|
90
91
|
const responseFields = pyOr(pick(opData, "ResponseFields", "responseFields"), []);
|
|
@@ -102,7 +103,7 @@ export function uniqueByName(objects) {
|
|
|
102
103
|
const unique = new Map();
|
|
103
104
|
for (const obj of pyIter(pyOr(objects, []))) {
|
|
104
105
|
const objectName = get(obj, "Name");
|
|
105
|
-
if (
|
|
106
|
+
if (pyTruthy(objectName) && !unique.has(objectName))
|
|
106
107
|
unique.set(objectName, obj);
|
|
107
108
|
}
|
|
108
109
|
return unique;
|
|
@@ -117,7 +118,7 @@ export async function listGenericObjects(ctx, connectorKey, templatesByOp, objec
|
|
|
117
118
|
const operationFilter = templatesByOp.size === 1 ? [...templatesByOp.keys()][0] : null;
|
|
118
119
|
const objects = await isResourcesList(ctx, connectorKey, { operation: operationFilter });
|
|
119
120
|
let unique = uniqueByName(objects);
|
|
120
|
-
if (
|
|
121
|
+
if (pyTruthy(objectNames) && !objectNames.every((name) => unique.has(name))) {
|
|
121
122
|
const refreshed = uniqueByName(await isResourcesList(ctx, connectorKey, { operation: operationFilter, refresh: true }));
|
|
122
123
|
// Keep the cached answer when the refresh failed outright, so a network
|
|
123
124
|
// blip does not turn a working lookup into "not found".
|
|
@@ -141,24 +142,24 @@ export async function expandGenericForConnector(ctx, connectorKey, templatesByOp
|
|
|
141
142
|
const out = [];
|
|
142
143
|
for (const obj of objects) {
|
|
143
144
|
const objectName = get(obj, "Name");
|
|
144
|
-
if (!
|
|
145
|
+
if (!pyTruthy(objectName))
|
|
145
146
|
continue;
|
|
146
147
|
if (objectNames !== undefined && !objectNames.includes(objectName))
|
|
147
148
|
continue;
|
|
148
149
|
const objData = await isDescribeObject(ctx, connectorKey, objectName);
|
|
149
|
-
if (!
|
|
150
|
+
if (!pyTruthy(objData))
|
|
150
151
|
continue;
|
|
151
152
|
const available = pyOr(pick(objData, "AvailableOperations", "availableOperations"), []);
|
|
152
153
|
for (const op of pyIter(available)) {
|
|
153
154
|
const opName = pick(op, "Name", "name");
|
|
154
|
-
if (!
|
|
155
|
+
if (!pyTruthy(opName))
|
|
155
156
|
continue;
|
|
156
157
|
const pair = JSON.stringify([objectName, opName]);
|
|
157
158
|
if (seenPairs.has(pair))
|
|
158
159
|
continue;
|
|
159
160
|
seenPairs.add(pair);
|
|
160
161
|
const template = templatesByOp.get(opName);
|
|
161
|
-
if (!
|
|
162
|
+
if (!pyTruthy(template))
|
|
162
163
|
continue;
|
|
163
164
|
const opData = await isDescribeOperation(ctx, connectorKey, objectName, opName);
|
|
164
165
|
out.push(makeExpandedEntry(template, obj, op, opData));
|
|
@@ -172,7 +173,7 @@ export async function expandGenericForConnector(ctx, connectorKey, templatesByOp
|
|
|
172
173
|
*/
|
|
173
174
|
export function objectsComeFromTheConnection(entry) {
|
|
174
175
|
const discovery = pyOr(get(pyOr(get(entry, "connector"), {}), "discovery"), {});
|
|
175
|
-
return
|
|
176
|
+
return pyTruthy(pyOr(get(discovery, "hasObjectDiscovery"), get(discovery, "hasCustomObjectDiscovery")));
|
|
176
177
|
}
|
|
177
178
|
/**
|
|
178
179
|
* `as_discovery_entry`: recast a Generic CRUD template as the discovery entry
|
|
@@ -184,7 +185,7 @@ export function asDiscoveryEntry(entry) {
|
|
|
184
185
|
operation.objectFromDiscovery = true;
|
|
185
186
|
operation.objectName = "";
|
|
186
187
|
const name = pyOr(get(operation, "name"), "");
|
|
187
|
-
if (
|
|
188
|
+
if (pyTruthy(name)) {
|
|
188
189
|
item(entry, "display").label = name === "List" ? "List Records" : `${pyStr(name)} Record`;
|
|
189
190
|
}
|
|
190
191
|
return entry;
|
|
@@ -197,7 +198,7 @@ export function asDiscoveryEntry(entry) {
|
|
|
197
198
|
export function disambiguateDiscoveryLabels(entries) {
|
|
198
199
|
const groups = new Map();
|
|
199
200
|
for (const entry of entries) {
|
|
200
|
-
if (!
|
|
201
|
+
if (!pyTruthy(get(pyOr(get(entry, "operation"), {}), "objectFromDiscovery")))
|
|
201
202
|
continue;
|
|
202
203
|
const key = JSON.stringify([
|
|
203
204
|
get(pyOr(get(entry, "connector"), {}), "key"),
|
|
@@ -314,16 +315,16 @@ export async function expandAllGenerics(ctx, entries) {
|
|
|
314
315
|
objData = undefined;
|
|
315
316
|
ctx.reporter.warn(` WARN: object describe failed for ${k}/${pyStr(item(obj, "Name"))}: ${messageOf(exc)}`);
|
|
316
317
|
}
|
|
317
|
-
if (
|
|
318
|
+
if (pyTruthy(objData)) {
|
|
318
319
|
const available = pyOr(pick(objData, "AvailableOperations", "availableOperations"), []);
|
|
319
320
|
const seenOpNames = new Set();
|
|
320
321
|
for (const op of pyIter(available)) {
|
|
321
322
|
const opName = pick(op, "Name", "name");
|
|
322
|
-
if (!
|
|
323
|
+
if (!pyTruthy(opName) || seenOpNames.has(opName))
|
|
323
324
|
continue;
|
|
324
325
|
seenOpNames.add(opName);
|
|
325
326
|
const template = templates.get(k).get(opName);
|
|
326
|
-
if (
|
|
327
|
+
if (pyTruthy(template))
|
|
327
328
|
found.push([k, obj, op, template]);
|
|
328
329
|
}
|
|
329
330
|
}
|
|
@@ -356,7 +357,7 @@ export async function expandAllGenerics(ctx, entries) {
|
|
|
356
357
|
return entry;
|
|
357
358
|
});
|
|
358
359
|
expanded.push(...results);
|
|
359
|
-
const schemaDynamic = expanded.filter((e) =>
|
|
360
|
+
const schemaDynamic = expanded.filter((e) => pyTruthy(get(item(e, "runtime"), "requiresConnectionForSchema"))).length;
|
|
360
361
|
ctx.reporter.info(` expanded ${expanded.length} entries from ${genericCrud.length} Generic CRUD actions`);
|
|
361
362
|
ctx.reporter.info(` schema-dynamic among expanded: ${schemaDynamic}/${expanded.length}`);
|
|
362
363
|
return [curatedAndPassthrough, expanded];
|
|
@@ -12,7 +12,7 @@ import { sortedByKeys } from "../compat/compare.js";
|
|
|
12
12
|
import { pyStr } from "../compat/pystr.js";
|
|
13
13
|
import { messageOf } from "../errors.js";
|
|
14
14
|
import { SCHEMA_VERSION, actionIdFromNodeType } from "./canonical.js";
|
|
15
|
-
import { get,
|
|
15
|
+
import { get, isPyDict, item, pyEquals, pyIter, pyOr, pyTruthy } from "./py.js";
|
|
16
16
|
export { actionIdFromNodeType };
|
|
17
17
|
/** `_safe_path_segment`: sanitize a string for use as one path segment. */
|
|
18
18
|
export function safePathSegment(s) {
|
|
@@ -42,7 +42,7 @@ export function entryRelpath(entry) {
|
|
|
42
42
|
return `${connectorKey}/generic/${obj}/${op}@${version}`;
|
|
43
43
|
}
|
|
44
44
|
const actionId = actionIdFromNodeType(item(entry, "nodeType"));
|
|
45
|
-
if (get(operation, "activityType") === "GenericTrigger" &&
|
|
45
|
+
if (get(operation, "activityType") === "GenericTrigger" && pyTruthy(get(operation, "objectName"))) {
|
|
46
46
|
const obj = safePathSegment(pyStr(item(operation, "objectName")));
|
|
47
47
|
return `${connectorKey}/events/${obj}/${actionId}@${version}`;
|
|
48
48
|
}
|
|
@@ -55,7 +55,7 @@ export function entryRelpath(entry) {
|
|
|
55
55
|
* connector definition when absent (`setdefault`: a present `null` stays).
|
|
56
56
|
*/
|
|
57
57
|
export function buildV1Definition(nodeData) {
|
|
58
|
-
if (!
|
|
58
|
+
if (!isPyDict(nodeData))
|
|
59
59
|
throw new TypeError(`'${nodeData === null ? "NoneType" : typeof nodeData}' object has no attribute 'items'`);
|
|
60
60
|
const keep = {};
|
|
61
61
|
for (const [k, v] of Object.entries(nodeData)) {
|
|
@@ -150,18 +150,18 @@ export function planLibrary(entries, loadNode, opts = {}) {
|
|
|
150
150
|
let v1def;
|
|
151
151
|
try {
|
|
152
152
|
const nodeData = typeof lookupKey === "string" ? loadNode(lookupKey) : undefined;
|
|
153
|
-
if (
|
|
153
|
+
if (pyTruthy(nodeData)) {
|
|
154
154
|
v1def = buildV1Definition(nodeData);
|
|
155
155
|
// The entry's fields are the node's own plus whatever the parameters
|
|
156
156
|
// filled in — a superset of a verbatim projection — so prefer them
|
|
157
157
|
// whenever there are any. An expanded Generic node stamps even an
|
|
158
158
|
// empty list: its template's fields belong to the dispatch node.
|
|
159
159
|
const resolvedFields = pyOr(get(get(entry, "inputSchema", {}), "fields"), []);
|
|
160
|
-
if (expanded ||
|
|
160
|
+
if (expanded || pyTruthy(resolvedFields)) {
|
|
161
161
|
if (!("inputDefinition" in v1def))
|
|
162
162
|
v1def.inputDefinition = {};
|
|
163
163
|
const inputDefinition = v1def.inputDefinition;
|
|
164
|
-
if (!
|
|
164
|
+
if (!isPyDict(inputDefinition)) {
|
|
165
165
|
throw new TypeError(`'${inputDefinition === null ? "NoneType" : typeof inputDefinition}' object does not support item assignment`);
|
|
166
166
|
}
|
|
167
167
|
// Stamp onto a COPY. `buildV1Definition` is shallow, so this block is
|
|
@@ -177,7 +177,7 @@ export function planLibrary(entries, loadNode, opts = {}) {
|
|
|
177
177
|
// A per-object EVENT variant carries its own subscription vocabulary,
|
|
178
178
|
// and this sidecar is where the SDK reads one.
|
|
179
179
|
const eventSchema = pyOr(get(entry, "eventSchema"), {});
|
|
180
|
-
if (
|
|
180
|
+
if (pyTruthy(eventSchema)) {
|
|
181
181
|
v1def.eventParameters = { fields: pyOr(get(eventSchema, "eventParameters"), []) };
|
|
182
182
|
v1def.filterFields = { fields: pyOr(get(eventSchema, "filterFields"), []) };
|
|
183
183
|
for (const fact of ["eventMode", "byoaConnection", "isWebhookUrlVisible"]) {
|
|
@@ -207,7 +207,7 @@ export function planLibrary(entries, loadNode, opts = {}) {
|
|
|
207
207
|
};
|
|
208
208
|
// `registry search` reads only this index; emitted only when set, so every
|
|
209
209
|
// other row stays byte-identical.
|
|
210
|
-
if (
|
|
210
|
+
if (pyTruthy(get(operation, "objectFromDiscovery")))
|
|
211
211
|
row.objectFromDiscovery = true;
|
|
212
212
|
index.push(row);
|
|
213
213
|
}
|
|
@@ -8,13 +8,10 @@
|
|
|
8
8
|
* to `dict.get`, and two dicts compare equal in any key order. The port spells
|
|
9
9
|
* each rule once here so every call site can read like the line it replaces.
|
|
10
10
|
*/
|
|
11
|
+
export { pyTruthy, pyOr } from "../compat/pyvalue.js";
|
|
11
12
|
export type Dict = Record<string, unknown>;
|
|
12
|
-
/** A plain JSON object: what `json.load` calls a dict. */
|
|
13
|
-
export declare function
|
|
14
|
-
/** `bool(value)` for a JSON-shaped value. */
|
|
15
|
-
export declare function truthy(value: unknown): boolean;
|
|
16
|
-
/** `a or b or …`: the first truthy operand, else the last one. */
|
|
17
|
-
export declare function pyOr<T>(...values: T[]): T;
|
|
13
|
+
/** A plain JSON object: what `json.load` calls a dict (Map excluded — unlike core/node-envelope.ts's isRecord). */
|
|
14
|
+
export declare function isPyDict(value: unknown): value is Dict;
|
|
18
15
|
/**
|
|
19
16
|
* `d.get(key, default)`. A present key answers its value even when that value
|
|
20
17
|
* is `null`; an absent key answers `dflt`, which is `null` (Python's `None`)
|
|
@@ -33,10 +30,6 @@ export declare function pyIter(value: unknown): unknown[];
|
|
|
33
30
|
* order; lists element by element; `True == 1` as in Python.
|
|
34
31
|
*/
|
|
35
32
|
export declare function pyEquals(a: unknown, b: unknown): boolean;
|
|
36
|
-
/** `json.loads(json.dumps(value))`: the generator's deep copy, JSON-shaped values only. */
|
|
37
|
-
export declare function deepCopy<T>(value: T): T;
|
|
38
|
-
/** `repr(str)`: single quotes unless the text holds one and no double quote. */
|
|
39
|
-
export declare function pyReprStr(s: string): string;
|
|
40
33
|
/** `repr(list_of_str)`: `['a', 'b']`. */
|
|
41
34
|
export declare function pyReprStrList(items: readonly string[]): string;
|
|
42
35
|
/** `s[start:end]` by code point. */
|
|
@@ -8,31 +8,12 @@
|
|
|
8
8
|
* to `dict.get`, and two dicts compare equal in any key order. The port spells
|
|
9
9
|
* each rule once here so every call site can read like the line it replaces.
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
export
|
|
11
|
+
import { pyReprString } from "../compat/pystr.js";
|
|
12
|
+
export { pyTruthy, pyOr } from "../compat/pyvalue.js";
|
|
13
|
+
/** A plain JSON object: what `json.load` calls a dict (Map excluded — unlike core/node-envelope.ts's isRecord). */
|
|
14
|
+
export function isPyDict(value) {
|
|
13
15
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Map);
|
|
14
16
|
}
|
|
15
|
-
/** `bool(value)` for a JSON-shaped value. */
|
|
16
|
-
export function truthy(value) {
|
|
17
|
-
if (value === null || value === undefined || value === false || value === "")
|
|
18
|
-
return false;
|
|
19
|
-
if (typeof value === "number")
|
|
20
|
-
return value !== 0;
|
|
21
|
-
if (Array.isArray(value))
|
|
22
|
-
return value.length > 0;
|
|
23
|
-
if (value instanceof Map)
|
|
24
|
-
return value.size > 0;
|
|
25
|
-
if (typeof value === "object")
|
|
26
|
-
return Object.keys(value).length > 0;
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
/** `a or b or …`: the first truthy operand, else the last one. */
|
|
30
|
-
export function pyOr(...values) {
|
|
31
|
-
for (const value of values)
|
|
32
|
-
if (truthy(value))
|
|
33
|
-
return value;
|
|
34
|
-
return values[values.length - 1];
|
|
35
|
-
}
|
|
36
17
|
/**
|
|
37
18
|
* Does the dict itself hold `key`? — `key in d` for a Python dict.
|
|
38
19
|
*
|
|
@@ -58,7 +39,7 @@ function hasKey(d, key) {
|
|
|
58
39
|
* Calling `.get` on a non-dict raises in Python, and does here.
|
|
59
40
|
*/
|
|
60
41
|
export function get(d, key, dflt = null) {
|
|
61
|
-
if (!
|
|
42
|
+
if (!isPyDict(d)) {
|
|
62
43
|
throw new TypeError(`'${pyTypeName(d)}' object has no attribute 'get'`);
|
|
63
44
|
}
|
|
64
45
|
if (!hasKey(d, key))
|
|
@@ -68,7 +49,7 @@ export function get(d, key, dflt = null) {
|
|
|
68
49
|
}
|
|
69
50
|
/** `d[key]`: raises on a missing key the way Python's `KeyError` does. */
|
|
70
51
|
export function item(d, key) {
|
|
71
|
-
if (!
|
|
52
|
+
if (!isPyDict(d)) {
|
|
72
53
|
throw new TypeError(`'${pyTypeName(d)}' object is not subscriptable`);
|
|
73
54
|
}
|
|
74
55
|
if (!hasKey(d, key) || d[key] === undefined)
|
|
@@ -83,7 +64,7 @@ export function pyIter(value) {
|
|
|
83
64
|
return [...value];
|
|
84
65
|
if (value instanceof Map)
|
|
85
66
|
return [...value.keys()];
|
|
86
|
-
if (
|
|
67
|
+
if (isPyDict(value))
|
|
87
68
|
return Object.keys(value);
|
|
88
69
|
throw new TypeError(`'${pyTypeName(value)}' object is not iterable`);
|
|
89
70
|
}
|
|
@@ -116,7 +97,7 @@ export function pyEquals(a, b) {
|
|
|
116
97
|
return false;
|
|
117
98
|
return a.every((item, i) => pyEquals(item, b[i]));
|
|
118
99
|
}
|
|
119
|
-
if (
|
|
100
|
+
if (isPyDict(a) && isPyDict(b)) {
|
|
120
101
|
const keysA = Object.keys(a).filter((k) => a[k] !== undefined);
|
|
121
102
|
const keysB = Object.keys(b).filter((k) => b[k] !== undefined);
|
|
122
103
|
if (keysA.length !== keysB.length)
|
|
@@ -128,33 +109,9 @@ export function pyEquals(a, b) {
|
|
|
128
109
|
}
|
|
129
110
|
return false;
|
|
130
111
|
}
|
|
131
|
-
/** `json.loads(json.dumps(value))`: the generator's deep copy, JSON-shaped values only. */
|
|
132
|
-
export function deepCopy(value) {
|
|
133
|
-
return JSON.parse(JSON.stringify(value));
|
|
134
|
-
}
|
|
135
|
-
/** `repr(str)`: single quotes unless the text holds one and no double quote. */
|
|
136
|
-
export function pyReprStr(s) {
|
|
137
|
-
const quote = s.includes("'") && !s.includes('"') ? '"' : "'";
|
|
138
|
-
let out = quote;
|
|
139
|
-
for (const ch of s) {
|
|
140
|
-
if (ch === "\\")
|
|
141
|
-
out += "\\\\";
|
|
142
|
-
else if (ch === quote)
|
|
143
|
-
out += `\\${quote}`;
|
|
144
|
-
else if (ch === "\n")
|
|
145
|
-
out += "\\n";
|
|
146
|
-
else if (ch === "\r")
|
|
147
|
-
out += "\\r";
|
|
148
|
-
else if (ch === "\t")
|
|
149
|
-
out += "\\t";
|
|
150
|
-
else
|
|
151
|
-
out += ch;
|
|
152
|
-
}
|
|
153
|
-
return out + quote;
|
|
154
|
-
}
|
|
155
112
|
/** `repr(list_of_str)`: `['a', 'b']`. */
|
|
156
113
|
export function pyReprStrList(items) {
|
|
157
|
-
return `[${items.map(
|
|
114
|
+
return `[${items.map(pyReprString).join(", ")}]`;
|
|
158
115
|
}
|
|
159
116
|
/** `s[start:end]` by code point. */
|
|
160
117
|
export function codePointSlice(s, start, end) {
|