@uipath/maestro-builder-sdk 6.3.0 → 6.5.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 +6 -6
- package/dist/cli/commands/registry.js +68 -12
- package/dist/registry/archive/entry-policy.d.ts +36 -0
- package/dist/registry/archive/entry-policy.js +60 -0
- package/dist/registry/archive/extract.d.ts +31 -0
- package/dist/registry/archive/extract.js +92 -0
- package/dist/registry/archive/zip-reader.d.ts +38 -0
- package/dist/registry/archive/zip-reader.js +256 -0
- package/dist/registry/codecs/pyjson.d.ts +62 -0
- package/dist/registry/codecs/pyjson.js +208 -0
- package/dist/registry/compat/compare.d.ts +18 -0
- package/dist/registry/compat/compare.js +32 -0
- package/dist/registry/compat/paths.d.ts +4 -0
- package/dist/registry/compat/paths.js +17 -0
- package/dist/registry/compat/pystr.d.ts +50 -0
- package/dist/registry/compat/pystr.js +221 -0
- package/dist/registry/concurrency.d.ts +10 -0
- package/dist/registry/concurrency.js +34 -0
- package/dist/registry/curated-overlay.d.ts +36 -4
- package/dist/registry/curated-overlay.js +87 -14
- package/dist/registry/errors.d.ts +26 -0
- package/dist/registry/errors.js +38 -0
- package/dist/registry/extractor.d.ts +9 -10
- package/dist/registry/extractor.js +5 -15
- package/dist/registry/files.d.ts +26 -0
- package/dist/registry/files.js +160 -0
- package/dist/registry/generators/descriptors.d.ts +81 -0
- package/dist/registry/generators/descriptors.js +405 -0
- package/dist/registry/generators/fields.d.ts +46 -0
- package/dist/registry/generators/fields.js +111 -0
- package/dist/registry/generators/markdown/common.d.ts +21 -0
- package/dist/registry/generators/markdown/common.js +55 -0
- package/dist/registry/generators/markdown/index.d.ts +33 -0
- package/dist/registry/generators/markdown/index.js +37 -0
- package/dist/registry/generators/markdown/page.d.ts +8 -0
- package/dist/registry/generators/markdown/page.js +52 -0
- package/dist/registry/generators/markdown/pyvalue.d.ts +34 -0
- package/dist/registry/generators/markdown/pyvalue.js +106 -0
- package/dist/registry/generators/markdown/sections/choosing-object.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/choosing-object.js +52 -0
- package/dist/registry/generators/markdown/sections/example.d.ts +22 -0
- package/dist/registry/generators/markdown/sections/example.js +295 -0
- package/dist/registry/generators/markdown/sections/filtering.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/filtering.js +76 -0
- package/dist/registry/generators/markdown/sections/header.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/header.js +37 -0
- package/dist/registry/generators/markdown/sections/inputs.d.ts +30 -0
- package/dist/registry/generators/markdown/sections/inputs.js +151 -0
- package/dist/registry/generators/markdown/sections/lookups.d.ts +12 -0
- package/dist/registry/generators/markdown/sections/lookups.js +150 -0
- package/dist/registry/generators/markdown/sections/outputs.d.ts +3 -0
- package/dist/registry/generators/markdown/sections/outputs.js +9 -0
- package/dist/registry/generators/markdown/sections/resolving-fields.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/resolving-fields.js +139 -0
- package/dist/registry/generators/markdown/sections/schema-dynamic.d.ts +2 -0
- package/dist/registry/generators/markdown/sections/schema-dynamic.js +86 -0
- package/dist/registry/generators/materialize.d.ts +30 -0
- package/dist/registry/generators/materialize.js +132 -0
- package/dist/registry/generators/md-symbols.d.ts +22 -0
- package/dist/registry/generators/md-symbols.js +58 -0
- package/dist/registry/generators/triggers.d.ts +15 -0
- package/dist/registry/generators/triggers.js +85 -0
- package/dist/registry/generators/type-registry.d.ts +50 -0
- package/dist/registry/generators/type-registry.js +154 -0
- package/dist/registry/generators/type-tree.d.ts +65 -0
- package/dist/registry/generators/type-tree.js +179 -0
- package/dist/registry/library-build/build.d.ts +57 -0
- package/dist/registry/library-build/build.js +286 -0
- package/dist/registry/library-build/canonical.d.ts +119 -0
- package/dist/registry/library-build/canonical.js +512 -0
- package/dist/registry/library-build/cli.d.ts +45 -0
- package/dist/registry/library-build/cli.js +182 -0
- package/dist/registry/library-build/context.d.ts +107 -0
- package/dist/registry/library-build/context.js +267 -0
- package/dist/registry/library-build/enrich.d.ts +36 -0
- package/dist/registry/library-build/enrich.js +158 -0
- package/dist/registry/library-build/events.d.ts +108 -0
- package/dist/registry/library-build/events.js +476 -0
- package/dist/registry/library-build/generic-expand.d.ts +77 -0
- package/dist/registry/library-build/generic-expand.js +363 -0
- package/dist/registry/library-build/plan.d.ts +71 -0
- package/dist/registry/library-build/plan.js +270 -0
- package/dist/registry/library-build/py.d.ts +45 -0
- package/dist/registry/library-build/py.js +167 -0
- package/dist/registry/library-build/schema-action.d.ts +38 -0
- package/dist/registry/library-build/schema-action.js +107 -0
- package/dist/registry/library-build/write.d.ts +6 -0
- package/dist/registry/library-build/write.js +26 -0
- package/dist/registry/library-reader.d.ts +20 -0
- package/dist/registry/library-reader.js +50 -0
- package/dist/registry/lookup-specs.d.ts +55 -0
- package/dist/registry/lookup-specs.js +140 -0
- package/dist/registry/materialize-cli.js +28 -14
- package/dist/registry/materializer.d.ts +25 -3
- package/dist/registry/materializer.js +45 -29
- package/dist/registry/model/library.d.ts +254 -0
- package/dist/registry/model/library.js +23 -0
- package/dist/registry/naming.d.ts +54 -0
- package/dist/registry/naming.js +142 -0
- package/dist/registry/orphans.d.ts +4 -0
- package/dist/registry/orphans.js +86 -0
- package/dist/registry/prepare/actions.d.ts +26 -0
- package/dist/registry/prepare/actions.js +94 -0
- package/dist/registry/prepare/connections.d.ts +181 -0
- package/dist/registry/prepare/connections.js +399 -0
- package/dist/registry/prepare/event.d.ts +96 -0
- package/dist/registry/prepare/event.js +268 -0
- package/dist/registry/prepare/handoff.d.ts +21 -0
- package/dist/registry/prepare/handoff.js +91 -0
- package/dist/registry/prepare/ledger.d.ts +104 -0
- package/dist/registry/prepare/ledger.js +216 -0
- package/dist/registry/prepare/messages.d.ts +190 -0
- package/dist/registry/prepare/messages.js +472 -0
- package/dist/registry/prepare/objects.d.ts +130 -0
- package/dist/registry/prepare/objects.js +318 -0
- package/dist/registry/prepare/overlay.d.ts +149 -0
- package/dist/registry/prepare/overlay.js +487 -0
- package/dist/registry/prepare/parents.d.ts +41 -0
- package/dist/registry/prepare/parents.js +122 -0
- package/dist/registry/prepare/pipeline.d.ts +91 -0
- package/dist/registry/prepare/pipeline.js +376 -0
- package/dist/registry/prepare/regenerate.d.ts +9 -0
- package/dist/registry/prepare/regenerate.js +50 -0
- package/dist/registry/prepare/resolve.d.ts +199 -0
- package/dist/registry/prepare/resolve.js +470 -0
- package/dist/registry/prepare/retry.d.ts +83 -0
- package/dist/registry/prepare/retry.js +109 -0
- package/dist/registry/prepare/source-import.d.ts +54 -0
- package/dist/registry/prepare/source-import.js +192 -0
- package/dist/registry/prepare/support.d.ts +41 -0
- package/dist/registry/prepare/support.js +178 -0
- package/dist/registry/prepare-cli.d.ts +16 -5
- package/dist/registry/prepare-cli.js +60 -53
- package/dist/registry/pull-cli.js +8 -7
- package/dist/registry/reporter.d.ts +26 -0
- package/dist/registry/reporter.js +41 -0
- package/dist/registry/uip/client.d.ts +99 -0
- package/dist/registry/uip/client.js +226 -0
- package/dist/registry/uip/commands.d.ts +40 -0
- package/dist/registry/uip/commands.js +80 -0
- package/dist/registry/uip/envelope.d.ts +37 -0
- package/dist/registry/uip/envelope.js +84 -0
- package/dist/registry/uip/response-cache.d.ts +28 -0
- package/dist/registry/uip/response-cache.js +57 -0
- package/lib/README.md +10 -8
- package/package.json +3 -3
- package/dist/generators/_connections.py +0 -331
- package/dist/generators/_events.py +0 -355
- package/dist/generators/_lookups.py +0 -114
- package/dist/generators/_naming.py +0 -185
- package/dist/generators/_objects.py +0 -299
- package/dist/generators/_resolve.py +0 -319
- package/dist/generators/convert_library_to_md.py +0 -1441
- package/dist/generators/extract_library.py +0 -125
- package/dist/generators/generate_connectors_ts.py +0 -936
- package/dist/generators/generate_library_json.py +0 -2527
- package/dist/generators/generate_triggers_ts.py +0 -110
- package/dist/generators/materialize_registry.py +0 -155
- package/dist/generators/prepare_connector.py +0 -1860
- package/dist/registry/python.d.ts +0 -26
- package/dist/registry/python.js +0 -46
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connector EVENTS — triggers and mid-flow waits — from `_events.py` and the
|
|
3
|
+
* event half of `generate_library_json.py`.
|
|
4
|
+
*
|
|
5
|
+
* Events have their own twin pair of CLI calls: `uip is triggers objects`
|
|
6
|
+
* answers PER EVENT OBJECT (the catalog a generic event ranges over, and per
|
|
7
|
+
* object the `Parameters` it contributes), `uip is triggers describe` answers
|
|
8
|
+
* PER (event, object) (`EventParameters`, `FilterFields`, `OutputFields`).
|
|
9
|
+
* The complete input set is the UNION of the two, required if EITHER side says
|
|
10
|
+
* required — a field can appear in one and not the other, so neither call alone
|
|
11
|
+
* is trusted.
|
|
12
|
+
*
|
|
13
|
+
* Key order in every field record is the Python's dict order, because these
|
|
14
|
+
* fields are written into the library and the sidecar with `json.dump`.
|
|
15
|
+
*/
|
|
16
|
+
import { sortedStrings } from "../compat/compare.js";
|
|
17
|
+
import { lowerFirst, pyStr, pyStrip, startsWithUpper } from "../compat/pystr.js";
|
|
18
|
+
import { mapConcurrent } from "../concurrency.js";
|
|
19
|
+
import { Refusal } from "../errors.js";
|
|
20
|
+
import { isTriggersDescribe, isTriggersObjects } from "../uip/commands.js";
|
|
21
|
+
import { safeCacheName } from "../uip/response-cache.js";
|
|
22
|
+
import { schemaCacheScope, writeCache } from "./context.js";
|
|
23
|
+
import { objectsComeFromTheConnection } from "./generic-expand.js";
|
|
24
|
+
import { codePointSlice, deepCopy, get, isRecord, item, pyIter, pyOr, truthy } from "./py.js";
|
|
25
|
+
/**
|
|
26
|
+
* `Parameters[].type` on a `triggers objects` row routes the value into one of
|
|
27
|
+
* three `--detail` buckets at emit time. Everything unrecognized is an event
|
|
28
|
+
* parameter — the designer's own fallback. A `Map`, not an object, because
|
|
29
|
+
* `_BUCKETS.get(…)` on a dict answers `None` for `"constructor"` where a plain
|
|
30
|
+
* object would answer `Object`'s constructor function.
|
|
31
|
+
*/
|
|
32
|
+
const BUCKETS = new Map([
|
|
33
|
+
["query", "query"],
|
|
34
|
+
["path", "path"],
|
|
35
|
+
]);
|
|
36
|
+
/** A trigger metadata call failed, with the reason and the way forward. */
|
|
37
|
+
export class EventDiscoveryError extends Refusal {
|
|
38
|
+
refused;
|
|
39
|
+
constructor(message,
|
|
40
|
+
/**
|
|
41
|
+
* True when the call was never made — the client refused it (`--offline`).
|
|
42
|
+
* Such a failure is not an answer from the tenant and must not be cached
|
|
43
|
+
* as one; see {@link triggersObjectsCached}.
|
|
44
|
+
*/
|
|
45
|
+
refused = false) {
|
|
46
|
+
super(message, "EVENT_DISCOVERY");
|
|
47
|
+
this.refused = refused;
|
|
48
|
+
this.name = "EventDiscoveryError";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** `str(value)` for a message, tolerating the shapes `pyStr` refuses. */
|
|
52
|
+
function pyFormat(value) {
|
|
53
|
+
try {
|
|
54
|
+
return pyStr(value);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** `_run_json`: the whole of stdout as one `Result: Success` envelope, or an {@link EventDiscoveryError}. */
|
|
61
|
+
async function runJson(client, argv, timeoutMs) {
|
|
62
|
+
const result = await client.run(argv, timeoutMs === undefined ? undefined : { timeoutMs });
|
|
63
|
+
const head = `\`${argv.slice(0, 5).join(" ")} …\``;
|
|
64
|
+
let payload;
|
|
65
|
+
try {
|
|
66
|
+
payload = JSON.parse(result.stdout);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
const text = codePointSlice(pyStrip(result.stderr || result.stdout), 0, 300);
|
|
70
|
+
throw new EventDiscoveryError(`${head} did not answer JSON: ${text || "no output"}`, result.refused === true);
|
|
71
|
+
}
|
|
72
|
+
if (get(payload, "Result") !== "Success") {
|
|
73
|
+
// `refused` is carried here too, although the refusing client in this tree
|
|
74
|
+
// answers with empty stdout and so always fails the parse above. The rule
|
|
75
|
+
// the cache depends on is "a call that never ran is never remembered", and
|
|
76
|
+
// that rule should not rest on what a client happens to put on stdout.
|
|
77
|
+
throw new EventDiscoveryError(`${head} failed: ${pyFormat(pyOr(get(payload, "Message"), get(payload, "Result")))}`, result.refused === true);
|
|
78
|
+
}
|
|
79
|
+
return payload;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* `triggers_objects`: the event objects for `(connector, operation)`, PascalCase
|
|
83
|
+
* rows. `connectionId` is optional; a connector whose objects do not come from
|
|
84
|
+
* the connection answers identically either way.
|
|
85
|
+
*/
|
|
86
|
+
export async function triggersObjects(client, key, operation, connectionId, timeoutMs) {
|
|
87
|
+
const payload = await runJson(client, isTriggersObjects(key, operation, connectionId), timeoutMs);
|
|
88
|
+
let data = get(payload, "Data");
|
|
89
|
+
if (isRecord(data))
|
|
90
|
+
data = get(data, "items");
|
|
91
|
+
return Array.isArray(data) ? data : [];
|
|
92
|
+
}
|
|
93
|
+
/** `triggers_describe`: `EventParameters` / `FilterFields` / `OutputFields` for one object. */
|
|
94
|
+
export async function triggersDescribe(client, key, operation, objectName, connectionId, timeoutMs) {
|
|
95
|
+
const payload = await runJson(client, isTriggersDescribe(key, operation, objectName, connectionId), timeoutMs);
|
|
96
|
+
return pyOr(get(payload, "Data"), {});
|
|
97
|
+
}
|
|
98
|
+
/** `k[0].lower() + k[1:] if k[:1].isupper() else k`. */
|
|
99
|
+
function lowerKey(k) {
|
|
100
|
+
return startsWithUpper(k) ? lowerFirst(k) : k;
|
|
101
|
+
}
|
|
102
|
+
/** `_camelize`: lower-case the first letter of every key, recursively. */
|
|
103
|
+
export function camelize(value) {
|
|
104
|
+
if (isRecord(value)) {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const [k, v] of Object.entries(value))
|
|
107
|
+
out[lowerKey(k)] = camelize(v);
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
if (Array.isArray(value))
|
|
111
|
+
return value.map(camelize);
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
/** The top-level-only key lowering `camel_fields` and `normalize_bucket_fields` do inline. */
|
|
115
|
+
function lowerTop(f) {
|
|
116
|
+
const low = {};
|
|
117
|
+
for (const [k, v] of Object.entries(f))
|
|
118
|
+
low[lowerKey(k)] = v;
|
|
119
|
+
return low;
|
|
120
|
+
}
|
|
121
|
+
/** `_is_hidden`: a hidden parameter that is safe to DROP — hidden and optional. */
|
|
122
|
+
export function isHidden(low) {
|
|
123
|
+
const raw = pyOr(get(low, "design"), {});
|
|
124
|
+
if (!isRecord(raw))
|
|
125
|
+
return false;
|
|
126
|
+
const design = camelize(raw);
|
|
127
|
+
const hidden = truthy(pyOr(get(design, "isHidden"), get(design, "hidden")));
|
|
128
|
+
return hidden && !truthy(get(low, "required"));
|
|
129
|
+
}
|
|
130
|
+
/** `_keep_hidden_marker`: flag a kept parameter the designer hides (necessarily a required one). */
|
|
131
|
+
export function keepHiddenMarker(low, field) {
|
|
132
|
+
const design = camelize(pyOr(get(low, "design"), {}));
|
|
133
|
+
if (isRecord(design) && truthy(pyOr(get(design, "isHidden"), get(design, "hidden"))))
|
|
134
|
+
field.hidden = true;
|
|
135
|
+
return field;
|
|
136
|
+
}
|
|
137
|
+
/** `(f.get("sortOrder") is None, f.get("sortOrder") or 0)`. */
|
|
138
|
+
function sortOrderKey(f) {
|
|
139
|
+
const so = get(f, "sortOrder");
|
|
140
|
+
return [so === null ? 1 : 0, truthy(so) ? so : 0];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* `_with_cascade`: mark each parameter that another one has to resolve before
|
|
144
|
+
* it, then order by the designer's `sortOrder` (unset last), stably.
|
|
145
|
+
*/
|
|
146
|
+
export function withCascade(fields) {
|
|
147
|
+
const names = new Set(fields.map((f) => get(f, "name")));
|
|
148
|
+
for (const field of fields) {
|
|
149
|
+
const reference = pyOr(get(field, "reference"), {});
|
|
150
|
+
const path = pyStr(pyOr(get(reference, "path"), ""));
|
|
151
|
+
const explicit = pyIter(pyOr(get(reference, "dependsOn"), []));
|
|
152
|
+
const own = get(field, "name");
|
|
153
|
+
const depends = sortedStrings([...names]
|
|
154
|
+
.filter((other) => truthy(other) && other !== own && (path.includes(`{${pyStr(other)}}`) || explicit.includes(other)))
|
|
155
|
+
.map((other) => pyStr(other)));
|
|
156
|
+
if (depends.length > 0) {
|
|
157
|
+
field.dependsOn = depends;
|
|
158
|
+
if (!("dependsOn" in reference))
|
|
159
|
+
reference.dependsOn = depends;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
fields.sort((a, b) => {
|
|
163
|
+
const ka = sortOrderKey(a);
|
|
164
|
+
const kb = sortOrderKey(b);
|
|
165
|
+
if (ka[0] !== kb[0])
|
|
166
|
+
return ka[0] - kb[0];
|
|
167
|
+
const x = ka[1];
|
|
168
|
+
const y = kb[1];
|
|
169
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
170
|
+
});
|
|
171
|
+
return fields;
|
|
172
|
+
}
|
|
173
|
+
/** `camel_fields`: describe/objects fields normalized to the registry's camelCase shape. */
|
|
174
|
+
export function camelFields(fields) {
|
|
175
|
+
const out = [];
|
|
176
|
+
for (const f of pyIter(pyOr(fields, []))) {
|
|
177
|
+
if (!isRecord(f))
|
|
178
|
+
continue;
|
|
179
|
+
const low = lowerTop(f);
|
|
180
|
+
const name = get(low, "name");
|
|
181
|
+
if (!truthy(name))
|
|
182
|
+
continue;
|
|
183
|
+
if (isHidden(low))
|
|
184
|
+
continue;
|
|
185
|
+
const field = { name };
|
|
186
|
+
for (const k of ["displayName", "description", "required", "dataType", "sortOrder"]) {
|
|
187
|
+
if (get(low, k) !== null)
|
|
188
|
+
field[k] = low[k];
|
|
189
|
+
}
|
|
190
|
+
if (get(low, "reference") !== null)
|
|
191
|
+
field.reference = camelize(low.reference);
|
|
192
|
+
keepHiddenMarker(low, field);
|
|
193
|
+
// Describe's `Type` is the DATA type; an objects-row Parameter's `type` is
|
|
194
|
+
// the BUCKET — those go through `normalizeBucketFields` instead.
|
|
195
|
+
if (get(low, "type") !== null && !("dataType" in field)) {
|
|
196
|
+
field.type = low.type;
|
|
197
|
+
field.dataType = low.type;
|
|
198
|
+
}
|
|
199
|
+
else if ("dataType" in field) {
|
|
200
|
+
field.type = field.dataType;
|
|
201
|
+
}
|
|
202
|
+
out.push(field);
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
/** `normalize_bucket_fields`: `triggers objects → Parameters[]` rows, camelCase, bucket made explicit. */
|
|
207
|
+
export function normalizeBucketFields(parameters) {
|
|
208
|
+
const out = [];
|
|
209
|
+
for (const f of pyIter(pyOr(parameters, []))) {
|
|
210
|
+
if (!isRecord(f))
|
|
211
|
+
continue;
|
|
212
|
+
const low = lowerTop(f);
|
|
213
|
+
const name = get(low, "name");
|
|
214
|
+
if (!truthy(name))
|
|
215
|
+
continue;
|
|
216
|
+
if (isHidden(low))
|
|
217
|
+
continue;
|
|
218
|
+
const field = {
|
|
219
|
+
name,
|
|
220
|
+
bucket: BUCKETS.get(pyStr(pyOr(get(low, "type"), "")).toLowerCase()) ?? "event",
|
|
221
|
+
};
|
|
222
|
+
for (const k of ["displayName", "description", "required", "sortOrder"]) {
|
|
223
|
+
if (get(low, k) !== null)
|
|
224
|
+
field[k] = low[k];
|
|
225
|
+
}
|
|
226
|
+
if (get(low, "reference") !== null)
|
|
227
|
+
field.reference = camelize(low.reference);
|
|
228
|
+
const dataType = pyOr(get(low, "dataType"), "string");
|
|
229
|
+
field.type = dataType;
|
|
230
|
+
field.dataType = dataType;
|
|
231
|
+
keepHiddenMarker(low, field);
|
|
232
|
+
out.push(field);
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* `union_event_parameters`: describe's EventParameters ∪ the object row's
|
|
238
|
+
* Parameters, deduped by name; `required` is an OR; the object row's bucket
|
|
239
|
+
* survives the merge.
|
|
240
|
+
*/
|
|
241
|
+
export function unionEventParameters(declared, objectParameters) {
|
|
242
|
+
const merged = new Map();
|
|
243
|
+
for (const field of camelFields(declared)) {
|
|
244
|
+
if (!("bucket" in field))
|
|
245
|
+
field.bucket = "event";
|
|
246
|
+
merged.set(field.name, field);
|
|
247
|
+
}
|
|
248
|
+
for (const field of normalizeBucketFields(objectParameters)) {
|
|
249
|
+
const existing = merged.get(field.name);
|
|
250
|
+
if (existing === undefined) {
|
|
251
|
+
merged.set(field.name, field);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
existing.required = truthy(get(existing, "required")) || truthy(get(field, "required"));
|
|
255
|
+
existing.bucket = field.bucket;
|
|
256
|
+
for (const k of ["displayName", "description", "reference", "sortOrder"]) {
|
|
257
|
+
if (get(field, k) !== null && get(existing, k) === null)
|
|
258
|
+
existing[k] = field[k];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return withCascade([...merged.values()]);
|
|
262
|
+
}
|
|
263
|
+
const EVENT_FACTS = [
|
|
264
|
+
["EventMode", "eventMode"],
|
|
265
|
+
["ByoaConnection", "byoaConnection"],
|
|
266
|
+
["IsWebhookUrlVisible", "isWebhookUrlVisible"],
|
|
267
|
+
];
|
|
268
|
+
/** `merge_describe_into_node`: fold a `triggers describe` answer into a registry trigger definition (a copy). */
|
|
269
|
+
export function mergeDescribeIntoNode(node, describe, objectParameters = null) {
|
|
270
|
+
const out = { ...node };
|
|
271
|
+
out.eventParameters = { fields: unionEventParameters(get(describe, "EventParameters"), objectParameters) };
|
|
272
|
+
out.filterFields = { fields: camelFields(get(describe, "FilterFields")) };
|
|
273
|
+
if (truthy(get(describe, "OutputFields"))) {
|
|
274
|
+
out.outputResponseDefinition = { fields: camelFields(get(describe, "OutputFields")) };
|
|
275
|
+
}
|
|
276
|
+
for (const [src, dst] of EVENT_FACTS)
|
|
277
|
+
if (get(describe, src) !== null)
|
|
278
|
+
out[dst] = describe[src];
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
/** `event_facts`: the three facts that change how a subscription is prepared or tested; the object row wins. */
|
|
282
|
+
export function eventFacts(node, objectRow) {
|
|
283
|
+
const row = pyOr(objectRow, {});
|
|
284
|
+
const fact = (rowKey, nodeKey, dflt) => {
|
|
285
|
+
if (get(row, rowKey) !== null)
|
|
286
|
+
return row[rowKey];
|
|
287
|
+
if (get(node, nodeKey) !== null)
|
|
288
|
+
return node[nodeKey];
|
|
289
|
+
return dflt;
|
|
290
|
+
};
|
|
291
|
+
const textBlocks = pyOr(get(pyOr(get(row, "Design"), {}), "TextBlocks"), get(pyOr(get(row, "design"), {}), "textBlocks"), []);
|
|
292
|
+
return {
|
|
293
|
+
eventMode: pyStr(pyOr(get(node, "eventMode"), "polling")),
|
|
294
|
+
byoaConnection: truthy(fact("ByoaConnection", "byoaConnection", false)),
|
|
295
|
+
isWebhookUrlVisible: truthy(fact("IsWebhookUrlVisible", "isWebhookUrlVisible", false)),
|
|
296
|
+
textBlocks: pyIter(textBlocks).map((t) => pyStr(t)),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// The generator's cached event calls and its event passes
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
/**
|
|
303
|
+
* `triggers_objects_cached`: cached `uip is triggers objects <key> <OPERATION>`,
|
|
304
|
+
* called WITHOUT a connection on purpose. A failure is reported and cached as
|
|
305
|
+
* an empty catalog — a REAL failure, that is. The Python had no offline mode,
|
|
306
|
+
* so every failure it cached was the tenant's answer. Under `--offline` the
|
|
307
|
+
* client refuses the call instead; caching that as `{objects: []}` would make
|
|
308
|
+
* the next ONLINE build over the same `--keep-temp` caches see an empty event
|
|
309
|
+
* catalogue and expand nothing, silently. So a refusal is reported and used,
|
|
310
|
+
* but never written down. (The four describe/list helpers in `context.ts` now
|
|
311
|
+
* apply the same rule explicitly. They used to rest on an accident — a
|
|
312
|
+
* refusal's empty stdout is not JSON, so they returned before their cache
|
|
313
|
+
* write — which held only for as long as the offline client wrote nothing to
|
|
314
|
+
* stdout. That is precisely the accident this flag replaced, so they check the
|
|
315
|
+
* flag instead.)
|
|
316
|
+
*/
|
|
317
|
+
export async function triggersObjectsCached(ctx, key, operation) {
|
|
318
|
+
const name = `${safeCacheName(schemaCacheScope(ctx), key, operation, "_trigger_objects")}.json`;
|
|
319
|
+
if (ctx.caches.is.has(name))
|
|
320
|
+
return pyOr(get(ctx.caches.is.read(name), "objects"), []);
|
|
321
|
+
let rows = [];
|
|
322
|
+
try {
|
|
323
|
+
rows = await triggersObjects(ctx.client, key, operation, undefined, ctx.timeoutMs);
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
if (!(error instanceof EventDiscoveryError))
|
|
327
|
+
throw error;
|
|
328
|
+
ctx.reporter.warn(` WARN: trigger objects failed for ${key} ${operation}: ${error.message}`);
|
|
329
|
+
if (error.refused)
|
|
330
|
+
return rows;
|
|
331
|
+
}
|
|
332
|
+
writeCache(ctx.caches.is, name, { objects: rows });
|
|
333
|
+
return rows;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* `triggers_describe_cached`: cached `uip is triggers describe <key> <OPERATION> <object>`.
|
|
337
|
+
* A refused call is not cached, for the reason given on {@link triggersObjectsCached}.
|
|
338
|
+
*/
|
|
339
|
+
export async function triggersDescribeCached(ctx, key, operation, objectName) {
|
|
340
|
+
const name = `${safeCacheName(schemaCacheScope(ctx), key, operation, objectName, "_trigger_describe")}.json`;
|
|
341
|
+
if (ctx.caches.is.has(name))
|
|
342
|
+
return pyOr(get(ctx.caches.is.read(name), "describe"), {});
|
|
343
|
+
let data = {};
|
|
344
|
+
try {
|
|
345
|
+
data = await triggersDescribe(ctx.client, key, operation, objectName, undefined, ctx.timeoutMs);
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
if (!(error instanceof EventDiscoveryError))
|
|
349
|
+
throw error;
|
|
350
|
+
ctx.reporter.warn(` WARN: trigger describe failed for ${key} ${operation} ${objectName}: ${error.message}`);
|
|
351
|
+
if (error.refused)
|
|
352
|
+
return data;
|
|
353
|
+
}
|
|
354
|
+
writeCache(ctx.caches.is, name, { describe: data });
|
|
355
|
+
return data;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* `_apply_event_schema`: put an event's OWN schema on the entry, replacing the
|
|
359
|
+
* node manifest's. `inputSchema` becomes the union rule's answer; `eventSchema`
|
|
360
|
+
* is appended to the entry and later stamped onto the sidecar.
|
|
361
|
+
*/
|
|
362
|
+
export function applyEventSchema(entry, describe, obj) {
|
|
363
|
+
const e = entry;
|
|
364
|
+
const d = pyOr(describe, {});
|
|
365
|
+
const o = pyOr(obj, {});
|
|
366
|
+
const fields = unionEventParameters(get(d, "EventParameters"), get(o, "Parameters"));
|
|
367
|
+
e.inputSchema = { fields };
|
|
368
|
+
const outputFields = camelFields(get(d, "OutputFields"));
|
|
369
|
+
if (outputFields.length > 0)
|
|
370
|
+
e.outputSchema = { fields: outputFields };
|
|
371
|
+
const eventSchema = { eventParameters: fields, filterFields: camelFields(get(d, "FilterFields")) };
|
|
372
|
+
for (const [src, dst] of EVENT_FACTS)
|
|
373
|
+
if (get(d, src) !== null)
|
|
374
|
+
eventSchema[dst] = d[src];
|
|
375
|
+
e.eventSchema = eventSchema;
|
|
376
|
+
return entry;
|
|
377
|
+
}
|
|
378
|
+
/** `_describe_curated_events`: give each CURATED event its own schema, in place. */
|
|
379
|
+
export async function describeCuratedEvents(ctx, entries) {
|
|
380
|
+
if (entries.length === 0)
|
|
381
|
+
return;
|
|
382
|
+
const results = await mapConcurrent(entries, ctx.concurrency, async (entry) => {
|
|
383
|
+
const key = item(item(entry, "connector"), "key");
|
|
384
|
+
const operation = item(entry, "operation");
|
|
385
|
+
const eventOperation = pyOr(get(operation, "eventOperation"), "");
|
|
386
|
+
const objectName = pyOr(get(operation, "objectName"), "");
|
|
387
|
+
if (!truthy(eventOperation) || !truthy(objectName))
|
|
388
|
+
return [entry, null, null];
|
|
389
|
+
const rows = await triggersObjectsCached(ctx, key, eventOperation);
|
|
390
|
+
const obj = rows.find((r) => get(r, "Name") === objectName) ?? null;
|
|
391
|
+
return [entry, await triggersDescribeCached(ctx, key, eventOperation, objectName), obj];
|
|
392
|
+
});
|
|
393
|
+
let described = 0;
|
|
394
|
+
for (const [entry, describe, obj] of results) {
|
|
395
|
+
if (!truthy(describe))
|
|
396
|
+
continue;
|
|
397
|
+
applyEventSchema(entry, describe, obj);
|
|
398
|
+
described += 1;
|
|
399
|
+
}
|
|
400
|
+
ctx.reporter.info(` ${described}/${entries.length} curated events described (subscription parameters, filter fields, payload)`);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* `_make_expanded_event_entry`: one per-object variant of a generic connector
|
|
404
|
+
* EVENT. The variant keeps `activityType: GenericTrigger` and the template's
|
|
405
|
+
* nodeType deliberately; the object rides on `operation.objectName`.
|
|
406
|
+
*/
|
|
407
|
+
export function makeExpandedEventEntry(template, obj, describe) {
|
|
408
|
+
const entry = deepCopy(template);
|
|
409
|
+
const objectName = pyOr(get(obj, "Name"), "");
|
|
410
|
+
const objectDisplay = pyOr(get(obj, "DisplayName"), objectName);
|
|
411
|
+
const operation = item(entry, "operation");
|
|
412
|
+
operation.objectName = objectName;
|
|
413
|
+
operation.objectDisplayName = objectDisplay;
|
|
414
|
+
operation.sourceNodeType = item(template, "nodeType");
|
|
415
|
+
const label = pyOr(get(pyOr(get(template, "display"), {}), "label"), "");
|
|
416
|
+
item(entry, "display").label = pyStrip(`${pyStr(label)} (${pyStr(objectDisplay)})`);
|
|
417
|
+
return applyEventSchema(entry, describe, obj);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* `_expand_generic_events`: expand each generic EVENT template into one entry
|
|
421
|
+
* per event object. Variants come back in template × object order (the Python
|
|
422
|
+
* appended them as the describes completed); `build` sorts them before
|
|
423
|
+
* planning.
|
|
424
|
+
*/
|
|
425
|
+
export async function expandGenericEvents(ctx, templates) {
|
|
426
|
+
if (templates.length === 0)
|
|
427
|
+
return [];
|
|
428
|
+
const work = [];
|
|
429
|
+
for (const template of templates) {
|
|
430
|
+
const key = item(item(template, "connector"), "key");
|
|
431
|
+
const operation = pyOr(get(item(template, "operation"), "eventOperation"), "");
|
|
432
|
+
if (!truthy(operation))
|
|
433
|
+
continue;
|
|
434
|
+
for (const obj of await triggersObjectsCached(ctx, key, operation)) {
|
|
435
|
+
if (truthy(get(obj, "Name")))
|
|
436
|
+
work.push([template, key, operation, obj]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (work.length === 0)
|
|
440
|
+
return [];
|
|
441
|
+
const describes = await mapConcurrent(work, ctx.concurrency, ([, key, operation, obj]) => triggersDescribeCached(ctx, key, operation, item(obj, "Name")));
|
|
442
|
+
const expanded = work.map(([template, , , obj], i) => makeExpandedEventEntry(template, obj, pyOr(describes[i], {})));
|
|
443
|
+
ctx.reporter.info(` ${expanded.length} generic EVENT variants expanded from ${templates.length} template(s)`);
|
|
444
|
+
return expanded;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* `resolve_connector_events`: give every connector EVENT its real schema;
|
|
448
|
+
* expand the expandable ones. Returns `[entries, expanded]` — `entries` is the
|
|
449
|
+
* input with curated events described in place.
|
|
450
|
+
*/
|
|
451
|
+
export async function resolveConnectorEvents(ctx, entries) {
|
|
452
|
+
const curated = [];
|
|
453
|
+
const generic = [];
|
|
454
|
+
for (const e of entries) {
|
|
455
|
+
const at = get(item(e, "operation"), "activityType", "");
|
|
456
|
+
if (at === "CuratedTrigger") {
|
|
457
|
+
curated.push(e);
|
|
458
|
+
}
|
|
459
|
+
else if (at === "GenericTrigger") {
|
|
460
|
+
// UNKNOWN is not "static": expand only where the connector positively
|
|
461
|
+
// says its objects do not come from the connection.
|
|
462
|
+
const discovery = get(pyOr(get(e, "connector"), {}), "discovery");
|
|
463
|
+
if (!truthy(discovery)) {
|
|
464
|
+
ctx.reporter.warn(` WARN: ${item(e, "nodeType")} not expanded — no connector discovery metadata, so whether its ` +
|
|
465
|
+
"objects come from the connection is unknown");
|
|
466
|
+
}
|
|
467
|
+
else if (!objectsComeFromTheConnection(e)) {
|
|
468
|
+
generic.push(e);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (curated.length === 0 && generic.length === 0)
|
|
473
|
+
return [entries, []];
|
|
474
|
+
await describeCuratedEvents(ctx, curated);
|
|
475
|
+
return [entries, await expandGenericEvents(ctx, generic)];
|
|
476
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { LibraryEntry } from "../model/library.js";
|
|
2
|
+
import { type BuildContext } from "./context.js";
|
|
3
|
+
import { type Dict } from "./py.js";
|
|
4
|
+
/** `{op_name: template}` for one connector, in first-seen order. */
|
|
5
|
+
export type TemplatesByOp = Map<string, LibraryEntry>;
|
|
6
|
+
/**
|
|
7
|
+
* `_generic_templates_by_op`: bucket Generic CRUD entries by IS operation
|
|
8
|
+
* name per connector. Generic CRUD actions carry an `operation` verb
|
|
9
|
+
* (`Create`/`Delete`/`Retrieve`/`List`/`Update`/`Replace`) but no
|
|
10
|
+
* `httpMethod`; the IS catalog returns the same verbs under
|
|
11
|
+
* `AvailableOperations[].Name`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function genericTemplatesByOp(genericEntries: readonly LibraryEntry[]): Map<string, TemplatesByOp>;
|
|
14
|
+
/**
|
|
15
|
+
* `_make_expanded_entry`: one expanded entry from a Generic template + an
|
|
16
|
+
* (object, op) pair. `obj` is a `resources list` row, `op` an element of
|
|
17
|
+
* `AvailableOperations`, `opData` the per-operation describe `Data` (may be
|
|
18
|
+
* absent — the entry is still built, with an empty schema). The template's
|
|
19
|
+
* discovery flag is copied, never inferred from field counts.
|
|
20
|
+
*/
|
|
21
|
+
export declare function makeExpandedEntry(template: LibraryEntry, obj: unknown, op: unknown, opData: unknown): LibraryEntry;
|
|
22
|
+
/** `_unique_by_name`: first occurrence of each `Name`, in order. */
|
|
23
|
+
export declare function uniqueByName(objects: unknown): Map<string, Dict>;
|
|
24
|
+
/**
|
|
25
|
+
* `_list_generic_objects`: list and deduplicate the objects available to one
|
|
26
|
+
* connector. When the caller named specific objects and the catalog does not
|
|
27
|
+
* contain them, retry once with a refresh before believing it — a stale
|
|
28
|
+
* `uip is resources list` cache reports a real object as missing.
|
|
29
|
+
*/
|
|
30
|
+
export declare function listGenericObjects(ctx: BuildContext, connectorKey: string, templatesByOp: TemplatesByOp, objectNames?: readonly string[]): Promise<Dict[]>;
|
|
31
|
+
/**
|
|
32
|
+
* `expand_generic_for_connector`: materialize per-(object, op) entries for one
|
|
33
|
+
* connector, sequentially — the shape `prepare` uses. `objectNames`, when
|
|
34
|
+
* given, restricts the objects described; an empty list describes none.
|
|
35
|
+
*/
|
|
36
|
+
export declare function expandGenericForConnector(ctx: BuildContext, connectorKey: string, templatesByOp: TemplatesByOp, objectNames?: readonly string[]): Promise<LibraryEntry[]>;
|
|
37
|
+
/**
|
|
38
|
+
* `objects_come_from_the_connection`: true when this connector's object set is
|
|
39
|
+
* only knowable per connection. Field discovery alone does NOT qualify.
|
|
40
|
+
*/
|
|
41
|
+
export declare function objectsComeFromTheConnection(entry: unknown): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* `as_discovery_entry`: recast a Generic CRUD template as the discovery entry
|
|
44
|
+
* shipped for it — flagged, object cleared, labelled by verb ("Update Record",
|
|
45
|
+
* "List Records").
|
|
46
|
+
*/
|
|
47
|
+
export declare function asDiscoveryEntry(entry: LibraryEntry): LibraryEntry;
|
|
48
|
+
/**
|
|
49
|
+
* `disambiguate_discovery_labels`: keep two discovery entries on one connector
|
|
50
|
+
* from sharing a label. Only the colliding ones are renamed, from their own
|
|
51
|
+
* node type (`search-records` → "Search Records", via `str.title()`).
|
|
52
|
+
*/
|
|
53
|
+
export declare function disambiguateDiscoveryLabels(entries: LibraryEntry[]): LibraryEntry[];
|
|
54
|
+
/**
|
|
55
|
+
* A deterministic order for expanded entries: connector key, object name,
|
|
56
|
+
* operation name, node type — stable, so ties keep their input order.
|
|
57
|
+
*
|
|
58
|
+
* DELIBERATE FIX. The Python appended expansions as thread-pool futures
|
|
59
|
+
* completed, so the order two entries reached `write_library` in — and, when
|
|
60
|
+
* both claimed one path with equal input counts, which one survived — varied
|
|
61
|
+
* from run to run. Sorting here makes the library a function of its inputs.
|
|
62
|
+
* The index is sorted separately, so this changes no bytes when no such tie
|
|
63
|
+
* exists.
|
|
64
|
+
*/
|
|
65
|
+
export declare function sortExpandedEntries(entries: readonly LibraryEntry[]): LibraryEntry[];
|
|
66
|
+
/**
|
|
67
|
+
* `expand_all_generics`: expand every Generic entry into per-(object, op)
|
|
68
|
+
* entries. Returns `[curatedAndPassthrough, expanded]`: Curated entries pass
|
|
69
|
+
* through untouched; HTTP-passthrough Generic entries pass through as
|
|
70
|
+
* one-per-connector entries; CRUD Generic entries are dropped in favour of
|
|
71
|
+
* their expansions; object-dynamic ones become discovery entries.
|
|
72
|
+
*
|
|
73
|
+
* The three remote phases (list objects, describe objects, describe
|
|
74
|
+
* operations) each run under `mapConcurrent`, so `expanded` comes back in
|
|
75
|
+
* connector × object × operation order rather than completion order.
|
|
76
|
+
*/
|
|
77
|
+
export declare function expandAllGenerics(ctx: BuildContext, entries: readonly LibraryEntry[]): Promise<[LibraryEntry[], LibraryEntry[]]>;
|