@uipath/maestro-builder-sdk 6.4.0 → 6.5.1
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/bpmn/uipath-moddle.v1.json +21 -0
- 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,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./connectors-local/` overlay: how one prepared operation is merged into
|
|
3
|
+
* it, and what `prepare` reads back out of it afterwards.
|
|
4
|
+
*
|
|
5
|
+
* Calls ACCUMULATE — each prepared op merges into the overlay rather than
|
|
6
|
+
* replacing it — and Generic object variants intentionally share a nodeType,
|
|
7
|
+
* so the identity of an entry is `(nodeType, version, objectName)`, never the
|
|
8
|
+
* nodeType alone. Descriptors live under `descriptors/<key>/…`; an overlay
|
|
9
|
+
* written before that rule is migrated on the next merge.
|
|
10
|
+
*
|
|
11
|
+
* Everything the summary and the notes need afterwards — field counts, the
|
|
12
|
+
* parent declarations, the lookup vocabulary — is read back from the overlay
|
|
13
|
+
* just written rather than recomputed, so a resolution is made against the
|
|
14
|
+
* SAME schema the descriptors were generated from, including any
|
|
15
|
+
* connection-resolved field that only exists because this `prepare` ran.
|
|
16
|
+
*
|
|
17
|
+
* Ported from `merge_into`, `_descriptor_overlay_path`,
|
|
18
|
+
* `_migrate_legacy_descriptor_roots`, `_overlay_has_generic_expanded`,
|
|
19
|
+
* `_count_input_fields`, `_parent_fields`, `_schema_action_parents`,
|
|
20
|
+
* `_keep_prepared_schema` and `_lookup_specs_from_overlay` in
|
|
21
|
+
* `prepare_connector.py`.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, statSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { basename, dirname, join } from "node:path";
|
|
25
|
+
import { pyJsonDocument } from "../codecs/pyjson.js";
|
|
26
|
+
import { sortedByKeys, sortedStrings } from "../compat/compare.js";
|
|
27
|
+
import { toPosix } from "../compat/paths.js";
|
|
28
|
+
import { lookupSpecs } from "../generators/fields.js";
|
|
29
|
+
import { SCHEMA_VERSION } from "../library-build/canonical.js";
|
|
30
|
+
import { sidecarPath, unwrapV1Def } from "../library-reader.js";
|
|
31
|
+
import { catalogReferences, loadInvariantCollections, specsFromFields } from "../lookup-specs.js";
|
|
32
|
+
import { isEventNodeType } from "../model/library.js";
|
|
33
|
+
import { isPlainObject, pyGet, pyOr, pyStrAny, pyTruthy, pyTypeName } from "./support.js";
|
|
34
|
+
export const INDEX_FILENAME = "index.json";
|
|
35
|
+
export const DESCRIPTORS_ROOT = "descriptors";
|
|
36
|
+
/**
|
|
37
|
+
* `_variant_key`: identity for one resolvable activity schema variant. Keying
|
|
38
|
+
* only by nodeType made the last prepared object overwrite every earlier one.
|
|
39
|
+
*/
|
|
40
|
+
export function variantKey(entry) {
|
|
41
|
+
return JSON.stringify([pyGet(entry, "nodeType", ""), pyGet(entry, "version", ""), pyGet(entry, "objectName", "")]);
|
|
42
|
+
}
|
|
43
|
+
/** `Path(p).parts` for a relative POSIX path: empties and `.` collapse, `..` is kept. */
|
|
44
|
+
function pathParts(p) {
|
|
45
|
+
return toPosix(p)
|
|
46
|
+
.split("/")
|
|
47
|
+
.filter((part) => part !== "" && part !== ".");
|
|
48
|
+
}
|
|
49
|
+
/** `_descriptor_overlay_path`: `<key>/op@1.json` → `descriptors/<key>/op@1.json`; already-rooted paths pass through. */
|
|
50
|
+
export function descriptorOverlayPath(path) {
|
|
51
|
+
const parts = pathParts(path);
|
|
52
|
+
if (parts.length > 0 && parts[0] === DESCRIPTORS_ROOT)
|
|
53
|
+
return parts.join("/");
|
|
54
|
+
return [DESCRIPTORS_ROOT, ...parts].join("/");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `_migrate_legacy_descriptor_roots`: move `<dst>/<key>/**` written by an
|
|
58
|
+
* earlier `prepare` under `<dst>/descriptors/<key>/**`, bottom-up, leaving no
|
|
59
|
+
* empty directory behind. Only roots the index still names are touched.
|
|
60
|
+
*/
|
|
61
|
+
export function migrateLegacyDescriptorRoots(dst, entries) {
|
|
62
|
+
const legacyRoots = new Set();
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (!isPlainObject(entry))
|
|
65
|
+
continue;
|
|
66
|
+
const path = pyGet(entry, "path", null);
|
|
67
|
+
if (!pyTruthy(path))
|
|
68
|
+
continue;
|
|
69
|
+
const parts = pathParts(pyStrAny(path));
|
|
70
|
+
if (parts.length > 0 && parts[0] !== DESCRIPTORS_ROOT)
|
|
71
|
+
legacyRoots.add(parts[0]);
|
|
72
|
+
}
|
|
73
|
+
for (const legacyRoot of sortedStrings(legacyRoots)) {
|
|
74
|
+
const sourceRoot = join(dst, legacyRoot);
|
|
75
|
+
if (!existsSync(sourceRoot) || !statSync(sourceRoot).isDirectory())
|
|
76
|
+
continue;
|
|
77
|
+
const destinationRoot = join(dst, DESCRIPTORS_ROOT, legacyRoot);
|
|
78
|
+
const walk = (dir) => {
|
|
79
|
+
const children = readdirSync(dir, { withFileTypes: true });
|
|
80
|
+
const subdirs = children.filter((c) => c.isDirectory()).map((c) => c.name);
|
|
81
|
+
// `os.walk(topdown=False)`: the children are handled before their parent.
|
|
82
|
+
for (const name of subdirs)
|
|
83
|
+
walk(join(dir, name));
|
|
84
|
+
const destination = join(destinationRoot, toPosix(dir.slice(sourceRoot.length)).replace(/^\//, ""));
|
|
85
|
+
mkdirSync(destination, { recursive: true });
|
|
86
|
+
for (const child of children) {
|
|
87
|
+
if (child.isFile())
|
|
88
|
+
renameSync(join(dir, child.name), join(destination, child.name));
|
|
89
|
+
}
|
|
90
|
+
for (const name of subdirs)
|
|
91
|
+
rmdirSync(join(dir, name));
|
|
92
|
+
};
|
|
93
|
+
walk(sourceRoot);
|
|
94
|
+
rmdirSync(sourceRoot);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function sortKey(entry, key) {
|
|
98
|
+
return pyStrAny(pyGet(entry, key, ""));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The overlay index rows after merging `srcEntries` over `dstEntries`: every
|
|
102
|
+
* row's `path` under `descriptors/`, a same-variant row refreshed, a new
|
|
103
|
+
* variant added, and the whole list sorted by
|
|
104
|
+
* `(connectorKey, nodeType, version, objectName, path)`. Pure — the inputs
|
|
105
|
+
* are copied, not rewritten.
|
|
106
|
+
*/
|
|
107
|
+
export function mergeIndexEntries(dstEntries, srcEntries) {
|
|
108
|
+
const byVariant = new Map();
|
|
109
|
+
const place = (raw) => {
|
|
110
|
+
if (!isPlainObject(raw))
|
|
111
|
+
throw new TypeError("index entry is not an object"); // Python: `entry["path"]` on a non-dict
|
|
112
|
+
if (!Object.prototype.hasOwnProperty.call(raw, "path"))
|
|
113
|
+
throw new Error("KeyError: 'path'");
|
|
114
|
+
const entry = { ...raw, path: descriptorOverlayPath(pyStrAny(raw.path)) };
|
|
115
|
+
byVariant.set(variantKey(entry), entry);
|
|
116
|
+
};
|
|
117
|
+
for (const entry of dstEntries)
|
|
118
|
+
place(entry);
|
|
119
|
+
// Re-preparing the same object refreshes it; preparing a different object
|
|
120
|
+
// accumulates a sibling variant under the shared generic nodeType.
|
|
121
|
+
for (const entry of srcEntries)
|
|
122
|
+
place(entry);
|
|
123
|
+
return sortedByKeys([...byVariant.values()], (e) => sortKey(e, "connectorKey"), (e) => sortKey(e, "nodeType"), (e) => sortKey(e, "version"), (e) => sortKey(e, "objectName"), (e) => sortKey(e, "path"));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The bytes `connectors-local/index.json` is written in.
|
|
127
|
+
*
|
|
128
|
+
* INTENTIONAL DIVERGENCE from the Python, which wrote `json.dump(idx, indent=2)`
|
|
129
|
+
* with no trailing newline — the one file in the overlay without one, since
|
|
130
|
+
* `write_library` and the ledger both appended it. Every other JSON document
|
|
131
|
+
* the registry writes ends in a newline, and the overlay index now does too,
|
|
132
|
+
* so a diff of a re-prepared overlay stops showing a "\ No newline at end of
|
|
133
|
+
* file" marker on the index alone.
|
|
134
|
+
*/
|
|
135
|
+
export function formatOverlayIndex(index) {
|
|
136
|
+
return pyJsonDocument(index);
|
|
137
|
+
}
|
|
138
|
+
/** `<dir>/index.json` parsed, or `undefined` when there is no file (a broken file throws, as `json.load` did). */
|
|
139
|
+
function readIndex(dir) {
|
|
140
|
+
const path = join(dir, INDEX_FILENAME);
|
|
141
|
+
if (!existsSync(path))
|
|
142
|
+
return undefined;
|
|
143
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* `merge_into` over an in-memory library: the shape `planLibrary` produces.
|
|
147
|
+
* `files` must carry `index.json`; every other file is copied under
|
|
148
|
+
* `descriptors/` (a nested `index.json` is skipped, as the Python's walk did).
|
|
149
|
+
*/
|
|
150
|
+
export function mergeFilesInto(files, dst) {
|
|
151
|
+
mkdirSync(dst, { recursive: true });
|
|
152
|
+
const srcIndexText = files.get(INDEX_FILENAME);
|
|
153
|
+
if (srcIndexText === undefined)
|
|
154
|
+
throw new Error(`merge_into: ${INDEX_FILENAME} missing from the prepared library`);
|
|
155
|
+
const srcIdx = JSON.parse(typeof srcIndexText === "string" ? srcIndexText : Buffer.from(srcIndexText).toString("utf8"));
|
|
156
|
+
const dstIdxRaw = readIndex(dst);
|
|
157
|
+
// A parseable index that is not an object is REFUSED, not replaced. The
|
|
158
|
+
// Python's `dst_idx.get("entries", [])` raised on a list, a string or a null
|
|
159
|
+
// and wrote nothing, so an overlay some other tool has mangled stays the
|
|
160
|
+
// author's to fix; silently starting a fresh index would discard every
|
|
161
|
+
// descriptor it still names. Only a MISSING file starts one.
|
|
162
|
+
if (dstIdxRaw !== undefined && !isPlainObject(dstIdxRaw)) {
|
|
163
|
+
throw new TypeError(`${join(dst, INDEX_FILENAME)}: expected a JSON object, found ${pyTypeName(dstIdxRaw)}`);
|
|
164
|
+
}
|
|
165
|
+
const dstIdx = dstIdxRaw === undefined ? { schemaVersion: SCHEMA_VERSION, entries: [] } : { ...dstIdxRaw };
|
|
166
|
+
const dstEntries = pyGet(dstIdx, "entries", []);
|
|
167
|
+
if (!Array.isArray(dstEntries))
|
|
168
|
+
throw new TypeError("index.json: \"entries\" is not a list");
|
|
169
|
+
migrateLegacyDescriptorRoots(dst, dstEntries);
|
|
170
|
+
const srcEntries = isPlainObject(srcIdx) ? pyGet(srcIdx, "entries", []) : [];
|
|
171
|
+
dstIdx.entries = mergeIndexEntries(dstEntries, Array.isArray(srcEntries) ? srcEntries : []);
|
|
172
|
+
writeFileSync(join(dst, INDEX_FILENAME), formatOverlayIndex(dstIdx), "utf8");
|
|
173
|
+
for (const [rel, content] of files) {
|
|
174
|
+
if (basename(rel) === INDEX_FILENAME)
|
|
175
|
+
continue;
|
|
176
|
+
const out = join(dst, ...descriptorOverlayPath(rel).split("/"));
|
|
177
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
178
|
+
writeFileSync(out, content);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Every file under `root`, as `POSIX-relative path → bytes`. */
|
|
182
|
+
function readTree(root) {
|
|
183
|
+
const files = new Map();
|
|
184
|
+
const walk = (dir, prefix) => {
|
|
185
|
+
for (const child of readdirSync(dir, { withFileTypes: true })) {
|
|
186
|
+
const rel = prefix ? `${prefix}/${child.name}` : child.name;
|
|
187
|
+
if (child.isDirectory())
|
|
188
|
+
walk(join(dir, child.name), rel);
|
|
189
|
+
else if (child.isFile())
|
|
190
|
+
files.set(rel, new Uint8Array(readFileSync(join(dir, child.name))));
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
walk(root, "");
|
|
194
|
+
return files;
|
|
195
|
+
}
|
|
196
|
+
/** `merge_into(src, dst)`: merge a freshly written single-op library directory into the overlay. */
|
|
197
|
+
export function mergeInto(src, dst) {
|
|
198
|
+
mergeFilesInto(readTree(src), dst);
|
|
199
|
+
}
|
|
200
|
+
/** `_overlay_has_generic_expanded`: does the overlay hold ANY `GenericExpanded` entry, from any prepare? */
|
|
201
|
+
export function overlayHasGenericExpanded(out) {
|
|
202
|
+
let index;
|
|
203
|
+
try {
|
|
204
|
+
index = readIndex(out);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (!isPlainObject(index))
|
|
210
|
+
return false;
|
|
211
|
+
const entries = pyOr(pyGet(index, "entries", null), []);
|
|
212
|
+
if (!Array.isArray(entries))
|
|
213
|
+
return false;
|
|
214
|
+
return entries.some((e) => isPlainObject(e) && e.activityType === "GenericExpanded");
|
|
215
|
+
}
|
|
216
|
+
/** A reader over a directory on disk. */
|
|
217
|
+
export function directoryReader(dir) {
|
|
218
|
+
return (rel) => JSON.parse(readFileSync(join(dir, ...rel.split("/")), "utf8"));
|
|
219
|
+
}
|
|
220
|
+
/** A reader over an in-memory library (`planLibrary`'s `files`), for the staged half of {@link keepPreparedSchema}. */
|
|
221
|
+
export function filesReader(files) {
|
|
222
|
+
return (rel) => {
|
|
223
|
+
const text = files.get(rel);
|
|
224
|
+
if (text === undefined)
|
|
225
|
+
throw new Error(`ENOENT: ${rel}`);
|
|
226
|
+
return JSON.parse(typeof text === "string" ? text : Buffer.from(text).toString("utf8"));
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function requireKey(record, key) {
|
|
230
|
+
if (!isPlainObject(record))
|
|
231
|
+
throw new TypeError(`'${record === null ? "NoneType" : typeof record}' object is not subscriptable`);
|
|
232
|
+
if (!Object.prototype.hasOwnProperty.call(record, key))
|
|
233
|
+
throw new Error(`KeyError: ${JSON.stringify(key)}`);
|
|
234
|
+
return record[key];
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The index rows for `nodeType`, narrowed to `objectName` when one is given
|
|
238
|
+
* (`object_name is not None` in the Python: an empty string IS a filter).
|
|
239
|
+
* Throws on any shape the Python's `except` clauses would have turned into
|
|
240
|
+
* the caller's "unavailable" answer.
|
|
241
|
+
*/
|
|
242
|
+
function matchingRows(read, nodeType, objectName) {
|
|
243
|
+
const entries = requireKey(read(INDEX_FILENAME), "entries");
|
|
244
|
+
if (!Array.isArray(entries))
|
|
245
|
+
throw new TypeError("entries is not a list");
|
|
246
|
+
const rows = [];
|
|
247
|
+
for (const row of entries) {
|
|
248
|
+
if (requireKey(row, "nodeType") !== nodeType)
|
|
249
|
+
continue;
|
|
250
|
+
if (objectName !== undefined && objectName !== null && pyGet(row, "objectName", null) !== objectName)
|
|
251
|
+
continue;
|
|
252
|
+
rows.push(row);
|
|
253
|
+
}
|
|
254
|
+
return rows;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* `_count_input_fields`: input-field count across the matching overlay
|
|
258
|
+
* variants, or `undefined` when nothing matched or a file could not be read
|
|
259
|
+
* — never `0` for "unavailable", which the summary line would print as a
|
|
260
|
+
* plausible success.
|
|
261
|
+
*/
|
|
262
|
+
export function countInputFieldsFrom(read, nodeType, objectName) {
|
|
263
|
+
try {
|
|
264
|
+
const rows = matchingRows(read, nodeType, objectName);
|
|
265
|
+
if (rows.length === 0)
|
|
266
|
+
return undefined;
|
|
267
|
+
let total = 0;
|
|
268
|
+
for (const row of rows) {
|
|
269
|
+
const descriptor = read(pyStrAny(requireKey(row, "path")));
|
|
270
|
+
if (!isPlainObject(descriptor))
|
|
271
|
+
return undefined;
|
|
272
|
+
const inputSchema = pyGet(descriptor, "inputSchema", null);
|
|
273
|
+
if (!isPlainObject(inputSchema))
|
|
274
|
+
return undefined;
|
|
275
|
+
const fields = pyGet(inputSchema, "fields", null);
|
|
276
|
+
if (!Array.isArray(fields))
|
|
277
|
+
return undefined;
|
|
278
|
+
total += fields.length;
|
|
279
|
+
}
|
|
280
|
+
return total;
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/** {@link countInputFieldsFrom} over an overlay directory. */
|
|
287
|
+
export function countInputFields(out, nodeType, objectName) {
|
|
288
|
+
return countInputFieldsFrom(directoryReader(out), nodeType, objectName);
|
|
289
|
+
}
|
|
290
|
+
const PLACEHOLDER = /\{([^{}]+)\}/g;
|
|
291
|
+
/**
|
|
292
|
+
* `_parent_fields`: `[parent field names, whether the prepared schema is parent-ONLY]`.
|
|
293
|
+
*
|
|
294
|
+
* A reference `path` carrying a `{placeholder}` is the declaration that an
|
|
295
|
+
* operation is parent-driven: `/project/{fields.project.key}/issuetypes` on
|
|
296
|
+
* `fields.issuetype.id` names the field whose value must be resolved first.
|
|
297
|
+
* The parent set is both halves of that relation — the placeholder names AND
|
|
298
|
+
* the fields that declare them — because both must be passed as `-f`.
|
|
299
|
+
* "Parent-only" then means every prepared field is a parent: the expected
|
|
300
|
+
* shape with no `-f`, and a silent failure WITH `-f`.
|
|
301
|
+
*/
|
|
302
|
+
export function parentFieldsFrom(read, nodeType, objectName) {
|
|
303
|
+
const names = [];
|
|
304
|
+
const placeholders = new Set();
|
|
305
|
+
const declaring = new Set();
|
|
306
|
+
try {
|
|
307
|
+
for (const row of matchingRows(read, nodeType, objectName)) {
|
|
308
|
+
const descriptor = read(pyStrAny(requireKey(row, "path")));
|
|
309
|
+
if (!isPlainObject(descriptor))
|
|
310
|
+
throw new TypeError("descriptor is not a dict");
|
|
311
|
+
const inputSchema = pyGet(descriptor, "inputSchema", {});
|
|
312
|
+
if (!isPlainObject(inputSchema))
|
|
313
|
+
throw new TypeError("inputSchema is not a dict");
|
|
314
|
+
const fields = pyOr(pyGet(inputSchema, "fields", []), []);
|
|
315
|
+
if (!Array.isArray(fields))
|
|
316
|
+
throw new TypeError("fields is not a list");
|
|
317
|
+
for (const field of fields) {
|
|
318
|
+
if (!isPlainObject(field))
|
|
319
|
+
throw new TypeError("field is not a dict");
|
|
320
|
+
const name = pyGet(field, "name", null);
|
|
321
|
+
if (pyTruthy(name) && !names.includes(name))
|
|
322
|
+
names.push(name);
|
|
323
|
+
const reference = pyOr(pyGet(field, "reference", null), {});
|
|
324
|
+
if (!isPlainObject(reference))
|
|
325
|
+
throw new TypeError("reference is not a dict");
|
|
326
|
+
const path = pyOr(pyGet(reference, "path", null), "");
|
|
327
|
+
if (typeof path !== "string")
|
|
328
|
+
throw new TypeError("path is not a str");
|
|
329
|
+
const found = [...path.matchAll(PLACEHOLDER)].map((m) => m[1]);
|
|
330
|
+
if (found.length > 0) {
|
|
331
|
+
for (const p of found)
|
|
332
|
+
placeholders.add(p);
|
|
333
|
+
if (pyTruthy(name))
|
|
334
|
+
declaring.add(name);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
return [[], false];
|
|
341
|
+
}
|
|
342
|
+
const parents = new Set([...placeholders, ...declaring]);
|
|
343
|
+
if (parents.size === 0)
|
|
344
|
+
return [[], false];
|
|
345
|
+
const ordered = names.filter((name) => parents.has(name));
|
|
346
|
+
ordered.push(...sortedStrings([...parents].filter((p) => !ordered.includes(p))));
|
|
347
|
+
return [ordered, names.every((name) => parents.has(name))];
|
|
348
|
+
}
|
|
349
|
+
/** {@link parentFieldsFrom} over an overlay directory. */
|
|
350
|
+
export function parentFields(out, nodeType, objectName) {
|
|
351
|
+
return parentFieldsFrom(directoryReader(out), nodeType, objectName);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* `_schema_action_parents`: the parent inputs the PREPARED operation's own
|
|
355
|
+
* `operation.schemaAction.parents` names — the one declaration that is a
|
|
356
|
+
* PRECONDITION rather than a symptom: with any of these missing from `-f` the
|
|
357
|
+
* action cannot have run, whatever the describe returned. Declaration order,
|
|
358
|
+
* which is also the resolution order.
|
|
359
|
+
*/
|
|
360
|
+
export function schemaActionParentsFrom(read, nodeType, objectName) {
|
|
361
|
+
const names = [];
|
|
362
|
+
try {
|
|
363
|
+
for (const row of matchingRows(read, nodeType, objectName)) {
|
|
364
|
+
const descriptor = read(pyStrAny(requireKey(row, "path")));
|
|
365
|
+
if (!isPlainObject(descriptor))
|
|
366
|
+
throw new TypeError("descriptor is not a dict");
|
|
367
|
+
const operation = pyOr(pyGet(descriptor, "operation", null), {});
|
|
368
|
+
if (!isPlainObject(operation))
|
|
369
|
+
throw new TypeError("operation is not a dict");
|
|
370
|
+
const action = pyOr(pyGet(operation, "schemaAction", null), {});
|
|
371
|
+
if (!isPlainObject(action))
|
|
372
|
+
throw new TypeError("schemaAction is not a dict");
|
|
373
|
+
const parents = pyOr(pyGet(action, "parents", null), []);
|
|
374
|
+
if (!Array.isArray(parents))
|
|
375
|
+
throw new TypeError("parents is not a list");
|
|
376
|
+
for (const name of parents) {
|
|
377
|
+
if (pyTruthy(name) && !names.includes(name))
|
|
378
|
+
names.push(name);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
return names;
|
|
386
|
+
}
|
|
387
|
+
/** {@link schemaActionParentsFrom} over an overlay directory. */
|
|
388
|
+
export function schemaActionParents(out, nodeType, objectName) {
|
|
389
|
+
return schemaActionParentsFrom(directoryReader(out), nodeType, objectName);
|
|
390
|
+
}
|
|
391
|
+
function hasParent(parentFieldValues, name) {
|
|
392
|
+
return parentFieldValues instanceof Map ? parentFieldValues.has(name) : Object.prototype.hasOwnProperty.call(parentFieldValues, name);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* `_keep_prepared_schema`: `[kept, discarded]` when this run must NOT
|
|
396
|
+
* overwrite the overlay, else `undefined`.
|
|
397
|
+
*
|
|
398
|
+
* `--resolve` records a lookup; `-f` supplies the value a schema action reads.
|
|
399
|
+
* A run carrying only the former cannot have run the action, so what it has
|
|
400
|
+
* to write is the UNRESOLVED entry — and the merge replaces by variant key, so
|
|
401
|
+
* it lands on top of a schema an earlier `-f` run resolved (Jira
|
|
402
|
+
* `create-issue`: 21 fields down to 3, #666). Three conditions, all necessary:
|
|
403
|
+
* the EXISTING overlay declares `schemaAction.parents`; none of them arrived
|
|
404
|
+
* as `-f`; and the staged entry has STRICTLY fewer input fields than the one
|
|
405
|
+
* on disk — equal or richer still merges, so a fresher entry is never pinned
|
|
406
|
+
* by a stale overlay, and a first prepare (no overlay) writes normally.
|
|
407
|
+
*
|
|
408
|
+
* `staged` is the freshly built library: a directory, or `planLibrary`'s
|
|
409
|
+
* in-memory files.
|
|
410
|
+
*/
|
|
411
|
+
export function keepPreparedSchema(out, nodeType, staged, parentFieldValues, objectName) {
|
|
412
|
+
const parents = schemaActionParents(out, nodeType, objectName);
|
|
413
|
+
if (parents.length === 0 || parents.some((name) => hasParent(parentFieldValues, name)))
|
|
414
|
+
return undefined;
|
|
415
|
+
const existing = countInputFields(out, nodeType, objectName);
|
|
416
|
+
const incoming = countInputFieldsFrom(typeof staged === "string" ? directoryReader(staged) : filesReader(staged), nodeType, objectName);
|
|
417
|
+
if (existing === undefined || incoming === undefined || existing <= incoming)
|
|
418
|
+
return undefined;
|
|
419
|
+
return [existing, incoming];
|
|
420
|
+
}
|
|
421
|
+
function withFilterPatterns(specs, catalogFields) {
|
|
422
|
+
const references = catalogReferences({ inputSchema: { fields: [...catalogFields] } });
|
|
423
|
+
const out = new Map();
|
|
424
|
+
for (const [field, spec] of specs) {
|
|
425
|
+
const copy = { ...spec };
|
|
426
|
+
const filterPattern = references.get(field)?.filterPattern;
|
|
427
|
+
if (pyTruthy(filterPattern))
|
|
428
|
+
copy.filterPattern = pyStrAny(filterPattern);
|
|
429
|
+
out.set(field, copy);
|
|
430
|
+
}
|
|
431
|
+
return out;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* `_lookup_specs_from_overlay`: the prepared operation's lookup specs, read
|
|
435
|
+
* back from the overlay just written. Generic object variants share a
|
|
436
|
+
* nodeType, so `objectName` narrows to the one just prepared (a variant with
|
|
437
|
+
* no object name still answers, as the Python's `not in ("", None, name)` did);
|
|
438
|
+
* the FIRST matching entry answers.
|
|
439
|
+
*
|
|
440
|
+
* An EVENT's resolvable fields are its subscription parameters, and
|
|
441
|
+
* `v1def.eventParameters.fields` is the ONE authority for those — the same
|
|
442
|
+
* list the SDK, `check` and the descriptor generator read.
|
|
443
|
+
*
|
|
444
|
+
* INTENTIONAL DIVERGENCE from the Python. It selected `eventParameters` as the
|
|
445
|
+
* `fields_source` but then handed that source to `lookup_specs`, which reads
|
|
446
|
+
* `v1def.inputDefinition.fields` FIRST and falls back to the source only when
|
|
447
|
+
* the sidecar's input list is empty AND the source has a required field. On a
|
|
448
|
+
* prepared event whose trigger definition carries message-body inputs, the
|
|
449
|
+
* specs therefore came from the BODY fields — the exact wrong list — and an
|
|
450
|
+
* optional event parameter produced no spec at all. Here an event's specs are
|
|
451
|
+
* built from `eventParameters.fields` directly, which is what the descriptor
|
|
452
|
+
* generator emits `lookups` from (`descriptorInputFields`), so `--resolve`,
|
|
453
|
+
* the vocabulary print and the generated `.by*` helpers agree.
|
|
454
|
+
*/
|
|
455
|
+
export function lookupSpecsFromOverlay(out, nodeType, objectName) {
|
|
456
|
+
const indexPath = join(out, INDEX_FILENAME);
|
|
457
|
+
if (!existsSync(indexPath))
|
|
458
|
+
return new Map();
|
|
459
|
+
const index = JSON.parse(readFileSync(indexPath, "utf8"));
|
|
460
|
+
const entries = isPlainObject(index) ? pyGet(index, "entries", []) : [];
|
|
461
|
+
if (!Array.isArray(entries))
|
|
462
|
+
return new Map();
|
|
463
|
+
const invariant = loadInvariantCollections(out);
|
|
464
|
+
for (const row of entries) {
|
|
465
|
+
if (!isPlainObject(row) || row.nodeType !== nodeType)
|
|
466
|
+
continue;
|
|
467
|
+
if (pyTruthy(objectName)) {
|
|
468
|
+
const rowObject = pyGet(row, "objectName", null);
|
|
469
|
+
if (rowObject !== "" && rowObject !== null && rowObject !== objectName)
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const rel = pyStrAny(requireKey(row, "path"));
|
|
473
|
+
const entryPath = join(out, ...rel.split("/"));
|
|
474
|
+
if (!existsSync(entryPath))
|
|
475
|
+
continue;
|
|
476
|
+
const canonical = JSON.parse(readFileSync(entryPath, "utf8"));
|
|
477
|
+
const v1Path = join(out, ...sidecarPath(rel).split("/"));
|
|
478
|
+
const v1def = (existsSync(v1Path) ? unwrapV1Def(JSON.parse(readFileSync(v1Path, "utf8"))) : undefined) ?? {};
|
|
479
|
+
if (isEventNodeType(nodeType)) {
|
|
480
|
+
const parameters = v1def.eventParameters?.fields ?? [];
|
|
481
|
+
const fields = Array.isArray(parameters) ? parameters : [];
|
|
482
|
+
return withFilterPatterns(specsFromFields(fields, invariant, catalogReferences({ inputSchema: { fields } })), fields);
|
|
483
|
+
}
|
|
484
|
+
return withFilterPatterns(lookupSpecs(canonical, v1def, invariant), canonical.inputSchema?.fields ?? []);
|
|
485
|
+
}
|
|
486
|
+
return new Map();
|
|
487
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `['a=1', 'b=2']` → `{a: '1', b: '2'}`, refusing a missing `=`.
|
|
3
|
+
*
|
|
4
|
+
* Values may themselves contain `=` (a Salesforce SOQL body token), so the
|
|
5
|
+
* split is on the FIRST separator only. Insertion order is the `-f` order on
|
|
6
|
+
* the command line, which is also the order the describe is issued with.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseFields(pairs: readonly string[]): Map<string, string>;
|
|
9
|
+
/** One api-type ObjectAction: its name and the parent-field names its rules read, in rule order. */
|
|
10
|
+
export type ObjectActionSpec = readonly [actionName: string, parentFields: string[]];
|
|
11
|
+
/**
|
|
12
|
+
* `_object_action_specs`: each api-type ObjectAction the registry node
|
|
13
|
+
* declares and its ordered parent-field names. `payload` is the raw
|
|
14
|
+
* `{"Data": {"Node": …}}` document; anything not shaped that way answers `[]`,
|
|
15
|
+
* as the Python's `except (KeyError, TypeError)` did.
|
|
16
|
+
*/
|
|
17
|
+
export declare function objectActionSpecs(payload: unknown): ObjectActionSpec[];
|
|
18
|
+
/** The design-time schema replay request `customFieldsRequestDetails` carries. */
|
|
19
|
+
export interface ObjectActionRequest {
|
|
20
|
+
objectActionName: string;
|
|
21
|
+
parameterValues: Array<[name: string, value: string]>;
|
|
22
|
+
}
|
|
23
|
+
type ParentFields = ReadonlyMap<string, string> | Readonly<Record<string, string>>;
|
|
24
|
+
/**
|
|
25
|
+
* `_object_action_request`: the replay request for the ObjectAction whose
|
|
26
|
+
* parent set is EXACTLY the `-f` names given, or `undefined` when none
|
|
27
|
+
* matches (a partial set is not a match). Parameter names are spelled the way
|
|
28
|
+
* the platform stores nested paths: `fields.project.key` → `fields_sub_project_sub_key`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function objectActionRequest(payload: unknown, parentFields: ParentFields): ObjectActionRequest | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* `_declared_parents`: parent-input names the registry declares through
|
|
33
|
+
* api-type ObjectActions, flattened in declaration order and deduplicated.
|
|
34
|
+
*
|
|
35
|
+
* The second way an operation says "my fields depend on a value": not a
|
|
36
|
+
* `{placeholder}` in a reference path (Jira), but a design-time action whose
|
|
37
|
+
* rules name the inputs it reads (Data Fabric `create-entity-record` declares
|
|
38
|
+
* `GenerateSchema` over `entityName`).
|
|
39
|
+
*/
|
|
40
|
+
export declare function declaredParents(payload: unknown): string[];
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent fields: the `-f NAME=VALUE` inputs a schema-dynamic operation resolves
|
|
3
|
+
* its real field set from, and the registry's own declaration of them.
|
|
4
|
+
*
|
|
5
|
+
* Schema-dynamic ops resolve their fields from the VALUES of a few parent
|
|
6
|
+
* fields, not from the connection alone (Jira `create-issue`: 2 fields with no
|
|
7
|
+
* `-f`, 19 with project + issue type). Studio Web persists the ObjectAction and
|
|
8
|
+
* the parent values that produced a field list so it can replay the same
|
|
9
|
+
* schema; the registry owns the action name and rule order, `-f` owns the
|
|
10
|
+
* values. This module reads both halves.
|
|
11
|
+
*
|
|
12
|
+
* Ported from `_parse_fields`, `_object_action_specs`, `_object_action_request`
|
|
13
|
+
* and `_declared_parents` in `prepare_connector.py`. Every function here is
|
|
14
|
+
* pure over the PARSED `flow registry get` payload — the Python re-read the
|
|
15
|
+
* cached file each time; the port takes the object the cache already holds.
|
|
16
|
+
*/
|
|
17
|
+
import { pyStrip } from "../compat/pystr.js";
|
|
18
|
+
import { PrepareRefusal, fieldFlagMessage } from "./messages.js";
|
|
19
|
+
import { isPlainObject, pyOr, pyTruthy } from "./support.js";
|
|
20
|
+
/**
|
|
21
|
+
* `['a=1', 'b=2']` → `{a: '1', b: '2'}`, refusing a missing `=`.
|
|
22
|
+
*
|
|
23
|
+
* Values may themselves contain `=` (a Salesforce SOQL body token), so the
|
|
24
|
+
* split is on the FIRST separator only. Insertion order is the `-f` order on
|
|
25
|
+
* the command line, which is also the order the describe is issued with.
|
|
26
|
+
*/
|
|
27
|
+
export function parseFields(pairs) {
|
|
28
|
+
const parsed = new Map();
|
|
29
|
+
for (const pair of pairs) {
|
|
30
|
+
const at = pair.indexOf("=");
|
|
31
|
+
const name = at < 0 ? pair : pair.slice(0, at);
|
|
32
|
+
if (at < 0 || !pyStrip(name)) {
|
|
33
|
+
throw new PrepareRefusal(fieldFlagMessage(pair), "FIELD_FLAG_MALFORMED");
|
|
34
|
+
}
|
|
35
|
+
parsed.set(pyStrip(name), pair.slice(at + 1));
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* `_object_action_specs`: each api-type ObjectAction the registry node
|
|
41
|
+
* declares and its ordered parent-field names. `payload` is the raw
|
|
42
|
+
* `{"Data": {"Node": …}}` document; anything not shaped that way answers `[]`,
|
|
43
|
+
* as the Python's `except (KeyError, TypeError)` did.
|
|
44
|
+
*/
|
|
45
|
+
export function objectActionSpecs(payload) {
|
|
46
|
+
if (!isPlainObject(payload) || !isPlainObject(payload.Data) || !isPlainObject(payload.Data.Node))
|
|
47
|
+
return [];
|
|
48
|
+
const node = payload.Data.Node;
|
|
49
|
+
const methodInfo = pyOr(node.connectorMethodInfo, {});
|
|
50
|
+
const design = isPlainObject(methodInfo) ? pyOr(methodInfo.design, {}) : {};
|
|
51
|
+
const actions = isPlainObject(design) ? pyOr(design.actions, []) : [];
|
|
52
|
+
if (!Array.isArray(actions))
|
|
53
|
+
return [];
|
|
54
|
+
const specs = [];
|
|
55
|
+
for (const action of actions) {
|
|
56
|
+
if (!isPlainObject(action) || action.actionType !== "api")
|
|
57
|
+
continue;
|
|
58
|
+
const actionName = action.name;
|
|
59
|
+
if (typeof actionName !== "string" || !actionName)
|
|
60
|
+
continue;
|
|
61
|
+
const rules = pyOr(action.rules, []);
|
|
62
|
+
if (!Array.isArray(rules))
|
|
63
|
+
return []; // Python: iterating a non-list raises TypeError → []
|
|
64
|
+
const refs = [];
|
|
65
|
+
for (const rule of rules) {
|
|
66
|
+
if (!isPlainObject(rule))
|
|
67
|
+
return [];
|
|
68
|
+
if (pyTruthy(rule.isCleared) || !pyTruthy(rule.refFieldName))
|
|
69
|
+
continue;
|
|
70
|
+
refs.push(String(rule.refFieldName));
|
|
71
|
+
}
|
|
72
|
+
if (refs.length > 0)
|
|
73
|
+
specs.push([actionName, refs]);
|
|
74
|
+
}
|
|
75
|
+
return specs;
|
|
76
|
+
}
|
|
77
|
+
function parentEntries(parentFields) {
|
|
78
|
+
return parentFields instanceof Map ? [...parentFields.entries()] : Object.entries(parentFields);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* `_object_action_request`: the replay request for the ObjectAction whose
|
|
82
|
+
* parent set is EXACTLY the `-f` names given, or `undefined` when none
|
|
83
|
+
* matches (a partial set is not a match). Parameter names are spelled the way
|
|
84
|
+
* the platform stores nested paths: `fields.project.key` → `fields_sub_project_sub_key`.
|
|
85
|
+
*/
|
|
86
|
+
export function objectActionRequest(payload, parentFields) {
|
|
87
|
+
const entries = parentEntries(parentFields);
|
|
88
|
+
if (entries.length === 0)
|
|
89
|
+
return undefined;
|
|
90
|
+
const given = new Set(entries.map(([name]) => name));
|
|
91
|
+
const values = new Map(entries);
|
|
92
|
+
for (const [actionName, refs] of objectActionSpecs(payload)) {
|
|
93
|
+
if (refs.length === 0)
|
|
94
|
+
continue;
|
|
95
|
+
const declared = new Set(refs);
|
|
96
|
+
if (declared.size !== given.size || [...declared].some((ref) => !given.has(ref)))
|
|
97
|
+
continue;
|
|
98
|
+
return {
|
|
99
|
+
objectActionName: actionName,
|
|
100
|
+
parameterValues: refs.map((name) => [name.replaceAll(".", "_sub_"), values.get(name)]),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* `_declared_parents`: parent-input names the registry declares through
|
|
107
|
+
* api-type ObjectActions, flattened in declaration order and deduplicated.
|
|
108
|
+
*
|
|
109
|
+
* The second way an operation says "my fields depend on a value": not a
|
|
110
|
+
* `{placeholder}` in a reference path (Jira), but a design-time action whose
|
|
111
|
+
* rules name the inputs it reads (Data Fabric `create-entity-record` declares
|
|
112
|
+
* `GenerateSchema` over `entityName`).
|
|
113
|
+
*/
|
|
114
|
+
export function declaredParents(payload) {
|
|
115
|
+
const names = [];
|
|
116
|
+
for (const [, refs] of objectActionSpecs(payload)) {
|
|
117
|
+
for (const ref of refs)
|
|
118
|
+
if (!names.includes(ref))
|
|
119
|
+
names.push(ref);
|
|
120
|
+
}
|
|
121
|
+
return names;
|
|
122
|
+
}
|