@uipath/maestro-builder-sdk 6.4.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.
Files changed (160) hide show
  1. package/dist/cli/commands/registry.js +68 -12
  2. package/dist/registry/archive/entry-policy.d.ts +36 -0
  3. package/dist/registry/archive/entry-policy.js +60 -0
  4. package/dist/registry/archive/extract.d.ts +31 -0
  5. package/dist/registry/archive/extract.js +92 -0
  6. package/dist/registry/archive/zip-reader.d.ts +38 -0
  7. package/dist/registry/archive/zip-reader.js +256 -0
  8. package/dist/registry/codecs/pyjson.d.ts +62 -0
  9. package/dist/registry/codecs/pyjson.js +208 -0
  10. package/dist/registry/compat/compare.d.ts +18 -0
  11. package/dist/registry/compat/compare.js +32 -0
  12. package/dist/registry/compat/paths.d.ts +4 -0
  13. package/dist/registry/compat/paths.js +17 -0
  14. package/dist/registry/compat/pystr.d.ts +50 -0
  15. package/dist/registry/compat/pystr.js +221 -0
  16. package/dist/registry/concurrency.d.ts +10 -0
  17. package/dist/registry/concurrency.js +34 -0
  18. package/dist/registry/curated-overlay.d.ts +36 -4
  19. package/dist/registry/curated-overlay.js +87 -14
  20. package/dist/registry/errors.d.ts +26 -0
  21. package/dist/registry/errors.js +38 -0
  22. package/dist/registry/extractor.d.ts +9 -10
  23. package/dist/registry/extractor.js +5 -15
  24. package/dist/registry/files.d.ts +26 -0
  25. package/dist/registry/files.js +160 -0
  26. package/dist/registry/generators/descriptors.d.ts +81 -0
  27. package/dist/registry/generators/descriptors.js +405 -0
  28. package/dist/registry/generators/fields.d.ts +46 -0
  29. package/dist/registry/generators/fields.js +111 -0
  30. package/dist/registry/generators/markdown/common.d.ts +21 -0
  31. package/dist/registry/generators/markdown/common.js +55 -0
  32. package/dist/registry/generators/markdown/index.d.ts +33 -0
  33. package/dist/registry/generators/markdown/index.js +37 -0
  34. package/dist/registry/generators/markdown/page.d.ts +8 -0
  35. package/dist/registry/generators/markdown/page.js +52 -0
  36. package/dist/registry/generators/markdown/pyvalue.d.ts +34 -0
  37. package/dist/registry/generators/markdown/pyvalue.js +106 -0
  38. package/dist/registry/generators/markdown/sections/choosing-object.d.ts +2 -0
  39. package/dist/registry/generators/markdown/sections/choosing-object.js +52 -0
  40. package/dist/registry/generators/markdown/sections/example.d.ts +22 -0
  41. package/dist/registry/generators/markdown/sections/example.js +295 -0
  42. package/dist/registry/generators/markdown/sections/filtering.d.ts +2 -0
  43. package/dist/registry/generators/markdown/sections/filtering.js +76 -0
  44. package/dist/registry/generators/markdown/sections/header.d.ts +2 -0
  45. package/dist/registry/generators/markdown/sections/header.js +37 -0
  46. package/dist/registry/generators/markdown/sections/inputs.d.ts +30 -0
  47. package/dist/registry/generators/markdown/sections/inputs.js +151 -0
  48. package/dist/registry/generators/markdown/sections/lookups.d.ts +12 -0
  49. package/dist/registry/generators/markdown/sections/lookups.js +150 -0
  50. package/dist/registry/generators/markdown/sections/outputs.d.ts +3 -0
  51. package/dist/registry/generators/markdown/sections/outputs.js +9 -0
  52. package/dist/registry/generators/markdown/sections/resolving-fields.d.ts +2 -0
  53. package/dist/registry/generators/markdown/sections/resolving-fields.js +139 -0
  54. package/dist/registry/generators/markdown/sections/schema-dynamic.d.ts +2 -0
  55. package/dist/registry/generators/markdown/sections/schema-dynamic.js +86 -0
  56. package/dist/registry/generators/materialize.d.ts +30 -0
  57. package/dist/registry/generators/materialize.js +132 -0
  58. package/dist/registry/generators/md-symbols.d.ts +22 -0
  59. package/dist/registry/generators/md-symbols.js +58 -0
  60. package/dist/registry/generators/triggers.d.ts +15 -0
  61. package/dist/registry/generators/triggers.js +85 -0
  62. package/dist/registry/generators/type-registry.d.ts +50 -0
  63. package/dist/registry/generators/type-registry.js +154 -0
  64. package/dist/registry/generators/type-tree.d.ts +65 -0
  65. package/dist/registry/generators/type-tree.js +179 -0
  66. package/dist/registry/library-build/build.d.ts +57 -0
  67. package/dist/registry/library-build/build.js +286 -0
  68. package/dist/registry/library-build/canonical.d.ts +119 -0
  69. package/dist/registry/library-build/canonical.js +512 -0
  70. package/dist/registry/library-build/cli.d.ts +45 -0
  71. package/dist/registry/library-build/cli.js +182 -0
  72. package/dist/registry/library-build/context.d.ts +107 -0
  73. package/dist/registry/library-build/context.js +267 -0
  74. package/dist/registry/library-build/enrich.d.ts +36 -0
  75. package/dist/registry/library-build/enrich.js +158 -0
  76. package/dist/registry/library-build/events.d.ts +108 -0
  77. package/dist/registry/library-build/events.js +476 -0
  78. package/dist/registry/library-build/generic-expand.d.ts +77 -0
  79. package/dist/registry/library-build/generic-expand.js +363 -0
  80. package/dist/registry/library-build/plan.d.ts +71 -0
  81. package/dist/registry/library-build/plan.js +270 -0
  82. package/dist/registry/library-build/py.d.ts +45 -0
  83. package/dist/registry/library-build/py.js +167 -0
  84. package/dist/registry/library-build/schema-action.d.ts +38 -0
  85. package/dist/registry/library-build/schema-action.js +107 -0
  86. package/dist/registry/library-build/write.d.ts +6 -0
  87. package/dist/registry/library-build/write.js +26 -0
  88. package/dist/registry/library-reader.d.ts +20 -0
  89. package/dist/registry/library-reader.js +50 -0
  90. package/dist/registry/lookup-specs.d.ts +55 -0
  91. package/dist/registry/lookup-specs.js +140 -0
  92. package/dist/registry/materialize-cli.js +28 -14
  93. package/dist/registry/materializer.d.ts +25 -3
  94. package/dist/registry/materializer.js +45 -29
  95. package/dist/registry/model/library.d.ts +254 -0
  96. package/dist/registry/model/library.js +23 -0
  97. package/dist/registry/naming.d.ts +54 -0
  98. package/dist/registry/naming.js +142 -0
  99. package/dist/registry/orphans.d.ts +4 -0
  100. package/dist/registry/orphans.js +86 -0
  101. package/dist/registry/prepare/actions.d.ts +26 -0
  102. package/dist/registry/prepare/actions.js +94 -0
  103. package/dist/registry/prepare/connections.d.ts +181 -0
  104. package/dist/registry/prepare/connections.js +399 -0
  105. package/dist/registry/prepare/event.d.ts +96 -0
  106. package/dist/registry/prepare/event.js +268 -0
  107. package/dist/registry/prepare/handoff.d.ts +21 -0
  108. package/dist/registry/prepare/handoff.js +91 -0
  109. package/dist/registry/prepare/ledger.d.ts +104 -0
  110. package/dist/registry/prepare/ledger.js +216 -0
  111. package/dist/registry/prepare/messages.d.ts +190 -0
  112. package/dist/registry/prepare/messages.js +472 -0
  113. package/dist/registry/prepare/objects.d.ts +130 -0
  114. package/dist/registry/prepare/objects.js +318 -0
  115. package/dist/registry/prepare/overlay.d.ts +149 -0
  116. package/dist/registry/prepare/overlay.js +487 -0
  117. package/dist/registry/prepare/parents.d.ts +41 -0
  118. package/dist/registry/prepare/parents.js +122 -0
  119. package/dist/registry/prepare/pipeline.d.ts +91 -0
  120. package/dist/registry/prepare/pipeline.js +376 -0
  121. package/dist/registry/prepare/regenerate.d.ts +9 -0
  122. package/dist/registry/prepare/regenerate.js +50 -0
  123. package/dist/registry/prepare/resolve.d.ts +199 -0
  124. package/dist/registry/prepare/resolve.js +470 -0
  125. package/dist/registry/prepare/retry.d.ts +83 -0
  126. package/dist/registry/prepare/retry.js +109 -0
  127. package/dist/registry/prepare/source-import.d.ts +54 -0
  128. package/dist/registry/prepare/source-import.js +192 -0
  129. package/dist/registry/prepare/support.d.ts +41 -0
  130. package/dist/registry/prepare/support.js +178 -0
  131. package/dist/registry/prepare-cli.d.ts +16 -5
  132. package/dist/registry/prepare-cli.js +60 -53
  133. package/dist/registry/pull-cli.js +8 -7
  134. package/dist/registry/reporter.d.ts +26 -0
  135. package/dist/registry/reporter.js +41 -0
  136. package/dist/registry/uip/client.d.ts +99 -0
  137. package/dist/registry/uip/client.js +226 -0
  138. package/dist/registry/uip/commands.d.ts +40 -0
  139. package/dist/registry/uip/commands.js +80 -0
  140. package/dist/registry/uip/envelope.d.ts +37 -0
  141. package/dist/registry/uip/envelope.js +84 -0
  142. package/dist/registry/uip/response-cache.d.ts +28 -0
  143. package/dist/registry/uip/response-cache.js +57 -0
  144. package/lib/README.md +10 -8
  145. package/package.json +3 -3
  146. package/dist/generators/_connections.py +0 -331
  147. package/dist/generators/_events.py +0 -355
  148. package/dist/generators/_lookups.py +0 -114
  149. package/dist/generators/_naming.py +0 -185
  150. package/dist/generators/_objects.py +0 -299
  151. package/dist/generators/_resolve.py +0 -319
  152. package/dist/generators/convert_library_to_md.py +0 -1441
  153. package/dist/generators/extract_library.py +0 -125
  154. package/dist/generators/generate_connectors_ts.py +0 -936
  155. package/dist/generators/generate_library_json.py +0 -2527
  156. package/dist/generators/generate_triggers_ts.py +0 -110
  157. package/dist/generators/materialize_registry.py +0 -155
  158. package/dist/generators/prepare_connector.py +0 -1860
  159. package/dist/registry/python.d.ts +0 -26
  160. package/dist/registry/python.js +0 -46
@@ -0,0 +1,270 @@
1
+ /**
2
+ * The pure half of `write_library`: which files the library consists of and
3
+ * what each one holds, before anything touches the disk.
4
+ *
5
+ * A plan is a map from POSIX-relative path to file text (every text produced by
6
+ * `pyJsonDocument`, i.e. `json.dump(indent=2)` + newline), the sorted index
7
+ * rows, and the warnings the collision protocol raised. `write.ts` puts it on
8
+ * disk; a test compares it in memory.
9
+ */
10
+ import { pyJsonDocument } from "../codecs/pyjson.js";
11
+ import { sortedByKeys } from "../compat/compare.js";
12
+ import { pyStr } from "../compat/pystr.js";
13
+ import { messageOf } from "../errors.js";
14
+ import { SCHEMA_VERSION, actionIdFromNodeType } from "./canonical.js";
15
+ import { get, isRecord, item, pyEquals, pyIter, pyOr, truthy } from "./py.js";
16
+ export { actionIdFromNodeType };
17
+ /** `_safe_path_segment`: sanitize a string for use as one path segment. */
18
+ export function safePathSegment(s) {
19
+ return s.replace(/[^a-zA-Z0-9_.\-]+/g, "_") || "_";
20
+ }
21
+ /**
22
+ * `_input_field_count`: how much of a schema an entry carries — the tie-break
23
+ * when two claim one path, since whichever loses is dropped from the catalogue.
24
+ */
25
+ export function inputFieldCount(entry) {
26
+ return pyIter(pyOr(get(pyOr(get(entry, "inputSchema"), {}), "fields"), [])).length;
27
+ }
28
+ /**
29
+ * `_entry_relpath`: where to write `entry` relative to the library root, sans
30
+ * extension, `/`-separated. Expanded Generic entries land under
31
+ * `<connector>/generic/<object>/<op>@<ver>`; per-object EVENT variants under
32
+ * `<connector>/events/<object>/<action>@<ver>` (they keep the template's
33
+ * nodeType, so the object is what separates them).
34
+ */
35
+ export function entryRelpath(entry) {
36
+ const connectorKey = pyStr(pyOr(item(item(entry, "connector"), "key"), "unknown"));
37
+ const version = pyStr(pyOr(item(entry, "version"), "0.0.0"));
38
+ const operation = item(entry, "operation");
39
+ if (get(operation, "activityType") === "GenericExpanded") {
40
+ const obj = safePathSegment(pyStr(pyOr(item(operation, "objectName"), "object")));
41
+ const op = safePathSegment(pyStr(pyOr(item(operation, "name"), "op")).toLowerCase());
42
+ return `${connectorKey}/generic/${obj}/${op}@${version}`;
43
+ }
44
+ const actionId = actionIdFromNodeType(item(entry, "nodeType"));
45
+ if (get(operation, "activityType") === "GenericTrigger" && truthy(get(operation, "objectName"))) {
46
+ const obj = safePathSegment(pyStr(item(operation, "objectName")));
47
+ return `${connectorKey}/events/${obj}/${actionId}@${version}`;
48
+ }
49
+ return `${connectorKey}/${actionId}@${version}`;
50
+ }
51
+ /**
52
+ * `_build_v1_definition`: project a registry `Data.Node` into the shape a
53
+ * `.flow` file expects under `definitions[]`: drop `connectorMethodInfo` and
54
+ * `outputResponseDefinition`, add the three fields the editor stamps on every
55
+ * connector definition when absent (`setdefault`: a present `null` stays).
56
+ */
57
+ export function buildV1Definition(nodeData) {
58
+ if (!isRecord(nodeData))
59
+ throw new TypeError(`'${nodeData === null ? "NoneType" : typeof nodeData}' object has no attribute 'items'`);
60
+ const keep = {};
61
+ for (const [k, v] of Object.entries(nodeData)) {
62
+ if (k !== "connectorMethodInfo" && k !== "outputResponseDefinition")
63
+ keep[k] = v;
64
+ }
65
+ if (!("supportsErrorHandling" in keep))
66
+ keep.supportsErrorHandling = true;
67
+ if (!("inputDefaults" in keep))
68
+ keep.inputDefaults = {};
69
+ if (!("debug" in keep))
70
+ keep.debug = { runtime: "bpmnEngine" };
71
+ return keep;
72
+ }
73
+ /**
74
+ * `write_library` without the disk.
75
+ *
76
+ * Collision protocol, per relpath: an identical second entry is skipped; one
77
+ * with fewer-or-equal input fields is skipped with a warning; a richer one
78
+ * replaces the earlier (its index row is dropped, its files are overwritten).
79
+ * The sidecar is projected from the node the loader answers, with the entry's
80
+ * resolved input fields stamped in (always for an expanded Generic node,
81
+ * otherwise only when the entry has any) and its `eventSchema` stamped in when
82
+ * present. Index rows carry `objectFromDiscovery` only when set.
83
+ */
84
+ export function planLibrary(entries, loadNode, opts = {}) {
85
+ const files = new Map();
86
+ const index = [];
87
+ const warnings = [];
88
+ const claimed = new Map();
89
+ let written = 0;
90
+ /**
91
+ * `pyJsonDocument` for one file of one entry, naming the entry if it refuses.
92
+ *
93
+ * Both of an entry's writes are fatal (see the sidecar note below), and this
94
+ * runs over thousands of entries, so the refusal has to say which one it was
95
+ * about. `pyJsonDocument`'s own message is about a KEY — "object key "0"
96
+ * looks like an integer" — which tells an operator reading a pipeline log
97
+ * nothing about where to look. The catch adds the connector, the node type
98
+ * and the path, and rethrows; it never turns the failure into a warning.
99
+ */
100
+ const document = (value, path, label) => {
101
+ try {
102
+ return pyJsonDocument(value);
103
+ }
104
+ catch (exc) {
105
+ throw new Error(`cannot write ${path} for ${label}: ${messageOf(exc)}`);
106
+ }
107
+ };
108
+ for (const entry of entries) {
109
+ const connectorKey = pyOr(item(item(entry, "connector"), "key"), "unknown");
110
+ const version = pyOr(item(entry, "version"), "0.0.0");
111
+ const relpath = entryRelpath(entry);
112
+ const previous = claimed.get(relpath);
113
+ if (previous !== undefined) {
114
+ if (pyEquals(previous, entry))
115
+ continue;
116
+ const before = inputFieldCount(previous);
117
+ const now = inputFieldCount(entry);
118
+ if (now <= before) {
119
+ warnings.push(` WARN: ${relpath} claimed by two differing entries; keeping the one with more inputs (${before} vs ${now})`);
120
+ continue;
121
+ }
122
+ // This one is richer — drop the row already recorded for it.
123
+ warnings.push(` WARN: ${relpath} claimed by two differing entries; replacing the thinner one (${before} -> ${now})`);
124
+ const entryPath = `${relpath}.json`;
125
+ for (let i = index.length - 1; i >= 0; i--)
126
+ if (index[i].path === entryPath)
127
+ index.splice(i, 1);
128
+ written -= 1;
129
+ }
130
+ claimed.set(relpath, entry);
131
+ const label = `${pyStr(pyOr(get(entry, "nodeType"), "?"))} (connector ${pyStr(connectorKey)}, version ${pyStr(version)})`;
132
+ files.set(`${relpath}.json`, document(entry, `${relpath}.json`, label));
133
+ written += 1;
134
+ // Flow definition sidecar. Expanded Generic entries reuse the source
135
+ // Generic action's `Data.Node` as a TEMPLATE.
136
+ const operation = item(entry, "operation");
137
+ const expanded = get(operation, "activityType") === "GenericExpanded";
138
+ const lookupKey = expanded ? get(operation, "sourceNodeType") : item(entry, "nodeType");
139
+ // Built inside the try, SERIALIZED outside it. The Python's `except
140
+ // Exception` turned any sidecar failure into a WARN, and for the failures
141
+ // it could actually meet — a node that would not load, an
142
+ // `inputDefinition` that is not a dict — that is reproduced below. But
143
+ // `pyJsonDocument` also refuses an object whose key order JavaScript has
144
+ // already lost (an integer-like key), and that refusal is this port's, not
145
+ // the Python's: the Python wrote the file. It makes the ENTRY write above
146
+ // fatal, so it must make the sidecar fatal too — a WARN there would leave
147
+ // a silently missing `.v1def.json` that `compile` trips over later. Both
148
+ // writes go through `document`, so the fatal message names the entry
149
+ // instead of leaving the operator with the JSON writer's own text.
150
+ let v1def;
151
+ try {
152
+ const nodeData = typeof lookupKey === "string" ? loadNode(lookupKey) : undefined;
153
+ if (truthy(nodeData)) {
154
+ v1def = buildV1Definition(nodeData);
155
+ // The entry's fields are the node's own plus whatever the parameters
156
+ // filled in — a superset of a verbatim projection — so prefer them
157
+ // whenever there are any. An expanded Generic node stamps even an
158
+ // empty list: its template's fields belong to the dispatch node.
159
+ const resolvedFields = pyOr(get(get(entry, "inputSchema", {}), "fields"), []);
160
+ if (expanded || truthy(resolvedFields)) {
161
+ if (!("inputDefinition" in v1def))
162
+ v1def.inputDefinition = {};
163
+ const inputDefinition = v1def.inputDefinition;
164
+ if (!isRecord(inputDefinition)) {
165
+ throw new TypeError(`'${inputDefinition === null ? "NoneType" : typeof inputDefinition}' object does not support item assignment`);
166
+ }
167
+ // Stamp onto a COPY. `buildV1Definition` is shallow, so this block is
168
+ // still the loader's own; the Python wrote into it in place and got
169
+ // away with it only because `_load_node_data` re-read the file for
170
+ // every entry. A loader here may answer one shared object (an
171
+ // in-memory cache does), and writing through would hand the first
172
+ // entry's fields to every later sidecar built from the same node.
173
+ // The spread keeps `fields` in its existing position, as the
174
+ // in-place write did.
175
+ v1def.inputDefinition = { ...inputDefinition, fields: resolvedFields };
176
+ }
177
+ // A per-object EVENT variant carries its own subscription vocabulary,
178
+ // and this sidecar is where the SDK reads one.
179
+ const eventSchema = pyOr(get(entry, "eventSchema"), {});
180
+ if (truthy(eventSchema)) {
181
+ v1def.eventParameters = { fields: pyOr(get(eventSchema, "eventParameters"), []) };
182
+ v1def.filterFields = { fields: pyOr(get(eventSchema, "filterFields"), []) };
183
+ for (const fact of ["eventMode", "byoaConnection", "isWebhookUrlVisible"]) {
184
+ if (get(eventSchema, fact) !== null)
185
+ v1def[fact] = eventSchema[fact];
186
+ }
187
+ }
188
+ }
189
+ }
190
+ catch (exc) {
191
+ v1def = undefined;
192
+ warnings.push(` WARN: v1def write failed for ${pyStr(item(entry, "nodeType"))}: ${messageOf(exc)}`);
193
+ }
194
+ if (v1def !== undefined) {
195
+ files.set(`${relpath}.v1def.json`, document(v1def, `${relpath}.v1def.json`, label));
196
+ }
197
+ const row = {
198
+ nodeType: item(entry, "nodeType"),
199
+ version: version,
200
+ connectorKey: connectorKey,
201
+ label: item(item(entry, "display"), "label"),
202
+ activityType: get(operation, "activityType", ""),
203
+ // `objectName` disambiguates expansions that share a nodeType.
204
+ objectName: pyOr(get(operation, "objectName", ""), ""),
205
+ operationName: pyOr(get(operation, "name", ""), ""),
206
+ path: `${relpath}.json`,
207
+ };
208
+ // `registry search` reads only this index; emitted only when set, so every
209
+ // other row stays byte-identical.
210
+ if (truthy(get(operation, "objectFromDiscovery")))
211
+ row.objectFromDiscovery = true;
212
+ index.push(row);
213
+ }
214
+ // Sort for stable diffs; many expanded entries share the source Generic
215
+ // nodeType, so `path` breaks ties.
216
+ const sorted = sortedByKeys(index, (r) => pyStr(r.connectorKey), (r) => pyStr(r.nodeType), (r) => pyStr(r.version), (r) => r.path);
217
+ files.set("index.json", pyJsonDocument({ schemaVersion: opts.schemaVersion ?? SCHEMA_VERSION, entries: sorted }));
218
+ return { files, index: sorted, warnings, written };
219
+ }
220
+ /** Files that live in a library directory without being entries. */
221
+ const NOT_ENTRIES = new Set(["index.json", "list-envelopes.json", "invariant-collections.json", ".registry-source.json"]);
222
+ /**
223
+ * Every entry file in `files` that has no index row: an empty list is the
224
+ * invariant `write_library` used to break (index one row longer than the
225
+ * directory). Sidecars and the known non-entry files are exempt.
226
+ */
227
+ export function orphanCheck(files, index) {
228
+ const indexed = new Set(index.map((row) => row.path));
229
+ return [...files.keys()].filter((path) => !NOT_ENTRIES.has(path) && !path.endsWith(".v1def.json") && !indexed.has(path));
230
+ }
231
+ // ---------------------------------------------------------------------------
232
+ // The trigger side-file
233
+ // ---------------------------------------------------------------------------
234
+ const TRIGGER_PREFIX = "uipath.connector.trigger.";
235
+ /**
236
+ * `write_triggers_side_file`'s rows: every trigger nodeType the registry
237
+ * search exposed, sorted by `(connectorKey, NodeType)`.
238
+ */
239
+ export function triggerRows(searchPayload) {
240
+ const rows = [];
241
+ for (const entry of pyIter(pyOr(get(searchPayload, "Data", []), []))) {
242
+ const nodeType = get(entry, "NodeType", "");
243
+ if (typeof nodeType !== "string")
244
+ throw new TypeError(`'${nodeType === null ? "NoneType" : typeof nodeType}' object has no attribute 'startswith'`);
245
+ if (!nodeType.startsWith(TRIGGER_PREFIX))
246
+ continue;
247
+ const parts = nodeType.split(".");
248
+ rows.push({
249
+ NodeType: nodeType,
250
+ DisplayName: get(entry, "DisplayName", ""),
251
+ Description: get(entry, "Description", ""),
252
+ Version: get(entry, "Version", ""),
253
+ connectorKey: parts.length > 3 ? parts[3] : "",
254
+ });
255
+ }
256
+ return sortedByKeys(rows, (t) => t.connectorKey, (t) => t.NodeType);
257
+ }
258
+ export const TRIGGER_CATALOGUE_NOTE = "Catalog of every IS trigger nodeType returned by " +
259
+ "`uip maestro flow registry search --filter tags:in=connector`. " +
260
+ "Full per-trigger schemas (filterFields, outputFields, eventMode) " +
261
+ "require `--connection-id` on `uip flow registry get`, which " +
262
+ "the library generator can't supply across all ~70 " +
263
+ "trigger-supporting connectors. Use this side-file for the " +
264
+ "'does an IS trigger exist for X provider?' question; fetch the " +
265
+ "full schema at author time with `uip is triggers describe " +
266
+ "<connector-key> <op> <object> --connection-id <id>`.";
267
+ /** The side-file's text: the envelope around the rows, `json.dump(indent=2)` + newline. */
268
+ export function triggerCatalogueDocument(rows) {
269
+ return pyJsonDocument({ schemaVersion: "1", note: TRIGGER_CATALOGUE_NOTE, count: rows.length, triggers: rows });
270
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Python value semantics the generator leaned on without saying so.
3
+ *
4
+ * `generate_library_json.py` reads JSON the platform served and decides what to
5
+ * write with `or`, `if not x`, `dict.get(k, default)` and `==`. Each of those
6
+ * differs from its JavaScript spelling on exactly the values the registry
7
+ * serves — `[]` and `{}` are falsy in Python, a present `null` is not "absent"
8
+ * to `dict.get`, and two dicts compare equal in any key order. The port spells
9
+ * each rule once here so every call site can read like the line it replaces.
10
+ */
11
+ export type Dict = Record<string, unknown>;
12
+ /** A plain JSON object: what `json.load` calls a dict. */
13
+ export declare function isRecord(value: unknown): value is Dict;
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;
18
+ /**
19
+ * `d.get(key, default)`. A present key answers its value even when that value
20
+ * is `null`; an absent key answers `dflt`, which is `null` (Python's `None`)
21
+ * unless given — never `undefined`, because `undefined` would vanish from the
22
+ * written JSON where Python would have written `null`.
23
+ *
24
+ * Calling `.get` on a non-dict raises in Python, and does here.
25
+ */
26
+ export declare function get(d: unknown, key: string, dflt?: unknown): unknown;
27
+ /** `d[key]`: raises on a missing key the way Python's `KeyError` does. */
28
+ export declare function item(d: unknown, key: string): unknown;
29
+ /** `for x in value`: a list yields items, a dict its keys, a string its characters. */
30
+ export declare function pyIter(value: unknown): unknown[];
31
+ /**
32
+ * `a == b` on JSON-loaded values: dicts compare by key set and value, in any
33
+ * order; lists element by element; `True == 1` as in Python.
34
+ */
35
+ 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
+ /** `repr(list_of_str)`: `['a', 'b']`. */
41
+ export declare function pyReprStrList(items: readonly string[]): string;
42
+ /** `s[start:end]` by code point. */
43
+ export declare function codePointSlice(s: string, start: number, end?: number): string;
44
+ /** `str.partition(sep)`. */
45
+ export declare function partition(s: string, sep: string): [string, string, string];
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Python value semantics the generator leaned on without saying so.
3
+ *
4
+ * `generate_library_json.py` reads JSON the platform served and decides what to
5
+ * write with `or`, `if not x`, `dict.get(k, default)` and `==`. Each of those
6
+ * differs from its JavaScript spelling on exactly the values the registry
7
+ * serves — `[]` and `{}` are falsy in Python, a present `null` is not "absent"
8
+ * to `dict.get`, and two dicts compare equal in any key order. The port spells
9
+ * each rule once here so every call site can read like the line it replaces.
10
+ */
11
+ /** A plain JSON object: what `json.load` calls a dict. */
12
+ export function isRecord(value) {
13
+ return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Map);
14
+ }
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
+ /**
37
+ * Does the dict itself hold `key`? — `key in d` for a Python dict.
38
+ *
39
+ * NOT JavaScript's `in`, which walks the prototype chain: `"constructor" in
40
+ * {}` and `"toString" in {}` are both true, so `get(node, "constructor")`
41
+ * would answer `Object`'s constructor FUNCTION where `dict.get` answers
42
+ * `None`, and `item()` would hand back a function instead of raising
43
+ * `KeyError`. Either one puts a function where the caller expects JSON, and
44
+ * `pyJsonStringify` then refuses to serialize it — a build that dies on a
45
+ * connector field named `constructor`. The event buckets were moved to a `Map`
46
+ * for this same reason (`events.ts`); these helpers are the shared version of
47
+ * the hazard, reached by every `get`/`item` in the generator.
48
+ */
49
+ function hasKey(d, key) {
50
+ return Object.prototype.hasOwnProperty.call(d, key);
51
+ }
52
+ /**
53
+ * `d.get(key, default)`. A present key answers its value even when that value
54
+ * is `null`; an absent key answers `dflt`, which is `null` (Python's `None`)
55
+ * unless given — never `undefined`, because `undefined` would vanish from the
56
+ * written JSON where Python would have written `null`.
57
+ *
58
+ * Calling `.get` on a non-dict raises in Python, and does here.
59
+ */
60
+ export function get(d, key, dflt = null) {
61
+ if (!isRecord(d)) {
62
+ throw new TypeError(`'${pyTypeName(d)}' object has no attribute 'get'`);
63
+ }
64
+ if (!hasKey(d, key))
65
+ return dflt;
66
+ const value = d[key];
67
+ return value === undefined ? dflt : value;
68
+ }
69
+ /** `d[key]`: raises on a missing key the way Python's `KeyError` does. */
70
+ export function item(d, key) {
71
+ if (!isRecord(d)) {
72
+ throw new TypeError(`'${pyTypeName(d)}' object is not subscriptable`);
73
+ }
74
+ if (!hasKey(d, key) || d[key] === undefined)
75
+ throw new Error(`KeyError: '${key}'`);
76
+ return d[key];
77
+ }
78
+ /** `for x in value`: a list yields items, a dict its keys, a string its characters. */
79
+ export function pyIter(value) {
80
+ if (Array.isArray(value))
81
+ return value;
82
+ if (typeof value === "string")
83
+ return [...value];
84
+ if (value instanceof Map)
85
+ return [...value.keys()];
86
+ if (isRecord(value))
87
+ return Object.keys(value);
88
+ throw new TypeError(`'${pyTypeName(value)}' object is not iterable`);
89
+ }
90
+ function pyTypeName(value) {
91
+ if (value === null || value === undefined)
92
+ return "NoneType";
93
+ if (Array.isArray(value))
94
+ return "list";
95
+ if (typeof value === "boolean")
96
+ return "bool";
97
+ if (typeof value === "number")
98
+ return Number.isInteger(value) ? "int" : "float";
99
+ if (typeof value === "string")
100
+ return "str";
101
+ return "dict";
102
+ }
103
+ /**
104
+ * `a == b` on JSON-loaded values: dicts compare by key set and value, in any
105
+ * order; lists element by element; `True == 1` as in Python.
106
+ */
107
+ export function pyEquals(a, b) {
108
+ if (a === b)
109
+ return true;
110
+ if (typeof a === "boolean" && typeof b === "number")
111
+ return Number(a) === b;
112
+ if (typeof a === "number" && typeof b === "boolean")
113
+ return a === Number(b);
114
+ if (Array.isArray(a) || Array.isArray(b)) {
115
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
116
+ return false;
117
+ return a.every((item, i) => pyEquals(item, b[i]));
118
+ }
119
+ if (isRecord(a) && isRecord(b)) {
120
+ const keysA = Object.keys(a).filter((k) => a[k] !== undefined);
121
+ const keysB = Object.keys(b).filter((k) => b[k] !== undefined);
122
+ if (keysA.length !== keysB.length)
123
+ return false;
124
+ // `hasKey`, not `k in b`, for the reason given on it: `"toString" in b` is
125
+ // true for every object, so the prototype could answer for a key the dict
126
+ // does not carry.
127
+ return keysA.every((k) => hasKey(b, k) && pyEquals(a[k], b[k]));
128
+ }
129
+ return false;
130
+ }
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
+ /** `repr(list_of_str)`: `['a', 'b']`. */
156
+ export function pyReprStrList(items) {
157
+ return `[${items.map(pyReprStr).join(", ")}]`;
158
+ }
159
+ /** `s[start:end]` by code point. */
160
+ export function codePointSlice(s, start, end) {
161
+ return [...s].slice(start, end).join("");
162
+ }
163
+ /** `str.partition(sep)`. */
164
+ export function partition(s, sep) {
165
+ const at = s.indexOf(sep);
166
+ return at < 0 ? [s, "", ""] : [s.slice(0, at), sep, s.slice(at + sep.length)];
167
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * `schema_action` and `_order_by_dependency`: the design-time action that
3
+ * resolves an operation's REAL field set, recorded as what a caller has to do.
4
+ *
5
+ * Some operations do not have one field list. Jira `create-issue` describes to
6
+ * 2 fields with nothing supplied — just the parents that select the schema —
7
+ * and to 32 once a project and an issue type are given. Studio Web runs the
8
+ * connector's api-type ObjectAction automatically as those parents change, and
9
+ * `uip is resources describe -f name=value` ports the same call. Neither is
10
+ * discoverable from the catalog, because the library drops
11
+ * `connectorMethodInfo.design`; this is what keeps the recipe.
12
+ */
13
+ import { type Dict } from "./py.js";
14
+ /**
15
+ * Cascade order for parent fields: a parent another one needs comes first.
16
+ *
17
+ * The action's `rules` are not in cascade order — Jira lists
18
+ * `fields.issuetype.id` before `fields.project.key` — but the order is
19
+ * recoverable, because the dependent field's own lookup path carries the
20
+ * other's name (`/project/{fields.project.key}/issuetypes`). `dependsOn` is
21
+ * honoured too where a connector sets it. Layers are emitted in `parents`
22
+ * order, and a cycle emits the rest rather than looping.
23
+ */
24
+ export declare function orderByDependency(parents: readonly string[], fields: readonly unknown[]): string[];
25
+ /**
26
+ * The `operation.schemaAction` record, or `null` when the node declares none:
27
+ *
28
+ * - `parents` the fields whose values select the schema, in cascade order
29
+ * - `trigger` a rule field carrying no `refFieldValues` is a button, not
30
+ * data: Salesforce's `generateSchema` has to be invoked
31
+ * explicitly, whereas Jira fires on the parents alone
32
+ * - `replaces` which half the result replaces — `input`, `output` or `both`
33
+ *
34
+ * A node can declare more than one api action; the smallest parent set wins,
35
+ * ties keeping declaration order (SAP OData offers GenerateSchemaWithExpand
36
+ * beside GenerateSchema, and the first read would demand `$expand`).
37
+ */
38
+ export declare function schemaAction(connectorMethodInfo: unknown, inputFields: readonly unknown[]): Dict | null;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * `schema_action` and `_order_by_dependency`: the design-time action that
3
+ * resolves an operation's REAL field set, recorded as what a caller has to do.
4
+ *
5
+ * Some operations do not have one field list. Jira `create-issue` describes to
6
+ * 2 fields with nothing supplied — just the parents that select the schema —
7
+ * and to 32 once a project and an issue type are given. Studio Web runs the
8
+ * connector's api-type ObjectAction automatically as those parents change, and
9
+ * `uip is resources describe -f name=value` ports the same call. Neither is
10
+ * discoverable from the catalog, because the library drops
11
+ * `connectorMethodInfo.design`; this is what keeps the recipe.
12
+ */
13
+ import { get, isRecord, pyIter, pyOr, truthy } from "./py.js";
14
+ /**
15
+ * Cascade order for parent fields: a parent another one needs comes first.
16
+ *
17
+ * The action's `rules` are not in cascade order — Jira lists
18
+ * `fields.issuetype.id` before `fields.project.key` — but the order is
19
+ * recoverable, because the dependent field's own lookup path carries the
20
+ * other's name (`/project/{fields.project.key}/issuetypes`). `dependsOn` is
21
+ * honoured too where a connector sets it. Layers are emitted in `parents`
22
+ * order, and a cycle emits the rest rather than looping.
23
+ */
24
+ export function orderByDependency(parents, fields) {
25
+ const depends = new Map();
26
+ for (const field of fields) {
27
+ const name = get(field, "name");
28
+ if (typeof name !== "string" || !parents.includes(name))
29
+ continue;
30
+ const reference = pyOr(get(field, "reference"), {});
31
+ const path = String(pyOr(get(reference, "path"), ""));
32
+ const explicit = pyOr(get(reference, "dependsOn"), []);
33
+ const explicitList = pyIter(explicit);
34
+ depends.set(name, new Set(parents.filter((other) => other !== name && (path.includes(`{${other}}`) || explicitList.includes(other)))));
35
+ }
36
+ const ordered = [];
37
+ let remaining = [...parents];
38
+ while (remaining.length > 0) {
39
+ const remainingSet = new Set(remaining);
40
+ const free = remaining.filter((p) => ![...(depends.get(p) ?? new Set())].some((d) => remainingSet.has(d)));
41
+ if (free.length === 0) {
42
+ ordered.push(...remaining);
43
+ break;
44
+ }
45
+ ordered.push(...free);
46
+ remaining = remaining.filter((p) => !free.includes(p));
47
+ }
48
+ return ordered;
49
+ }
50
+ /**
51
+ * The `operation.schemaAction` record, or `null` when the node declares none:
52
+ *
53
+ * - `parents` the fields whose values select the schema, in cascade order
54
+ * - `trigger` a rule field carrying no `refFieldValues` is a button, not
55
+ * data: Salesforce's `generateSchema` has to be invoked
56
+ * explicitly, whereas Jira fires on the parents alone
57
+ * - `replaces` which half the result replaces — `input`, `output` or `both`
58
+ *
59
+ * A node can declare more than one api action; the smallest parent set wins,
60
+ * ties keeping declaration order (SAP OData offers GenerateSchemaWithExpand
61
+ * beside GenerateSchema, and the first read would demand `$expand`).
62
+ */
63
+ export function schemaAction(connectorMethodInfo, inputFields) {
64
+ const design = pyOr(get(pyOr(connectorMethodInfo, {}), "design"), {});
65
+ const candidates = [];
66
+ for (const action of pyIter(pyOr(get(design, "actions"), []))) {
67
+ if (get(action, "actionType") !== "api")
68
+ continue;
69
+ const parents = [];
70
+ let trigger = null;
71
+ for (const rule of pyIter(pyOr(get(action, "rules"), []))) {
72
+ const name = get(rule, "refFieldName");
73
+ if (!truthy(name))
74
+ continue;
75
+ if (truthy(get(rule, "refFieldValues")))
76
+ parents.push(name);
77
+ else if (trigger === null)
78
+ trigger = name;
79
+ }
80
+ if (parents.length === 0)
81
+ continue;
82
+ candidates.push({ action: action, parents, trigger });
83
+ }
84
+ if (candidates.length === 0)
85
+ return null;
86
+ let chosen = candidates[0];
87
+ for (const candidate of candidates)
88
+ if (candidate.parents.length < chosen.parents.length)
89
+ chosen = candidate;
90
+ const { action, parents, trigger } = chosen;
91
+ const remap = pyOr(get(pyOr(get(action, "onSuccess"), {}), "remapConfiguration"), {});
92
+ const record = {
93
+ name: pyOr(get(action, "name"), ""),
94
+ parents: orderByDependency(parents, inputFields),
95
+ };
96
+ if (truthy(trigger))
97
+ record.trigger = trigger;
98
+ const replacesInput = isRecord(remap) && get(remap, "input") === "replace";
99
+ const replacesOutput = isRecord(remap) && get(remap, "output") === "replace";
100
+ if (replacesInput && replacesOutput)
101
+ record.replaces = "both";
102
+ else if (replacesInput)
103
+ record.replaces = "input";
104
+ else if (replacesOutput)
105
+ record.replaces = "output";
106
+ return record;
107
+ }
@@ -0,0 +1,6 @@
1
+ import type { TriggerCatalogueRow } from "../model/library.js";
2
+ import { type LibraryPlan } from "./plan.js";
3
+ /** Write the plan under `outputDir` (removed first). Returns the entry count, as `write_library` did. */
4
+ export declare function writePlan(plan: LibraryPlan, outputDir: string): number;
5
+ /** Write the trigger catalogue to `outputPath`, creating its directory. Returns the row count. */
6
+ export declare function writeTriggerSideFile(rows: readonly TriggerCatalogueRow[], outputPath: string): number;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Putting a {@link LibraryPlan} on disk, the way `write_library` did: the
3
+ * output directory is removed first, then every planned file is written under
4
+ * it. The trigger side-file goes wherever the caller says.
5
+ */
6
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
8
+ import { triggerCatalogueDocument } from "./plan.js";
9
+ /** Write the plan under `outputDir` (removed first). Returns the entry count, as `write_library` did. */
10
+ export function writePlan(plan, outputDir) {
11
+ if (existsSync(outputDir))
12
+ rmSync(outputDir, { recursive: true, force: true });
13
+ mkdirSync(outputDir, { recursive: true });
14
+ for (const [relative, text] of plan.files) {
15
+ const path = join(outputDir, ...relative.split("/"));
16
+ mkdirSync(dirname(path), { recursive: true });
17
+ writeFileSync(path, text, "utf8");
18
+ }
19
+ return plan.written;
20
+ }
21
+ /** Write the trigger catalogue to `outputPath`, creating its directory. Returns the row count. */
22
+ export function writeTriggerSideFile(rows, outputPath) {
23
+ mkdirSync(dirname(outputPath) || ".", { recursive: true });
24
+ writeFileSync(outputPath, triggerCatalogueDocument(rows), "utf8");
25
+ return rows.length;
26
+ }