@uipath/maestro-builder-sdk 5.2.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/LICENSE +21 -0
- package/README.md +322 -0
- package/dist/bindings-v2.d.ts +37 -0
- package/dist/bindings-v2.js +155 -0
- package/dist/bindings.d.ts +6 -0
- package/dist/bindings.js +6 -0
- package/dist/bpmn/bpmn-expr-check.d.ts +41 -0
- package/dist/bpmn/bpmn-expr-check.js +218 -0
- package/dist/bpmn/bpmn-sdk.d.ts +2257 -0
- package/dist/bpmn/bpmn-sdk.js +1476 -0
- package/dist/bpmn/check-cli.d.ts +3 -0
- package/dist/bpmn/check-cli.js +32 -0
- package/dist/bpmn/check.d.ts +25 -0
- package/dist/bpmn/check.js +430 -0
- package/dist/bpmn/compile-cli.d.ts +3 -0
- package/dist/bpmn/compile-cli.js +60 -0
- package/dist/bpmn/connector.d.ts +56 -0
- package/dist/bpmn/connector.js +199 -0
- package/dist/bpmn/decompile-cli.d.ts +8 -0
- package/dist/bpmn/decompile-cli.js +54 -0
- package/dist/bpmn/decompile.d.ts +6 -0
- package/dist/bpmn/decompile.js +1108 -0
- package/dist/bpmn/format-profile.d.ts +27 -0
- package/dist/bpmn/format-profile.js +11 -0
- package/dist/bpmn/intsvc.d.ts +77 -0
- package/dist/bpmn/intsvc.js +155 -0
- package/dist/bpmn/load.d.ts +4 -0
- package/dist/bpmn/load.js +22 -0
- package/dist/bpmn/merge-cli.d.ts +3 -0
- package/dist/bpmn/merge-cli.js +45 -0
- package/dist/bpmn/merge.d.ts +19 -0
- package/dist/bpmn/merge.js +293 -0
- package/dist/bpmn/registry-types.json +1869 -0
- package/dist/bpmn/serialize.d.ts +28 -0
- package/dist/bpmn/serialize.js +757 -0
- package/dist/bpmn/tidy-cli.d.ts +2 -0
- package/dist/bpmn/tidy-cli.js +39 -0
- package/dist/bpmn/typed-node.d.ts +382 -0
- package/dist/bpmn/typed-node.js +551 -0
- package/dist/bpmn/uipath-moddle.v1.json +823 -0
- package/dist/bpmn/xml-ranges.d.ts +45 -0
- package/dist/bpmn/xml-ranges.js +151 -0
- package/dist/case/case-expr-check.d.ts +35 -0
- package/dist/case/case-expr-check.js +85 -0
- package/dist/case/case-sdk.d.ts +1282 -0
- package/dist/case/case-sdk.js +923 -0
- package/dist/case/check-cli.d.ts +3 -0
- package/dist/case/check-cli.js +31 -0
- package/dist/case/check.d.ts +18 -0
- package/dist/case/check.js +456 -0
- package/dist/case/compile-cli.d.ts +3 -0
- package/dist/case/compile-cli.js +291 -0
- package/dist/case/decompile-cli.d.ts +9 -0
- package/dist/case/decompile-cli.js +117 -0
- package/dist/case/decompile.d.ts +187 -0
- package/dist/case/decompile.js +1170 -0
- package/dist/case/entry-points.d.ts +79 -0
- package/dist/case/entry-points.js +118 -0
- package/dist/case/format-profile.d.ts +60 -0
- package/dist/case/format-profile.js +79 -0
- package/dist/case/ids.d.ts +24 -0
- package/dist/case/ids.js +76 -0
- package/dist/case/load.d.ts +12 -0
- package/dist/case/load.js +32 -0
- package/dist/case/preserve.d.ts +133 -0
- package/dist/case/preserve.js +105 -0
- package/dist/case/serialize.d.ts +10 -0
- package/dist/case/serialize.js +1414 -0
- package/dist/check-cli.d.ts +3 -0
- package/dist/check-cli.js +118 -0
- package/dist/check.d.ts +146 -0
- package/dist/check.js +4898 -0
- package/dist/cli/commands/bpmn.d.ts +2 -0
- package/dist/cli/commands/bpmn.js +73 -0
- package/dist/cli/commands/case.d.ts +2 -0
- package/dist/cli/commands/case.js +60 -0
- package/dist/cli/commands/flow.d.ts +2 -0
- package/dist/cli/commands/flow.js +99 -0
- package/dist/cli/commands/registry.d.ts +10 -0
- package/dist/cli/commands/registry.js +170 -0
- package/dist/cli/common.d.ts +30 -0
- package/dist/cli/common.js +92 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli-run.d.ts +2 -0
- package/dist/cli-run.js +42 -0
- package/dist/compile-cli.d.ts +3 -0
- package/dist/compile-cli.js +160 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.js +201 -0
- package/dist/core/actions.d.ts +3244 -0
- package/dist/core/actions.js +1062 -0
- package/dist/core/binding-messages.d.ts +1 -0
- package/dist/core/binding-messages.js +16 -0
- package/dist/core/bindings.d.ts +12 -0
- package/dist/core/bindings.js +67 -0
- package/dist/core/ceql.d.ts +73 -0
- package/dist/core/ceql.js +272 -0
- package/dist/core/cli-spelling.d.ts +39 -0
- package/dist/core/cli-spelling.js +62 -0
- package/dist/core/connector-raw-node.d.ts +21 -0
- package/dist/core/connector-raw-node.js +47 -0
- package/dist/core/connectors.d.ts +184 -0
- package/dist/core/connectors.js +128 -0
- package/dist/core/expr-check.d.ts +93 -0
- package/dist/core/expr-check.js +186 -0
- package/dist/core/expr.d.ts +323 -0
- package/dist/core/expr.js +400 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.js +10 -0
- package/dist/core/library.d.ts +440 -0
- package/dist/core/library.js +1183 -0
- package/dist/core/list-envelope.d.ts +45 -0
- package/dist/core/list-envelope.js +97 -0
- package/dist/core/lookup-store.d.ts +22 -0
- package/dist/core/lookup-store.js +85 -0
- package/dist/core/lookups.d.ts +227 -0
- package/dist/core/lookups.js +231 -0
- package/dist/core/node-classes.d.ts +234 -0
- package/dist/core/node-classes.js +158 -0
- package/dist/core/script-return.d.ts +68 -0
- package/dist/core/script-return.js +197 -0
- package/dist/core/stable-id.d.ts +28 -0
- package/dist/core/stable-id.js +37 -0
- package/dist/core-definitions.json +16249 -0
- package/dist/decompile-cli.d.ts +3 -0
- package/dist/decompile-cli.js +131 -0
- package/dist/decompile.d.ts +138 -0
- package/dist/decompile.js +2482 -0
- package/dist/emit.d.ts +10 -0
- package/dist/emit.js +60 -0
- package/dist/event-filters.d.ts +130 -0
- package/dist/event-filters.js +137 -0
- package/dist/flow-expr-check.d.ts +17 -0
- package/dist/flow-expr-check.js +193 -0
- package/dist/flow-sdk.d.ts +1341 -0
- package/dist/flow-sdk.js +1164 -0
- package/dist/flow-variables.d.ts +13 -0
- package/dist/flow-variables.js +42 -0
- package/dist/format-profile.d.ts +72 -0
- package/dist/format-profile.js +97 -0
- package/dist/generators/_connections.py +287 -0
- package/dist/generators/_events.py +355 -0
- package/dist/generators/_lookups.py +114 -0
- package/dist/generators/_naming.py +185 -0
- package/dist/generators/_objects.py +299 -0
- package/dist/generators/_resolve.py +273 -0
- package/dist/generators/convert_library_to_md.py +1441 -0
- package/dist/generators/extract_library.py +125 -0
- package/dist/generators/generate_connectors_ts.py +936 -0
- package/dist/generators/generate_library_json.py +2527 -0
- package/dist/generators/generate_triggers_ts.py +110 -0
- package/dist/generators/materialize_registry.py +155 -0
- package/dist/generators/prepare_connector.py +1688 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/ixp-definition.json +1982 -0
- package/dist/library.d.ts +6 -0
- package/dist/library.js +6 -0
- package/dist/load.d.ts +16 -0
- package/dist/load.js +95 -0
- package/dist/merge-cli.d.ts +3 -0
- package/dist/merge-cli.js +47 -0
- package/dist/merge.d.ts +125 -0
- package/dist/merge.js +0 -0
- package/dist/migrate.d.ts +15 -0
- package/dist/migrate.js +245 -0
- package/dist/node-runtime.d.ts +3 -0
- package/dist/node-runtime.js +22 -0
- package/dist/package-name.d.ts +17 -0
- package/dist/package-name.js +17 -0
- package/dist/ref-resolve.d.ts +77 -0
- package/dist/ref-resolve.js +170 -0
- package/dist/registry/cache.d.ts +55 -0
- package/dist/registry/cache.js +134 -0
- package/dist/registry/curated-overlay.d.ts +16 -0
- package/dist/registry/curated-overlay.js +62 -0
- package/dist/registry/extractor.d.ts +22 -0
- package/dist/registry/extractor.js +18 -0
- package/dist/registry/materialize-bin.d.ts +2 -0
- package/dist/registry/materialize-bin.js +7 -0
- package/dist/registry/materialize-cli.d.ts +1 -0
- package/dist/registry/materialize-cli.js +46 -0
- package/dist/registry/materializer.d.ts +23 -0
- package/dist/registry/materializer.js +34 -0
- package/dist/registry/path-cli.d.ts +1 -0
- package/dist/registry/path-cli.js +56 -0
- package/dist/registry/prepare-cli.d.ts +62 -0
- package/dist/registry/prepare-cli.js +185 -0
- package/dist/registry/pull-cli.d.ts +1 -0
- package/dist/registry/pull-cli.js +274 -0
- package/dist/registry/python.d.ts +26 -0
- package/dist/registry/python.js +46 -0
- package/dist/registry/search-cli.d.ts +52 -0
- package/dist/registry/search-cli.js +159 -0
- package/dist/schema-version.d.ts +165 -0
- package/dist/schema-version.js +148 -0
- package/dist/serialize.d.ts +64 -0
- package/dist/serialize.js +6398 -0
- package/lib/README.md +112 -0
- package/lib/library-json.samples/index.json +158 -0
- package/lib/library-json.samples/invariant-collections.json +83 -0
- package/lib/library-json.samples/list-envelopes.json +123 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.json +131 -0
- package/lib/library-json.samples/uipath-atlassian-jira/create-issue@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.json +86 -0
- package/lib/library-json.samples/uipath-atlassian-jira/generic/create_issue/create@1.0.0.v1def.json +208 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.json +2040 -0
- package/lib/library-json.samples/uipath-atlassian-jira/get-issue@1.0.0.v1def.json +195 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.json +169 -0
- package/lib/library-json.samples/uipath-google-vertex/execute-google-vertex-agent@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/event-http-webhook@1.0.0.v1def.json +262 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.json +63 -0
- package/lib/library-json.samples/uipath-http-webhook/trigger-http-webhook@1.0.0.v1def.json +213 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.json +227 -0
- package/lib/library-json.samples/uipath-microsoft-azureactivedirectory/list-groups@1.0.0.v1def.json +373 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-file-created@1.0.0.v1def.json +369 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/event-list-item-added@1.0.0.v1def.json +256 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.json +116 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-file-created@1.0.0.v1def.json +320 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.json +56 -0
- package/lib/library-json.samples/uipath-microsoft-onedrive/trigger-list-item-added@1.0.0.v1def.json +207 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/event-email-received@1.0.0.v1def.json +264 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.json +142 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/send-email@1.0.0.v1def.json +306 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.json +107 -0
- package/lib/library-json.samples/uipath-microsoft-outlook365/trigger-email-received@1.0.0.v1def.json +217 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.json +85 -0
- package/lib/library-json.samples/uipath-microsoft-powerautomate/invoke-a-microsoft-power-automate-flow@1.0.0.v1def.json +209 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.json +755 -0
- package/lib/library-json.samples/uipath-salesforce-slack/send-message-to-user@1.0.0.v1def.json +513 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.json +122 -0
- package/lib/library-json.samples/uipath-uipath-dataservice/query-entity-records@1.0.0.v1def.json +227 -0
- package/package.json +85 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* library — resolve Integration Service connector definitions from the on-disk
|
|
3
|
+
* connector library cache (the same `library-json` the Flow Code arm uses).
|
|
4
|
+
*
|
|
5
|
+
* For a connector action we need two files per action:
|
|
6
|
+
* <key>/<action>@<ver>.json the entry (connector key, HTTP method, endpoint)
|
|
7
|
+
* <key>/<action>@<ver>.v1def.json the Flow `definitions[]` shape (model, ports, …)
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { loadListEnvelopes, lookupListEnvelope } from './list-envelope.js';
|
|
12
|
+
import { lookupSpecOf } from './lookups.js';
|
|
13
|
+
import { loadInvariantCollections, loadLookupResolutions } from './lookup-store.js';
|
|
14
|
+
export class Library {
|
|
15
|
+
layers = [];
|
|
16
|
+
/** The LIST-ENVELOPE LEDGER, overlay first so a prepared entry can add a row. */
|
|
17
|
+
envelopeRows = [];
|
|
18
|
+
/**
|
|
19
|
+
* Everything `prepare --resolve` has recorded, from the overlay only.
|
|
20
|
+
*
|
|
21
|
+
* Never from the baked library: a resolution is a fact about ONE tenant's
|
|
22
|
+
* data, so shipping one in the published archive would hand every consumer an
|
|
23
|
+
* id from someone else's connection.
|
|
24
|
+
*/
|
|
25
|
+
lookupResolutions;
|
|
26
|
+
/** Collections the ledger declares connector-invariant; overlay unions over baked. */
|
|
27
|
+
invariantCollections;
|
|
28
|
+
constructor(dir, opts = {}) {
|
|
29
|
+
this.lookupResolutions = loadLookupResolutions(opts.overlayDir);
|
|
30
|
+
this.invariantCollections = new Set([
|
|
31
|
+
...loadInvariantCollections(dir),
|
|
32
|
+
...loadInvariantCollections(opts.overlayDir),
|
|
33
|
+
]);
|
|
34
|
+
// Overlay first (local wins), then the baked library.
|
|
35
|
+
if (opts.overlayDir) {
|
|
36
|
+
const oidx = join(opts.overlayDir, 'index.json');
|
|
37
|
+
if (existsSync(oidx))
|
|
38
|
+
this.layers.push({ dir: opts.overlayDir, entries: JSON.parse(readFileSync(oidx, 'utf8')).entries ?? [], overlay: true });
|
|
39
|
+
this.envelopeRows.push(...loadListEnvelopes(opts.overlayDir));
|
|
40
|
+
}
|
|
41
|
+
this.envelopeRows.push(...loadListEnvelopes(dir));
|
|
42
|
+
const idx = join(dir, 'index.json');
|
|
43
|
+
if (!existsSync(idx))
|
|
44
|
+
throw new Error(`connector library not found at ${idx}. Build it first (uipath-flow-v2-setup-library) ` +
|
|
45
|
+
`or point --library / $FLOW_SDK_LIBRARY_JSON at a library-json dir.`);
|
|
46
|
+
this.layers.push({ dir, entries: JSON.parse(readFileSync(idx, 'utf8')).entries ?? [] });
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a connector ACTION (`uipath.connector.<key>.<action>`).
|
|
50
|
+
*
|
|
51
|
+
* `objectName` picks WHICH object a **generic** operation addresses; see
|
|
52
|
+
* {@link Library.resolveType} for what happens when it is omitted on a
|
|
53
|
+
* nodeType that needs it, and when it names an object the library has never
|
|
54
|
+
* heard of.
|
|
55
|
+
*
|
|
56
|
+
* The event family lives under two sibling namespaces and is resolved by
|
|
57
|
+
* `resolveEvent` below — same files, same sidecar convention, different
|
|
58
|
+
* position in the flow.
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* One operation's resolvable lookup fields, keyed by the WIRE field name.
|
|
62
|
+
*
|
|
63
|
+
* The same set the descriptor generator emits helpers for, decided by the same
|
|
64
|
+
* rules ({@link lookupSpecOf}) over the same field vocabulary — so a `check`
|
|
65
|
+
* diagnostic can never name a `.by*` the generator did not write.
|
|
66
|
+
*
|
|
67
|
+
* @param key - Connector library key.
|
|
68
|
+
* @param action - Operation id.
|
|
69
|
+
* @param version - Pin an operation version; newest otherwise.
|
|
70
|
+
* @param objectName - The object a generic operation addresses.
|
|
71
|
+
* @returns `{ field: spec }`, empty when the operation has no resolvable lookups.
|
|
72
|
+
*/
|
|
73
|
+
lookupSpecs(key, action, version, objectName) {
|
|
74
|
+
let resolved;
|
|
75
|
+
try {
|
|
76
|
+
resolved = this.resolve(key, action, version, objectName);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
const out = {};
|
|
82
|
+
for (const field of resolved.fields) {
|
|
83
|
+
const spec = lookupSpecOf(field.reference, this.invariantCollections);
|
|
84
|
+
if (spec)
|
|
85
|
+
out[field.name] = spec;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
resolve(key, action, version, objectName) {
|
|
90
|
+
return this.resolveType(`uipath.connector.${key}.${action}`, version, objectName);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Whether a connector step's schema is materialized locally — see
|
|
94
|
+
* {@link SchemaReadiness}. Index rows are enough for the `object` question
|
|
95
|
+
* (activityType + objectName travel on every row); the `customFields`
|
|
96
|
+
* question reads one canonical entry, because the discovery flags and
|
|
97
|
+
* `requiresConnectionForSchema` live there and nowhere thinner.
|
|
98
|
+
*
|
|
99
|
+
* `undefined` means "this library has never heard of the operation", which
|
|
100
|
+
* is a different answer from "unprepared": the caller fails open on it, the
|
|
101
|
+
* same rule every other check callback follows.
|
|
102
|
+
*/
|
|
103
|
+
schemaReadiness(key, action, version, object) {
|
|
104
|
+
const nodeType = `uipath.connector.${key}.${action}`;
|
|
105
|
+
const rows = [];
|
|
106
|
+
for (const layer of this.layers) {
|
|
107
|
+
for (const row of layer.entries) {
|
|
108
|
+
if (row?.nodeType !== nodeType)
|
|
109
|
+
continue;
|
|
110
|
+
if (version && String(row.version) !== version)
|
|
111
|
+
continue;
|
|
112
|
+
rows.push({ row, dir: layer.dir, overlay: layer.overlay === true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (rows.length === 0)
|
|
116
|
+
return undefined;
|
|
117
|
+
const generic = rows.some(({ row }) => row.activityType === 'Generic'
|
|
118
|
+
|| row.activityType === 'GenericExpanded'
|
|
119
|
+
|| row.objectFromDiscovery === true);
|
|
120
|
+
if (object) {
|
|
121
|
+
// An explicit object on a curated op is compile's business, not owed work.
|
|
122
|
+
if (!generic)
|
|
123
|
+
return { kind: 'static', prepared: true };
|
|
124
|
+
if (rows.some(({ row }) => row.objectName === object)) {
|
|
125
|
+
return { kind: 'object', prepared: true };
|
|
126
|
+
}
|
|
127
|
+
const canonical = rows.find(({ row }) => typeof row.objectName === 'string' && row.objectName !== ''
|
|
128
|
+
&& row.objectName.toLowerCase() === object.toLowerCase())?.row.objectName;
|
|
129
|
+
return {
|
|
130
|
+
kind: 'object',
|
|
131
|
+
prepared: false,
|
|
132
|
+
...(canonical ? { canonical: String(canonical) } : {}),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// No object named. A Generic op without one is a different, existing
|
|
136
|
+
// failure (compile refuses it outright); nothing to add here.
|
|
137
|
+
if (generic)
|
|
138
|
+
return { kind: 'static', prepared: true };
|
|
139
|
+
// Curated: is the real field set connection-resolved? Decided FIRST, from
|
|
140
|
+
// the canonical entry, so `kind` is never a label of convenience — an
|
|
141
|
+
// overlay row alone used to earn `customFields` without anyone checking
|
|
142
|
+
// the op resolves anything, which was a lie waiting for a consumer that
|
|
143
|
+
// branches on kind. Read the baked layer's entry where one exists: a
|
|
144
|
+
// prepared overlay copy may not carry the discovery block.
|
|
145
|
+
let entry;
|
|
146
|
+
try {
|
|
147
|
+
const source = rows.find(({ overlay }) => !overlay) ?? rows[0];
|
|
148
|
+
entry = JSON.parse(readFileSync(join(source.dir, source.row.path), 'utf8'));
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
const connectionResolved = entry?.runtime?.requiresConnectionForSchema === true
|
|
154
|
+
|| entry?.connector?.discovery?.hasCustomFieldDiscovery === true;
|
|
155
|
+
if (!connectionResolved)
|
|
156
|
+
return { kind: 'static', prepared: true };
|
|
157
|
+
if (rows.some(({ overlay }) => overlay))
|
|
158
|
+
return { kind: 'customFields', prepared: true };
|
|
159
|
+
let bakedFields = [];
|
|
160
|
+
try {
|
|
161
|
+
bakedFields = this.resolve(key, action, version).fields.map((f) => f.name);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
bakedFields = [];
|
|
165
|
+
}
|
|
166
|
+
return { kind: 'customFields', prepared: false, bakedFields };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Reverse a resolved connector activity back to the symbolic authoring key.
|
|
170
|
+
* Case decompile uses the same library that compile will use, so it never has
|
|
171
|
+
* to guess an action name from a display label.
|
|
172
|
+
*/
|
|
173
|
+
identifyAction(wire) {
|
|
174
|
+
const prefix = `uipath.connector.${wire.connectorKey}.`;
|
|
175
|
+
for (const layer of this.layers) {
|
|
176
|
+
for (const indexed of layer.entries) {
|
|
177
|
+
if (indexed.connectorKey !== wire.connectorKey || !String(indexed.nodeType ?? '').startsWith(prefix))
|
|
178
|
+
continue;
|
|
179
|
+
if (wire.version && indexed.version !== wire.version)
|
|
180
|
+
continue;
|
|
181
|
+
const entry = JSON.parse(readFileSync(join(layer.dir, indexed.path), 'utf8'));
|
|
182
|
+
const operation = entry.operation ?? {};
|
|
183
|
+
if (wire.httpMethod && String(operation.httpMethod ?? '').toUpperCase() !== wire.httpMethod.toUpperCase())
|
|
184
|
+
continue;
|
|
185
|
+
if (wire.endpoint && operation.path !== wire.endpoint)
|
|
186
|
+
continue;
|
|
187
|
+
if (wire.objectName && operation.objectName !== wire.objectName)
|
|
188
|
+
continue;
|
|
189
|
+
const siblings = layer.entries.filter((candidate) => candidate.nodeType === indexed.nodeType);
|
|
190
|
+
return {
|
|
191
|
+
key: wire.connectorKey,
|
|
192
|
+
action: String(indexed.nodeType).slice(prefix.length),
|
|
193
|
+
version: String(indexed.version),
|
|
194
|
+
...(siblings.length > 1 && operation.objectName ? { object: String(operation.objectName) } : {}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
throw new Error(`connector activity not found in library: ${wire.connectorKey} ${wire.httpMethod ?? '?'} ${wire.endpoint ?? '?'}`);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Resolve a connector EVENT: the START trigger
|
|
202
|
+
* (`uipath.connector.trigger.<key>.<event>`, a bpmn:StartEvent — the flow
|
|
203
|
+
* begins when the event fires) or the mid-flow wait
|
|
204
|
+
* (`uipath.connector.event.<key>.<event>`, a bpmn:ReceiveTask — the flow pauses
|
|
205
|
+
* until it fires). The platform ships both for one connector operation, with
|
|
206
|
+
* the same `uiPathActivityTypeId`; the namespace is the only difference.
|
|
207
|
+
*/
|
|
208
|
+
/**
|
|
209
|
+
* The connector's discovery classification, from whichever layer carries it.
|
|
210
|
+
*
|
|
211
|
+
* `resolveType` takes METADATA from the highest-priority layer, and a
|
|
212
|
+
* `prepare-connector` overlay writes `connector: { key }` with no
|
|
213
|
+
* `discovery` block — and a `runtime.requiresConnectionForSchema: false`
|
|
214
|
+
* that CONTRADICTS the baked library (jira is `hasFieldDiscovery: true`
|
|
215
|
+
* there). So reading discovery off the resolved entry silently reclassifies
|
|
216
|
+
* every prepared connector as static, which is exactly backwards: preparing
|
|
217
|
+
* is what an author does on the connectors whose schema comes from the
|
|
218
|
+
* connection. Fields legitimately come from the overlay; this connector-level
|
|
219
|
+
* fact does not, so it is read across layers and the first real block wins.
|
|
220
|
+
*/
|
|
221
|
+
connectorDiscovery(kind, key, event, objectName) {
|
|
222
|
+
const nodeType = `uipath.connector.${kind}.${key}.${event}`;
|
|
223
|
+
for (const layer of this.layers) {
|
|
224
|
+
const rc = this.resolveInLayer(layer, nodeType, undefined, objectName);
|
|
225
|
+
const discovery = rc?.entry
|
|
226
|
+
?.connector?.discovery;
|
|
227
|
+
if (discovery && typeof discovery === 'object' && Object.keys(discovery).length) {
|
|
228
|
+
return discovery;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {};
|
|
232
|
+
}
|
|
233
|
+
resolveEvent(kind, key, event, version, objectName) {
|
|
234
|
+
const nodeType = `uipath.connector.${kind}.${key}.${event}`;
|
|
235
|
+
let rc;
|
|
236
|
+
try {
|
|
237
|
+
rc = this.resolveType(nodeType, version, objectName);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
// The registry dump carries the WAIT form of every connector event and the
|
|
241
|
+
// START form of none (448 `.event.` entries, 0 `.trigger.`; only the curated
|
|
242
|
+
// overlay adds a handful of `.trigger.` pairs). The platform ships both for
|
|
243
|
+
// one operation and the namespace is the only difference, so the start form
|
|
244
|
+
// is DERIVED from its wait sibling here — measured against the platform's
|
|
245
|
+
// own `registry get` answers for both forms (see `deriveTriggerForm`).
|
|
246
|
+
if (kind !== 'trigger')
|
|
247
|
+
throw error;
|
|
248
|
+
let sibling;
|
|
249
|
+
try {
|
|
250
|
+
sibling = this.resolveType(`uipath.connector.event.${key}.${event}`, version, objectName);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
rc = deriveTriggerForm(sibling, nodeType);
|
|
256
|
+
}
|
|
257
|
+
return normalizeEventOperation(rc);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Reverse a resolved connector event payload back to its symbolic event name.
|
|
261
|
+
* Case decompile uses the same library that compile will use, mirroring
|
|
262
|
+
* {@link identifyAction} for connector activities.
|
|
263
|
+
*/
|
|
264
|
+
identifyEvent(kind, wire) {
|
|
265
|
+
const prefix = `uipath.connector.${kind}.${wire.connectorKey}.`;
|
|
266
|
+
for (const layer of this.layers) {
|
|
267
|
+
for (const indexed of layer.entries) {
|
|
268
|
+
if (indexed.connectorKey !== wire.connectorKey || !String(indexed.nodeType ?? '').startsWith(prefix))
|
|
269
|
+
continue;
|
|
270
|
+
if (wire.version && indexed.version !== wire.version)
|
|
271
|
+
continue;
|
|
272
|
+
const entry = JSON.parse(readFileSync(join(layer.dir, indexed.path), 'utf8'));
|
|
273
|
+
const operation = normalizeEventOperation({ entry, v1def: readV1def(join(layer.dir, indexed.path)) }).entry.operation ?? {};
|
|
274
|
+
// A GENERIC event's entry names no object — the wire's object is the
|
|
275
|
+
// author's choice, so it identifies nothing here and comes back as `object`.
|
|
276
|
+
const generic = operation.activityType === 'GenericTrigger';
|
|
277
|
+
if (!generic && wire.objectName && operation.objectName !== wire.objectName)
|
|
278
|
+
continue;
|
|
279
|
+
if (wire.eventOperation && operation.eventOperation !== wire.eventOperation)
|
|
280
|
+
continue;
|
|
281
|
+
if (wire.eventMode && operation.eventMode !== wire.eventMode)
|
|
282
|
+
continue;
|
|
283
|
+
return {
|
|
284
|
+
key: wire.connectorKey,
|
|
285
|
+
event: String(indexed.nodeType).slice(prefix.length),
|
|
286
|
+
version: String(indexed.version),
|
|
287
|
+
...(generic && wire.objectName ? { object: wire.objectName } : {}),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
throw new Error(`connector ${kind} event not found in library: ${wire.connectorKey} `
|
|
292
|
+
+ `${wire.eventOperation ?? '?'} ${wire.objectName ?? '?'}`);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* The EVENT PARAMETERS a connector event operation declares — the `where` half
|
|
296
|
+
* of a subscription — or `undefined` if the library has never heard of the
|
|
297
|
+
* operation.
|
|
298
|
+
*
|
|
299
|
+
* `{ known: true, names: [] }` and `undefined` are DIFFERENT answers, and the
|
|
300
|
+
* difference is the whole reason this method exists: "this operation takes no
|
|
301
|
+
* event parameters" is a fact (HTTP Webhook scopes by the connection, so its
|
|
302
|
+
* only correct `where` is `{}`), while "I could not look it up" is not. Reading
|
|
303
|
+
* an empty list as "none declared, so scope it anyway" is what made
|
|
304
|
+
* `EVENT_NO_SCOPE` reject correct authoring for four of the FIVE event
|
|
305
|
+
* OPERATIONS the deployed corpus contains.
|
|
306
|
+
*
|
|
307
|
+
* Two declaration sites, in order of authority:
|
|
308
|
+
*
|
|
309
|
+
* 1. the v1def's `eventParameters.fields` — the registry's OWN enrichment,
|
|
310
|
+
* returned verbatim by `uip maestro flow registry get <trigger nodeType>
|
|
311
|
+
* --connection-id <id>`. Present only on the `.trigger.` form (IS enriches
|
|
312
|
+
* per node type, not per operation), so the pair is tried together;
|
|
313
|
+
* 2. the entry's `inputSchema.fields`, where a curated def declares them as
|
|
314
|
+
* `eventParameters.<name>` alongside `filter.<name>` — PR 10's convention.
|
|
315
|
+
*
|
|
316
|
+
* Note this is the one family where `inputSchema.fields` IS authoritative. For
|
|
317
|
+
* connector ACTIONS it is a lossy third summary (`readFields`' doc comment: 5,243
|
|
318
|
+
* of 7,029 entries disagree with the union) — but both event node types ship
|
|
319
|
+
* `inputDefinition: {}` in every version, so there is no other declaration to
|
|
320
|
+
* disagree with, and the whole population is the curated overlay's four defs (the
|
|
321
|
+
* raw library snapshot carries ZERO trigger/event entries).
|
|
322
|
+
*/
|
|
323
|
+
eventParameterFields(key, event, objectName) {
|
|
324
|
+
let resolved;
|
|
325
|
+
const declaredOnEitherForm = [];
|
|
326
|
+
const selectors = [];
|
|
327
|
+
let known = false;
|
|
328
|
+
for (const kind of ['event', 'trigger']) {
|
|
329
|
+
let rc;
|
|
330
|
+
try {
|
|
331
|
+
rc = this.resolveEvent(kind, key, event, undefined, objectName);
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
continue; // the sibling may exist even when this one does not
|
|
335
|
+
}
|
|
336
|
+
resolved = rc;
|
|
337
|
+
const fromV1def = rc.v1def
|
|
338
|
+
?.eventParameters?.fields;
|
|
339
|
+
if (Array.isArray(fromV1def)) {
|
|
340
|
+
known = true;
|
|
341
|
+
for (const f of fromV1def) {
|
|
342
|
+
if (typeof f?.name !== 'string' || f.name === '')
|
|
343
|
+
continue;
|
|
344
|
+
declaredOnEitherForm.push(f.name);
|
|
345
|
+
// A REQUIRED parameter outside the event bucket selects the schema
|
|
346
|
+
// rather than scoping the subscription — Jira's `project` and
|
|
347
|
+
// `issuetype` (both required, query-bucket) are what the connector
|
|
348
|
+
// derives an issue's filter and output fields FROM. `uip is triggers
|
|
349
|
+
// describe` takes no parameter values, so a vocabulary fetched
|
|
350
|
+
// without them is a baseline that the real one extends.
|
|
351
|
+
if (f.required && (f.bucket === 'query' || f.bucket === 'path')) {
|
|
352
|
+
selectors.push(f.name);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const fromEntry = rc.entry
|
|
358
|
+
?.inputSchema?.fields;
|
|
359
|
+
if (Array.isArray(fromEntry)) {
|
|
360
|
+
known = true;
|
|
361
|
+
for (const f of fromEntry) {
|
|
362
|
+
if (typeof f?.name !== 'string')
|
|
363
|
+
continue;
|
|
364
|
+
if (f.name.startsWith('eventParameters.'))
|
|
365
|
+
declaredOnEitherForm.push(f.name.slice('eventParameters.'.length));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
if (!resolved)
|
|
370
|
+
return undefined;
|
|
371
|
+
const activityType = String(resolved.entry?.operation?.activityType ?? '');
|
|
372
|
+
const generic = activityType === 'GenericTrigger';
|
|
373
|
+
// The same two questions `schemaReadiness` answers for an activity, asked
|
|
374
|
+
// of an event: does a variant for the named object exist, and has an
|
|
375
|
+
// overlay prepared this event at all. Both are index facts, so they are
|
|
376
|
+
// read the same way — across layers, noting which layer is the overlay.
|
|
377
|
+
let discovery = (resolved.entry
|
|
378
|
+
?.connector?.discovery ?? {});
|
|
379
|
+
if (!Object.keys(discovery).length) {
|
|
380
|
+
for (const kind of ['event', 'trigger']) {
|
|
381
|
+
discovery = this.connectorDiscovery(kind, key, event, objectName);
|
|
382
|
+
if (Object.keys(discovery).length)
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
let objectPrepared = false;
|
|
387
|
+
let overlayPrepared = false;
|
|
388
|
+
for (const layer of this.layers) {
|
|
389
|
+
for (const row of layer.entries) {
|
|
390
|
+
const nodeType = String(row?.nodeType ?? '');
|
|
391
|
+
if (nodeType !== `uipath.connector.event.${key}.${event}`
|
|
392
|
+
&& nodeType !== `uipath.connector.trigger.${key}.${event}`)
|
|
393
|
+
continue;
|
|
394
|
+
if (layer.overlay === true)
|
|
395
|
+
overlayPrepared = true;
|
|
396
|
+
if (objectName && row.objectName === objectName)
|
|
397
|
+
objectPrepared = true;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const objectsFromConnection = Boolean(discovery.hasObjectDiscovery || discovery.hasCustomObjectDiscovery);
|
|
401
|
+
return {
|
|
402
|
+
known,
|
|
403
|
+
names: [...new Set(declaredOnEitherForm)],
|
|
404
|
+
generic,
|
|
405
|
+
object: resolved.objectName,
|
|
406
|
+
selectors: [...new Set(selectors)],
|
|
407
|
+
objectUnprepared: Boolean(generic && objectsFromConnection && objectName && !objectPrepared),
|
|
408
|
+
customFieldsUnprepared: Boolean(discovery.hasCustomFieldDiscovery && !overlayPrepared),
|
|
409
|
+
// Whether a PREPARED vocabulary can still be short. For an ACTIVITY it
|
|
410
|
+
// cannot: `resources describe` takes `-f`/`--action`, so a prepare can
|
|
411
|
+
// supply the parent values and materialize the custom fields. The
|
|
412
|
+
// trigger surface has NEITHER flag — `triggers describe` accepts only
|
|
413
|
+
// connector, operation, object and `--connection-id` — so wherever the
|
|
414
|
+
// schema comes from the connection at all, the event's filter list is a
|
|
415
|
+
// subset no prepare can close. Jira's designer grows past the 2 fields
|
|
416
|
+
// `describe` returns once a project and issue type are chosen; nothing
|
|
417
|
+
// we can call reaches that.
|
|
418
|
+
//
|
|
419
|
+
// ANY discovery flag, not `hasCustomFieldDiscovery` alone: the registry
|
|
420
|
+
// classifies jira `static` with `hasFieldDiscovery: true` and
|
|
421
|
+
// `hasCustomFieldDiscovery: false`, so keying on the custom flag would
|
|
422
|
+
// hard-error on the one connector we have measured growing. This is the
|
|
423
|
+
// same predicate as `runtime.requiresConnectionForSchema`, recomputed
|
|
424
|
+
// from `discovery` because the overlay's copy of that flag is wrong.
|
|
425
|
+
filtersPartial: Object.entries(discovery)
|
|
426
|
+
.some(([name, value]) => name !== 'type' && Boolean(value)),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* The filterable-field names a connector event declares — the vocabulary a
|
|
431
|
+
* `filters` leaf may name.
|
|
432
|
+
*
|
|
433
|
+
* TWO SOURCES, because the platform builds the two kinds of event's filter
|
|
434
|
+
* list differently, and reading one array for both under-advertises the
|
|
435
|
+
* generic form by an order of magnitude:
|
|
436
|
+
*
|
|
437
|
+
* - a CURATED event filters on the fields its definition MARKS
|
|
438
|
+
* (`order: 1` / `isCuratedEventField`), which is what `triggers describe`
|
|
439
|
+
* returns as `FilterFields` and what lands in `v1def.filterFields.fields`.
|
|
440
|
+
* Outlook `email-received`: 15 of the Message resource's 73 fields.
|
|
441
|
+
* - a GENERIC event filters on EVERY field the object supports GET on,
|
|
442
|
+
* which is exactly `triggers describe`'s `OutputFields` and lands in the
|
|
443
|
+
* entry's `outputSchema.fields`. Verified against the connector
|
|
444
|
+
* definitions: jira `issue` 323 fields → 258 GET → 258 output; outlook
|
|
445
|
+
* `Message` 73 → 73 → 73; gmail `Message` 33 → 33 → 33.
|
|
446
|
+
*
|
|
447
|
+
* Reading `filterFields` for both is what made jira `record-created`
|
|
448
|
+
* advertise 2 filterable fields where the designer offers 258, and gmail
|
|
449
|
+
* `new-record-created` advertise none where it offers 33. The union is taken
|
|
450
|
+
* on the generic side rather than a swap, so a connector that populates both
|
|
451
|
+
* (outlook's generic form carries its curated sibling's 15) loses nothing.
|
|
452
|
+
*
|
|
453
|
+
* Only a connection-scoped prepare or an enriched generation writes either
|
|
454
|
+
* array: the thin baked manifests carry none, and for them this answers
|
|
455
|
+
* `undefined` (unknown), never `[]` (known-empty). A GENERIC event's
|
|
456
|
+
* vocabulary is per object, so pass the subscription's `object` to select
|
|
457
|
+
* the prepared variant.
|
|
458
|
+
*/
|
|
459
|
+
eventFilterFields(key, event, objectName) {
|
|
460
|
+
const names = [];
|
|
461
|
+
let known = false;
|
|
462
|
+
for (const kind of ['event', 'trigger']) {
|
|
463
|
+
let rc;
|
|
464
|
+
try {
|
|
465
|
+
rc = this.resolveEvent(kind, key, event, undefined, objectName);
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
continue; // the sibling may exist even when this one does not
|
|
469
|
+
}
|
|
470
|
+
const marked = rc.v1def
|
|
471
|
+
?.filterFields?.fields;
|
|
472
|
+
const generic = String(rc.entry?.operation?.activityType ?? '') === 'GenericTrigger';
|
|
473
|
+
const getFields = generic
|
|
474
|
+
? rc.entry?.outputSchema?.fields
|
|
475
|
+
: undefined;
|
|
476
|
+
const sources = [marked, getFields].filter(Array.isArray);
|
|
477
|
+
if (!sources.length)
|
|
478
|
+
continue;
|
|
479
|
+
known = true;
|
|
480
|
+
for (const source of sources)
|
|
481
|
+
for (const f of source)
|
|
482
|
+
if (typeof f?.name === 'string' && f.name !== '')
|
|
483
|
+
names.push(f.name);
|
|
484
|
+
}
|
|
485
|
+
if (!known)
|
|
486
|
+
return undefined;
|
|
487
|
+
return [...new Set(names)];
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Resolve one library entry for `nodeType`. Two orthogonal concerns compose:
|
|
491
|
+
* - **object selection** within a layer — a GENERIC operation encodes only the
|
|
492
|
+
* verb in its node type and the library expands it into one entry per object,
|
|
493
|
+
* so `objectName` picks which. See `resolveInLayer` for the four cases (#143).
|
|
494
|
+
* - **field union** across layers — a connection-scoped overlay
|
|
495
|
+
* (`prepare-connector`) is searched before the baked library, and the fields
|
|
496
|
+
* of the entries each layer resolves are UNIONED: the overlay can ADD fields
|
|
497
|
+
* but never drop ones the baked library already exposed (C4).
|
|
498
|
+
*/
|
|
499
|
+
resolveType(nodeType, version, objectName) {
|
|
500
|
+
// Resolve the op within each layer (overlay first), then union fields across
|
|
501
|
+
// the layers that carry it.
|
|
502
|
+
const resolved = [];
|
|
503
|
+
for (const layer of this.layers) {
|
|
504
|
+
const r = this.resolveInLayer(layer, nodeType, version, objectName);
|
|
505
|
+
if (r)
|
|
506
|
+
resolved.push(r);
|
|
507
|
+
}
|
|
508
|
+
if (resolved.length) {
|
|
509
|
+
// Metadata comes from the highest-priority layer (the overlay when
|
|
510
|
+
// present); fields are the UNION across all layers.
|
|
511
|
+
const primary = resolved[0];
|
|
512
|
+
if (resolved.length === 1)
|
|
513
|
+
return primary;
|
|
514
|
+
const byName = new Map();
|
|
515
|
+
// Insert lowest-priority (baked) first so a shared field keeps the baked
|
|
516
|
+
// position but takes the overlay's (higher-priority) definition.
|
|
517
|
+
for (let i = resolved.length - 1; i >= 0; i--) {
|
|
518
|
+
for (const f of resolved[i].fields)
|
|
519
|
+
byName.set(f.name, f);
|
|
520
|
+
}
|
|
521
|
+
return { ...primary, fields: [...byName.values()] };
|
|
522
|
+
}
|
|
523
|
+
// Name the two event namespaces when the caller asked for one, so a typo in
|
|
524
|
+
// an event name does not read as "this connector is missing".
|
|
525
|
+
const hint = /^uipath\.connector\.(trigger|event)\./.test(nodeType)
|
|
526
|
+
? ` Event triggers are curated per connector operation — check the event name against `
|
|
527
|
+
+ `the library index (\`index.json\`), and remember the two namespaces: `
|
|
528
|
+
+ `\`…connector.trigger.…\` starts a flow, \`…connector.event.…\` waits mid-flow.`
|
|
529
|
+
: '';
|
|
530
|
+
throw new Error(`connector not in library: ${nodeType}${version ? '@' + version : ''}.${hint}`
|
|
531
|
+
+ this.describeMiss(nodeType));
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Turn a lookup miss into something actionable: which operations this connector
|
|
535
|
+
* DOES carry, the nearest spelling to what was asked for, and where the library
|
|
536
|
+
* being searched actually is.
|
|
537
|
+
*
|
|
538
|
+
* Without this the message was a dead end. In the 2026-09-09 case eval an agent
|
|
539
|
+
* read the tenant's `objectName` (`send-mail-v2`) off `uip maestro case spec`
|
|
540
|
+
* and passed it as the OPERATION slug; the real slug is `send-email`, and
|
|
541
|
+
* `connector not in library: …send-mail-v2.` said nothing that would reveal
|
|
542
|
+
* that. It resorted to grepping the library's JSON out of `node_modules`.
|
|
543
|
+
*/
|
|
544
|
+
describeMiss(nodeType) {
|
|
545
|
+
// `uipath.connector.<key>.<op>`, or `uipath.connector.(trigger|event).<key>.<ev>`
|
|
546
|
+
// for the two event namespaces. No connector key contains a dot.
|
|
547
|
+
const parts = nodeType.split('.');
|
|
548
|
+
const eventNs = parts[2] === 'trigger' || parts[2] === 'event';
|
|
549
|
+
const key = eventNs ? parts[3] : parts[2];
|
|
550
|
+
const asked = (eventNs ? parts.slice(4) : parts.slice(3)).join('.');
|
|
551
|
+
if (!key)
|
|
552
|
+
return '';
|
|
553
|
+
const prefix = eventNs ? `uipath.connector.${parts[2]}.${key}.` : `uipath.connector.${key}.`;
|
|
554
|
+
const ops = [...new Set(this.layers.flatMap((l) => l.entries
|
|
555
|
+
.map((e) => String(e.nodeType ?? ''))
|
|
556
|
+
.filter((t) => t.startsWith(prefix))
|
|
557
|
+
.map((t) => t.slice(prefix.length))))].sort();
|
|
558
|
+
const where = ` Searched: ${this.layers.map((l) => l.dir).join(', ')}.`;
|
|
559
|
+
if (!ops.length) {
|
|
560
|
+
const keys = [...new Set(this.layers.flatMap((l) => l.entries.map((e) => {
|
|
561
|
+
const p = String(e.nodeType ?? '').split('.');
|
|
562
|
+
return (p[2] === 'trigger' || p[2] === 'event' ? p[3] : p[2]) ?? '';
|
|
563
|
+
})))].filter(Boolean).sort();
|
|
564
|
+
const near = nearest(key, keys);
|
|
565
|
+
return ` No connector "${key}" in the library (${keys.length} indexed).`
|
|
566
|
+
+ (near && near !== key ? ` Closest: ${near}.` : '')
|
|
567
|
+
+ where
|
|
568
|
+
+ ' If the connector is newer than the library, refresh it with `uip maestro flow registry pull --force`.';
|
|
569
|
+
}
|
|
570
|
+
const near = asked ? nearest(asked, ops) : undefined;
|
|
571
|
+
const listed = ops.length <= 12
|
|
572
|
+
? `Operations: ${ops.join(', ')}.`
|
|
573
|
+
: `Operations (${ops.length}, first 12): ${ops.slice(0, 12).join(', ')}, … — `
|
|
574
|
+
+ `the full list is every \`nodeType\` under "${key}" in the library's index.json.`;
|
|
575
|
+
return ` Connector "${key}" is present but carries no "${asked}".`
|
|
576
|
+
+ (near ? ` Closest: ${near}.` : '')
|
|
577
|
+
+ ` ${listed}`
|
|
578
|
+
+ where
|
|
579
|
+
+ ' Note the operation slug is the nodeType segment, NOT the tenant\'s `objectName`'
|
|
580
|
+
+ ' — `uip maestro case spec` reports the latter, and the two can differ.'
|
|
581
|
+
+ ' If the operation is newer than the library, refresh it with `uip maestro flow registry pull --force`.';
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Resolve the op WITHIN one layer, selecting the entry by `objectName` for a
|
|
585
|
+
* GENERIC operation. Returns undefined when the layer does not carry the op;
|
|
586
|
+
* throws on an author error (a missing/ambiguous object). The four cases (#143):
|
|
587
|
+
* (1) one entry — resolved (a disagreeing `objectName` is an error, unless the
|
|
588
|
+
* entry is an un-expanded GENERIC — then accepted connection-scoped like case 4);
|
|
589
|
+
* (2) many + matching `objectName` — that entry; (3) many + no `objectName` — a
|
|
590
|
+
* named error listing the objects; (4) many + an unlisted object — accepted with
|
|
591
|
+
* a warning, synthesized from a sibling entry with the object stamped in. A
|
|
592
|
+
* connector's object set belongs to the CONNECTION, not to the registry snapshot,
|
|
593
|
+
* and the platform's designer resolves such objects live.
|
|
594
|
+
*/
|
|
595
|
+
resolveInLayer(layer, nodeType, version, objectName) {
|
|
596
|
+
const matches = layer.entries.filter((e) => e.nodeType === nodeType && (!version || e.version === version));
|
|
597
|
+
if (matches.length === 0)
|
|
598
|
+
return undefined;
|
|
599
|
+
if (objectName !== undefined) {
|
|
600
|
+
const exact = matches.find((e) => e.objectName === objectName);
|
|
601
|
+
if (exact)
|
|
602
|
+
return this.build(layer.dir, exact, nodeType);
|
|
603
|
+
}
|
|
604
|
+
if (matches.length === 1) {
|
|
605
|
+
// A single entry: the object is not a choice. Refuse a DISAGREEING one
|
|
606
|
+
// rather than quietly ignoring it — an author who passed `object` meant it.
|
|
607
|
+
const only = matches[0];
|
|
608
|
+
if (objectName !== undefined && (only.objectName ?? '') !== objectName) {
|
|
609
|
+
// …unless the single entry is a GENERIC op the snapshot ships
|
|
610
|
+
// UN-EXPANDED. Its object set belongs to the CONNECTION, exactly as in
|
|
611
|
+
// case 4 below — the snapshot just carries one object-less entry
|
|
612
|
+
// instead of many expanded ones, so entry COUNT cannot be the
|
|
613
|
+
// discriminator. Older snapshots pre-expanded these ops and this shape
|
|
614
|
+
// only ever reached case 4; a snapshot that stopped expanding turned
|
|
615
|
+
// the same author input into a hard error here, which broke the
|
|
616
|
+
// documented prepare-then-compile path for every Salesforce custom
|
|
617
|
+
// object (the overlay resolved `Company__c`, then the union pass hit
|
|
618
|
+
// this throw in the baked layer).
|
|
619
|
+
// A GENERIC EVENT (`record-created` on Data Fabric, Salesforce, ServiceNow,
|
|
620
|
+
// …) is the same shape one namespace over: one object-less entry whose
|
|
621
|
+
// object belongs to the connection. There the object is not a variant to
|
|
622
|
+
// warn about but the subscription's REQUIRED target, so it is stamped in
|
|
623
|
+
// silently; `check`/`compile` refuse the subscription that omits it.
|
|
624
|
+
if (only.activityType === 'GenericTrigger') {
|
|
625
|
+
return this.build(layer.dir, { ...only, objectName }, nodeType, { objectNameUnverified: true });
|
|
626
|
+
}
|
|
627
|
+
if (only.activityType === 'Generic' || only.objectFromDiscovery === true) {
|
|
628
|
+
process.stderr.write(` warn: connector ${nodeType}: the library carries this operation un-expanded `
|
|
629
|
+
+ `(one object-less entry). Object "${objectName}" is accepted as a connection-scoped `
|
|
630
|
+
+ `object — the schema comes from the generic entry and the tenant resolves the name at `
|
|
631
|
+
+ `dispatch. If it is a typo, compile will not catch it.\n`);
|
|
632
|
+
return this.build(layer.dir, { ...only, objectName }, nodeType, { objectNameUnverified: true });
|
|
633
|
+
}
|
|
634
|
+
throw new Error(`connector ${nodeType} does not have an object "${objectName}": it is a single, `
|
|
635
|
+
+ `non-generic operation addressing ${only.objectName ? `"${only.objectName}"` : 'no object'}. `
|
|
636
|
+
+ `Drop the \`object\` option (it is only for generic operations, whose node type covers many objects).`);
|
|
637
|
+
}
|
|
638
|
+
return this.build(layer.dir, only, nodeType);
|
|
639
|
+
}
|
|
640
|
+
// Ambiguous. Either the author said nothing — which used to resolve to
|
|
641
|
+
// whichever object sorted first, silently — or named an object the library
|
|
642
|
+
// does not list.
|
|
643
|
+
const objects = matches
|
|
644
|
+
.map((e) => e.objectName)
|
|
645
|
+
.filter((o) => typeof o === 'string' && o.length > 0)
|
|
646
|
+
.sort();
|
|
647
|
+
if (objectName === undefined) {
|
|
648
|
+
throw new Error(`connector ${nodeType} is a GENERIC operation covering ${matches.length} objects — `
|
|
649
|
+
+ `say which one with the \`object\` option, e.g. `
|
|
650
|
+
+ `connector('${nodeType.split('.').slice(2, 3)[0]}', '${nodeType.split('.').slice(3).join('.')}', `
|
|
651
|
+
+ `{ … }, { object: '${objects[0] ?? '<object>'}' }). ${describeObjects(objects)}`);
|
|
652
|
+
}
|
|
653
|
+
// Case 4 — an object the snapshot does not list. Accept it, but say so, and
|
|
654
|
+
// offer the nearest known name: a typo and a real tenant-custom object are
|
|
655
|
+
// indistinguishable here, and only the author knows which they meant. Prefer a
|
|
656
|
+
// sibling whose path template is exactly `/<its object>` — the only shape a
|
|
657
|
+
// substitution can re-point cleanly.
|
|
658
|
+
const near = nearest(objectName, objects);
|
|
659
|
+
process.stderr.write(` warn: connector ${nodeType}: object "${objectName}" is not one of the `
|
|
660
|
+
+ `${objects.length} the library lists for it${near ? ` (nearest: "${near}")` : ''}. `
|
|
661
|
+
+ `Accepted as a connection-scoped object — the schema comes from a sibling entry and the `
|
|
662
|
+
+ `tenant resolves the name at dispatch. If it is a typo, compile will not catch it.\n`);
|
|
663
|
+
const sibling = this.pickDerivationSibling(layer.dir, matches);
|
|
664
|
+
return this.build(layer.dir, { ...sibling, objectName }, nodeType, { objectNameUnverified: true });
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Pick the sibling entry a connection-scoped object borrows its schema from:
|
|
668
|
+
* the first whose path template is exactly `/<its own objectName>`, else the
|
|
669
|
+
* first whose template merely CONTAINS its object name, else the first entry.
|
|
670
|
+
* Index order breaks every tie, so the choice is stable across builds.
|
|
671
|
+
*/
|
|
672
|
+
pickDerivationSibling(dir, matches) {
|
|
673
|
+
const templateOf = (e) => {
|
|
674
|
+
try {
|
|
675
|
+
const entry = JSON.parse(readFileSync(join(dir, e.path), 'utf8'));
|
|
676
|
+
return entry.operation?.pathTemplate ?? entry.operation?.path ?? '';
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
return '';
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
const canonical = matches.find((e) => e.objectName && templateOf(e) === `/${e.objectName}`);
|
|
683
|
+
if (canonical)
|
|
684
|
+
return canonical;
|
|
685
|
+
const substitutable = matches.find((e) => e.objectName && templateOf(e).includes(e.objectName));
|
|
686
|
+
return substitutable ?? matches[0];
|
|
687
|
+
}
|
|
688
|
+
build(dir, e, nodeType, opts = {}) {
|
|
689
|
+
const entryPath = join(dir, e.path);
|
|
690
|
+
const entry = JSON.parse(readFileSync(entryPath, 'utf8'));
|
|
691
|
+
const v1defRaw = JSON.parse(readFileSync(entryPath.replace(/\.json$/, '.v1def.json'), 'utf8'));
|
|
692
|
+
const v1def = Array.isArray(v1defRaw) ? v1defRaw[0] : v1defRaw;
|
|
693
|
+
// A stamped object (case 4) reaches here with the index entry's `objectName`
|
|
694
|
+
// replaced but the sibling's FILE on disk — so restate the object and derive
|
|
695
|
+
// the endpoint from the sibling's own template, rather than shipping a node
|
|
696
|
+
// whose `objectName` and `path` name two different objects.
|
|
697
|
+
// The index and the definition file can disagree about which object the
|
|
698
|
+
// operation addresses, and the definition is the stale side: for
|
|
699
|
+
// `uipath-microsoft-outlook365.send-email` the index says `send-mail-v2`
|
|
700
|
+
// (what the tenant's typecache reports, connector 1.39.0) while the
|
|
701
|
+
// definition still says `send-mail` with `/hubs/productivity/send-mail`.
|
|
702
|
+
// `build` reads the definition, so the compiled node dispatched the retired
|
|
703
|
+
// path and the runtime SendTask failed — the whole external-outcome half of
|
|
704
|
+
// one eval task, from a one-token disagreement.
|
|
705
|
+
//
|
|
706
|
+
// The index is the fresher side and the one resolution keys on, so restate
|
|
707
|
+
// the object from it and re-derive the endpoint, exactly as the stamped
|
|
708
|
+
// branch below does. Warned, not silent: it is upstream archive data, so the
|
|
709
|
+
// author should see that the two sources disagreed. Unique in the current
|
|
710
|
+
// archive (1 of 8035 entries).
|
|
711
|
+
if (!opts.objectNameUnverified && entry.operation
|
|
712
|
+
&& typeof e.objectName === 'string' && e.objectName !== ''
|
|
713
|
+
&& typeof entry.operation.objectName === 'string'
|
|
714
|
+
&& entry.operation.objectName !== e.objectName) {
|
|
715
|
+
const stale = entry.operation.objectName;
|
|
716
|
+
const derived = deriveObjectPath(entry.operation, e.objectName);
|
|
717
|
+
process.stderr.write(` warn: connector ${nodeType}: the library index says objectName "${e.objectName}" but its `
|
|
718
|
+
+ `definition file says "${stale}". Using the index and deriving the endpoint`
|
|
719
|
+
+ (derived.path ? ` (${entry.operation.path} -> ${derived.path})` : '')
|
|
720
|
+
+ `; the definition is the stale side when these disagree.\n`);
|
|
721
|
+
entry.operation = { ...entry.operation, objectName: e.objectName, ...derived };
|
|
722
|
+
}
|
|
723
|
+
if (opts.objectNameUnverified && entry.operation) {
|
|
724
|
+
entry.operation = {
|
|
725
|
+
...entry.operation,
|
|
726
|
+
objectName: e.objectName,
|
|
727
|
+
...deriveObjectPath(entry.operation, e.objectName),
|
|
728
|
+
};
|
|
729
|
+
delete entry.operation.objectDisplayName; // the sibling's label, not this object's
|
|
730
|
+
}
|
|
731
|
+
const objectName = entry.operation?.objectName ?? '';
|
|
732
|
+
return {
|
|
733
|
+
nodeType,
|
|
734
|
+
version: e.version,
|
|
735
|
+
connectorKey: entry.connector?.key ?? '',
|
|
736
|
+
connectorName: entry.connector?.name ?? '',
|
|
737
|
+
httpMethod: entry.operation?.httpMethod ?? 'POST',
|
|
738
|
+
endpoint: entry.operation?.pathTemplate ?? entry.operation?.path ?? '',
|
|
739
|
+
activityTypeId: findKey(v1def, 'uiPathActivityTypeId'),
|
|
740
|
+
v1def,
|
|
741
|
+
entry,
|
|
742
|
+
fields: readFields(v1def, entry),
|
|
743
|
+
multipartParts: readMultipartParts(entry),
|
|
744
|
+
objectName,
|
|
745
|
+
...(opts.objectNameUnverified ? { objectNameUnverified: true } : {}),
|
|
746
|
+
...this.resolveListEnvelope(entry, nodeType, objectName),
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* The measured LIST envelope for one resolved operation, if any.
|
|
751
|
+
*
|
|
752
|
+
* Two sources, entry-first: an OVERLAY entry may declare `envelope` on the
|
|
753
|
+
* entry JSON (a `prepare-connector` result that measured it live), and that
|
|
754
|
+
* wins; otherwise the shipped ledger beside `index.json`. Nothing is inferred
|
|
755
|
+
* — an operation the ledger has never measured comes back undeclared, and
|
|
756
|
+
* every consumer treats that as "behave exactly as before" (#156).
|
|
757
|
+
*
|
|
758
|
+
* Fenced to the LIST verb: the envelope question is only about a collection,
|
|
759
|
+
* and a row against a non-List operation would be a ledger mistake, so it is
|
|
760
|
+
* ignored rather than honoured.
|
|
761
|
+
*/
|
|
762
|
+
resolveListEnvelope(entry, nodeType, objectName) {
|
|
763
|
+
if (entry?.operation?.name !== 'List')
|
|
764
|
+
return {};
|
|
765
|
+
if (entry.envelope !== undefined) {
|
|
766
|
+
return { listEnvelope: {
|
|
767
|
+
envelope: entry.envelope ?? null,
|
|
768
|
+
source: typeof entry.envelopeSource === 'string' ? entry.envelopeSource : 'the library entry',
|
|
769
|
+
tier: typeof entry.envelopeTier === 'string' ? entry.envelopeTier : 'A',
|
|
770
|
+
} };
|
|
771
|
+
}
|
|
772
|
+
const row = lookupListEnvelope(this.envelopeRows, nodeType, objectName || undefined);
|
|
773
|
+
return row
|
|
774
|
+
? { listEnvelope: { envelope: row.envelope ?? null, source: row.source, tier: row.tier } }
|
|
775
|
+
: {};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Re-point a sibling entry's path template at a different object.
|
|
780
|
+
*
|
|
781
|
+
* The endpoint is **not** dispatch input — dispatch is
|
|
782
|
+
* `uip is resources run <verb> <connectorKey> <objectName>` and the platform's
|
|
783
|
+
* adapter resolves the object server-side (verified live against the tenant's
|
|
784
|
+
* ServiceNow connection) — but it IS in the artifact, so a node claiming
|
|
785
|
+
* `objectName: "acr_user"` must not also carry `path: "/advanced_curated_incident"`.
|
|
786
|
+
*
|
|
787
|
+
* Substitution only, never invention: if the sibling's template contains its own
|
|
788
|
+
* object name, swap it (`/advanced_curated_incident` → `/acr_user`, which is
|
|
789
|
+
* byte-identical to what the platform's own designer emitted for `acr_user` in
|
|
790
|
+
* all four green `generic_dynamic_node` nightlies). If it does not — 1,534 of the
|
|
791
|
+
* 5,693 generic entries have an object that never appears in their path, e.g.
|
|
792
|
+
* `Campaigns::Contact` → `/Campaigns/{id}/Contact/{contactId}` — the template is
|
|
793
|
+
* left alone and the warning already told the author the schema is a sibling's.
|
|
794
|
+
*/
|
|
795
|
+
function deriveObjectPath(operation, objectName) {
|
|
796
|
+
const sibling = operation.objectName ?? '';
|
|
797
|
+
const out = {};
|
|
798
|
+
if (!sibling || !objectName)
|
|
799
|
+
return out;
|
|
800
|
+
for (const key of ['pathTemplate', 'path']) {
|
|
801
|
+
const tmpl = operation[key];
|
|
802
|
+
if (typeof tmpl === 'string' && tmpl.includes(sibling)) {
|
|
803
|
+
out[key] = tmpl.split(sibling).join(objectName);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return out;
|
|
807
|
+
}
|
|
808
|
+
/** Read the `.v1def.json` sidecar beside a library entry file. */
|
|
809
|
+
function readV1def(entryPath) {
|
|
810
|
+
const raw = JSON.parse(readFileSync(entryPath.replace(/\.json$/, '.v1def.json'), 'utf8'));
|
|
811
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* The platform's `connectorDetail.configuration` blob — the designer's own
|
|
815
|
+
* declaration of an event operation (`eventOperation`, `eventMode`, `activityType`,
|
|
816
|
+
* …), carried as a JSON STRING inside the v1def's form. It is the one place a raw
|
|
817
|
+
* registry entry states the event's mode, so it is read here rather than guessed.
|
|
818
|
+
*/
|
|
819
|
+
function connectorDetailConfiguration(v1def) {
|
|
820
|
+
const sections = Array.isArray(v1def?.form?.sections) ? v1def.form.sections : [];
|
|
821
|
+
for (const section of sections) {
|
|
822
|
+
for (const field of Array.isArray(section?.fields) ? section.fields : []) {
|
|
823
|
+
const raw = field?.componentProps?.connectorDetail?.configuration;
|
|
824
|
+
if (typeof raw !== 'string')
|
|
825
|
+
continue;
|
|
826
|
+
try {
|
|
827
|
+
const parsed = JSON.parse(raw);
|
|
828
|
+
if (parsed && typeof parsed === 'object')
|
|
829
|
+
return parsed;
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
// not JSON — fall through to the next field
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return {};
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Fill in what a raw registry EVENT entry leaves out, so every consumer of an
|
|
840
|
+
* event operation reads one complete shape.
|
|
841
|
+
*
|
|
842
|
+
* The 448 `.event.` entries of the registry dump carry `operation.name`
|
|
843
|
+
* (`CREATED`) but neither `eventOperation` nor `eventMode` — the curated overlay's
|
|
844
|
+
* four pairs are the only entries that do. Both are stated by the platform's own
|
|
845
|
+
* `connectorDetail.configuration` blob in the v1def (`{"eventOperation":"CREATED",
|
|
846
|
+
* "eventMode":"polling",…}`), which is what the designer reads too; the
|
|
847
|
+
* operation name is the same value under its registry spelling. A node emitted
|
|
848
|
+
* without them fails `flow validate` ("eventMode is required for trigger nodes")
|
|
849
|
+
* and the platform's own dispatcher refuses it (`buildEventCall`).
|
|
850
|
+
*
|
|
851
|
+
* `objectDisplayName` follows the stamped object of a generic event — the entry's
|
|
852
|
+
* empty label is a sibling's, not this object's.
|
|
853
|
+
*/
|
|
854
|
+
function normalizeEventOperation(rc) {
|
|
855
|
+
const operation = rc.entry?.operation;
|
|
856
|
+
if (!operation || typeof operation !== 'object')
|
|
857
|
+
return rc;
|
|
858
|
+
const activityType = String(operation.activityType ?? '');
|
|
859
|
+
if (!/Trigger|WaitFor/.test(activityType) && !/^uipath\.connector\.(trigger|event)\./.test(rc.nodeType ?? ''))
|
|
860
|
+
return rc;
|
|
861
|
+
const declared = connectorDetailConfiguration(rc.v1def);
|
|
862
|
+
const eventOperation = operation.eventOperation
|
|
863
|
+
?? (typeof declared.eventOperation === 'string' ? declared.eventOperation : undefined)
|
|
864
|
+
?? (typeof operation.name === 'string' && operation.name !== '' ? operation.name : undefined);
|
|
865
|
+
const eventMode = operation.eventMode
|
|
866
|
+
?? (typeof declared.eventMode === 'string' ? declared.eventMode : undefined)
|
|
867
|
+
?? (typeof rc.v1def?.eventMode === 'string' ? rc.v1def.eventMode : undefined);
|
|
868
|
+
const normalized = {
|
|
869
|
+
...operation,
|
|
870
|
+
...(eventOperation !== undefined ? { eventOperation } : {}),
|
|
871
|
+
...(eventMode !== undefined ? { eventMode } : {}),
|
|
872
|
+
...(rc.objectNameUnverified && operation.objectName ? { objectDisplayName: operation.objectName } : {}),
|
|
873
|
+
};
|
|
874
|
+
return { ...rc, entry: { ...rc.entry, operation: normalized } };
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Derive a connector event's START form (`uipath.connector.trigger.<key>.<event>`)
|
|
878
|
+
* from its WAIT form (`uipath.connector.event.<key>.<event>`).
|
|
879
|
+
*
|
|
880
|
+
* The platform serves both for one operation and the differences are structural,
|
|
881
|
+
* never per-connector — measured 2026-09-02 by asking `uip maestro flow registry
|
|
882
|
+
* get` for both forms of Data Fabric `record-created` (a GENERIC event) and
|
|
883
|
+
* comparing them with the curated OneDrive/Outlook/HTTP-webhook pairs:
|
|
884
|
+
*
|
|
885
|
+
* category `connector.<pkg>` → `trigger.<pkg>`
|
|
886
|
+
* tags `event` → `trigger`
|
|
887
|
+
* model.type `bpmn:ReceiveTask` → `bpmn:StartEvent`
|
|
888
|
+
* model.serviceType `Intsvc.WaitForEvent` → `Intsvc.EventTrigger`
|
|
889
|
+
* model +`entryPointId: true`, +`eventDefinition: bpmn:MessageEventDefinition`
|
|
890
|
+
* handles input + output + error → one output handle (a start has no
|
|
891
|
+
* input and no error port)
|
|
892
|
+
* outputDefinition output + error → output only ("…of the connector trigger")
|
|
893
|
+
* supportsErrorHandling, inputDefaults, debug → dropped
|
|
894
|
+
* activityType `CuratedWaitFor` → `CuratedTrigger` (a generic one stays `GenericTrigger`)
|
|
895
|
+
* runtime.bpmnType / serviceType → the start values above
|
|
896
|
+
*
|
|
897
|
+
* `model.bindings`, `model.context`, `display` (plus the start's `shape: circle`),
|
|
898
|
+
* the form (same `uiPathActivityTypeId`) and the version are kept verbatim.
|
|
899
|
+
*/
|
|
900
|
+
function deriveTriggerForm(sibling, triggerType) {
|
|
901
|
+
const entry = JSON.parse(JSON.stringify(sibling.entry ?? {}));
|
|
902
|
+
const v1def = JSON.parse(JSON.stringify(sibling.v1def ?? {}));
|
|
903
|
+
const retag = (tags) => Array.isArray(tags) ? tags.map((t) => (t === 'event' ? 'trigger' : t)) : tags;
|
|
904
|
+
const recategorize = (category) => typeof category === 'string' ? category.replace(/^connector\./, 'trigger.') : category;
|
|
905
|
+
entry.nodeType = triggerType;
|
|
906
|
+
entry.category = recategorize(entry.category);
|
|
907
|
+
entry.tags = retag(entry.tags);
|
|
908
|
+
if (entry.operation && typeof entry.operation === 'object') {
|
|
909
|
+
if (entry.operation.activityType === 'CuratedWaitFor')
|
|
910
|
+
entry.operation.activityType = 'CuratedTrigger';
|
|
911
|
+
}
|
|
912
|
+
entry.runtime = { ...(entry.runtime ?? {}), bpmnType: 'bpmn:StartEvent', serviceType: 'Intsvc.EventTrigger' };
|
|
913
|
+
v1def.nodeType = triggerType;
|
|
914
|
+
v1def.category = recategorize(v1def.category);
|
|
915
|
+
v1def.tags = retag(v1def.tags);
|
|
916
|
+
delete v1def.supportsErrorHandling;
|
|
917
|
+
delete v1def.inputDefaults;
|
|
918
|
+
delete v1def.debug;
|
|
919
|
+
v1def.display = { ...(v1def.display ?? {}), shape: 'circle' };
|
|
920
|
+
v1def.handleConfiguration = [{
|
|
921
|
+
position: 'right',
|
|
922
|
+
handles: [{
|
|
923
|
+
id: 'output', type: 'source', handleType: 'output', showButton: true,
|
|
924
|
+
constraints: { forbiddenTargetCategories: ['trigger'] },
|
|
925
|
+
}],
|
|
926
|
+
visible: true,
|
|
927
|
+
}];
|
|
928
|
+
const model = v1def.model ?? {};
|
|
929
|
+
v1def.model = {
|
|
930
|
+
type: 'bpmn:StartEvent',
|
|
931
|
+
entryPointId: true,
|
|
932
|
+
serviceType: 'Intsvc.EventTrigger',
|
|
933
|
+
eventDefinition: 'bpmn:MessageEventDefinition',
|
|
934
|
+
...(model.bindings !== undefined ? { bindings: model.bindings } : {}),
|
|
935
|
+
...(model.context !== undefined ? { context: model.context } : {}),
|
|
936
|
+
};
|
|
937
|
+
v1def.inputDefinition = {};
|
|
938
|
+
const output = v1def.outputDefinition?.output;
|
|
939
|
+
v1def.outputDefinition = {
|
|
940
|
+
output: {
|
|
941
|
+
type: 'object',
|
|
942
|
+
description: 'The return value of the connector trigger.',
|
|
943
|
+
source: '=result.response',
|
|
944
|
+
var: 'output',
|
|
945
|
+
...(output && typeof output === 'object' && output.schema !== undefined ? { schema: output.schema } : {}),
|
|
946
|
+
},
|
|
947
|
+
};
|
|
948
|
+
return { ...sibling, nodeType: triggerType, entry, v1def };
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Name the objects an ambiguous nodeType covers, in a message a human can act on.
|
|
952
|
+
* Whole list when it is short; a sample plus the count when it is
|
|
953
|
+
* `uipath-oracle-netsuite.update-record` (197 of them) — a 197-name error is a
|
|
954
|
+
* wall of text nobody reads, and the library index is the full answer.
|
|
955
|
+
*/
|
|
956
|
+
function describeObjects(objects) {
|
|
957
|
+
if (objects.length === 0)
|
|
958
|
+
return 'The library lists no object names for it.';
|
|
959
|
+
if (objects.length <= 12)
|
|
960
|
+
return `Objects: ${objects.join(', ')}.`;
|
|
961
|
+
return (`Objects (${objects.length}, first 12): ${objects.slice(0, 12).join(', ')}, … — `
|
|
962
|
+
+ `the full list is every \`objectName\` for this nodeType in the library's index.json.`);
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* The closest known object name to `want`, or undefined when nothing is close.
|
|
966
|
+
*
|
|
967
|
+
* A case-insensitive hit is reported as-is (the index has **zero** case-folded
|
|
968
|
+
* collisions across all 448 ambiguous nodeTypes, so it is unambiguous); otherwise
|
|
969
|
+
* an edit distance within a third of the name's length, which catches a typo
|
|
970
|
+
* (`acr_users`) without pretending `incident` is near `acr_user`.
|
|
971
|
+
*/
|
|
972
|
+
function nearest(want, objects) {
|
|
973
|
+
const ci = objects.find((o) => o.toLowerCase() === want.toLowerCase());
|
|
974
|
+
if (ci)
|
|
975
|
+
return ci;
|
|
976
|
+
let best;
|
|
977
|
+
let bestD = Math.max(2, Math.floor(want.length / 3));
|
|
978
|
+
for (const o of objects) {
|
|
979
|
+
const d = editDistance(want.toLowerCase(), o.toLowerCase());
|
|
980
|
+
if (d <= bestD) {
|
|
981
|
+
bestD = d;
|
|
982
|
+
best = o;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return best;
|
|
986
|
+
}
|
|
987
|
+
/** Levenshtein distance, two rows. Short strings only (object names). */
|
|
988
|
+
function editDistance(a, b) {
|
|
989
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
990
|
+
for (let i = 1; i <= a.length; i++) {
|
|
991
|
+
const cur = [i];
|
|
992
|
+
for (let j = 1; j <= b.length; j++) {
|
|
993
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
994
|
+
}
|
|
995
|
+
prev = cur;
|
|
996
|
+
}
|
|
997
|
+
return prev[b.length];
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* The accepted input vocabulary for one connector operation, with each input's
|
|
1001
|
+
* wire LOCATION.
|
|
1002
|
+
*
|
|
1003
|
+
* A connector operation declares its inputs in two places, and reading only the
|
|
1004
|
+
* first made **515 of the library's 1,336 curated operations (38.5%)**
|
|
1005
|
+
* unauthorable — every "get one by id", every "list with a filter"
|
|
1006
|
+
* ([#132](https://github.com/UiPath/flow-builder-sdk/issues/132)):
|
|
1007
|
+
*
|
|
1008
|
+
* 1. **`<action>@<ver>.v1def.json` → `inputDefinition.fields`** — the request
|
|
1009
|
+
* BODY. For the 515 this array is literally `[]`.
|
|
1010
|
+
* 2. **`<action>@<ver>.json` → `operation.parameters`** — the QUERY and PATH
|
|
1011
|
+
* parameters, each carrying `type: "query" | "path"` and a `dataType`:
|
|
1012
|
+
*
|
|
1013
|
+
* ```jsonc
|
|
1014
|
+
* // uipath-atlassian-jira/get-issue@1.0.0.json
|
|
1015
|
+
* "operation": { "httpMethod": "GETBYID", "pathTemplate": "/curated_get_issue/{issueId}",
|
|
1016
|
+
* "parameters": [ { "name": "project", "type": "query", "dataType": "string", "required": true },
|
|
1017
|
+
* { "name": "issuetype","type": "query", "dataType": "string", "required": true },
|
|
1018
|
+
* { "name": "issueId", "type": "path", "dataType": "string", "required": true } ] }
|
|
1019
|
+
* ```
|
|
1020
|
+
*
|
|
1021
|
+
* **Why not `entry.inputSchema.fields`**, which looks like the merged list:
|
|
1022
|
+
* measured over the shipped library, **5,243 of 7,029 entries disagree** with
|
|
1023
|
+
* the union — e.g. `uipath-act-act365.delete-record` declares a `contactsId`
|
|
1024
|
+
* path parameter and an EMPTY `inputSchema.fields`. It is a third, lossy
|
|
1025
|
+
* summary, so the union of the two authoritative declarations is what we read.
|
|
1026
|
+
*
|
|
1027
|
+
* **A name in both wins as the PARAMETER** (its location is the wire truth).
|
|
1028
|
+
* Three curated ops do this and the endpoint template settles it: Trello
|
|
1029
|
+
* `add-card-attachment` declares `id` as a body field *and* as the `{id}` of
|
|
1030
|
+
* `/cards/{id}/attachments`.
|
|
1031
|
+
*
|
|
1032
|
+
* Multipart is a Flow-artifact transport, not a claim about the separate
|
|
1033
|
+
* `uip is resources run` command. The product designer emits it as
|
|
1034
|
+
* `detail.multipartParameters`, including binary expressions between connector
|
|
1035
|
+
* nodes, so authoring must preserve it even though that CLI command has no
|
|
1036
|
+
* multipart flag.
|
|
1037
|
+
*
|
|
1038
|
+
* **A multipart part is an author input only when it is a FILE.** The platform
|
|
1039
|
+
* splits a multipart operation the same way it splits every other one: the
|
|
1040
|
+
* author's fields are `inputDefinition.fields` (Teams `body.content`, Gmail
|
|
1041
|
+
* `Body`, GenAI `prompt`), and the `string`-typed multipart parameter (`body`,
|
|
1042
|
+
* `RagRequest`) is the transport CONTAINER the runtime composes from
|
|
1043
|
+
* `bodyParameters`. Measured on 413 designer-authored multipart nodes in the eval
|
|
1044
|
+
* archive: 0 carry a value on the string part, while `file` parts do (Slack
|
|
1045
|
+
* `send-file-to-channel` 70/72, Data Service `upload-file-to-record-field`
|
|
1046
|
+
* 20/20). Reading the string part as a required author field did three wrong
|
|
1047
|
+
* things at once: the generated docs advertised `body` as the REQUIRED input
|
|
1048
|
+
* beside the real `body.content`; `compile` refused the documented fields
|
|
1049
|
+
* (`'body.content'` → `missing required input "body"`, `body: { content }` →
|
|
1050
|
+
* `expected string, got object`); and the one shape it accepted, `body: 'text'`,
|
|
1051
|
+
* emitted `multipartParameters[body].value`, which the runtime ignores — verified
|
|
1052
|
+
* live 2026-09-02 on GenAI `content-generation`: the part value never reached the
|
|
1053
|
+
* model, `bodyParameters.prompt` did. So a non-file part goes to
|
|
1054
|
+
* {@link readMultipartParts}, not here, and its `required` flag does not bind the
|
|
1055
|
+
* author (the container is always sent; its fields carry their own flags).
|
|
1056
|
+
*/
|
|
1057
|
+
function readFields(v1def, entry) {
|
|
1058
|
+
const params = authorParameters(entry);
|
|
1059
|
+
// LAST RESORT: `inputSchema.fields`, and only when the two authoritative
|
|
1060
|
+
// declarations above are BOTH empty.
|
|
1061
|
+
//
|
|
1062
|
+
// The doc comment's reason for distrusting `inputSchema.fields` stands — it is a
|
|
1063
|
+
// lossy third summary and 5,243 of 7,029 entries disagree with the union — but it
|
|
1064
|
+
// argues for PREFERRING the union, not for ignoring the summary when the union
|
|
1065
|
+
// says nothing at all. Where both are empty there is no disagreement to lose:
|
|
1066
|
+
// the choice is between the summary's fields and NO fields.
|
|
1067
|
+
//
|
|
1068
|
+
// No fields is not a neutral outcome, because `convert_library_to_md.py` reads
|
|
1069
|
+
// `inputSchema.fields` directly. So the markdown — the artifact the skill tells an
|
|
1070
|
+
// agent to author from — advertises inputs that compile then rejects as
|
|
1071
|
+
// `unknown input`, reporting `Required fields: (none required)`. Measured over the
|
|
1072
|
+
// shipped library: **804 ops, 43,991 field declarations**, including Jira
|
|
1073
|
+
// `insert-record`/`create_issue`, whose 83 documented fields (`fields.summary`,
|
|
1074
|
+
// and the REQUIRED `fields.project.key`) were all invisible to compile. An author
|
|
1075
|
+
// following the documentation exactly could not compile, and the diagnostic
|
|
1076
|
+
// actively misled — it named no required fields for an operation that has two.
|
|
1077
|
+
//
|
|
1078
|
+
// This does not weaken the unknown-field guard: a name in neither the union nor
|
|
1079
|
+
// the summary is still refused, which is what catches a typo.
|
|
1080
|
+
if ((v1def?.inputDefinition?.fields ?? []).length === 0 && params.length === 0) {
|
|
1081
|
+
const schema = entry?.inputSchema?.fields;
|
|
1082
|
+
if (Array.isArray(schema) && schema.length > 0) {
|
|
1083
|
+
return schema.map((f) => ({ ...f, location: 'body' }));
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const out = (v1def?.inputDefinition?.fields ?? []).map((f) => ({ ...f, location: 'body' }));
|
|
1087
|
+
for (const p of params) {
|
|
1088
|
+
const name = typeof p?.name === 'string' ? p.name : undefined;
|
|
1089
|
+
const location = PARAM_LOCATIONS[p?.type];
|
|
1090
|
+
if (!name || !location)
|
|
1091
|
+
continue;
|
|
1092
|
+
const field = {
|
|
1093
|
+
name,
|
|
1094
|
+
type: scalarType(p.dataType),
|
|
1095
|
+
required: p.required === true,
|
|
1096
|
+
location,
|
|
1097
|
+
...(p.displayName ? { displayName: p.displayName } : {}),
|
|
1098
|
+
...(p.description ? { description: p.description } : {}),
|
|
1099
|
+
// A QUERY parameter can declare a lookup too (Slack's `send_as` does).
|
|
1100
|
+
// Rebuilding the field from scratch used to drop it.
|
|
1101
|
+
...(p.reference && typeof p.reference === 'object'
|
|
1102
|
+
? { reference: p.reference }
|
|
1103
|
+
: {}),
|
|
1104
|
+
};
|
|
1105
|
+
// The parameter declaration is authoritative on location; replace a
|
|
1106
|
+
// same-named body field rather than shadowing it with a duplicate entry.
|
|
1107
|
+
const at = out.findIndex((f) => f.name === name);
|
|
1108
|
+
if (at >= 0)
|
|
1109
|
+
out[at] = field;
|
|
1110
|
+
else
|
|
1111
|
+
out.push(field);
|
|
1112
|
+
}
|
|
1113
|
+
return out;
|
|
1114
|
+
}
|
|
1115
|
+
/** `operation.parameters` minus the multipart CONTAINER parts — the ones an author fills. */
|
|
1116
|
+
function authorParameters(entry) {
|
|
1117
|
+
const params = Array.isArray(entry?.operation?.parameters) ? entry.operation.parameters : [];
|
|
1118
|
+
return params.filter((p) => !isContainerPart(p));
|
|
1119
|
+
}
|
|
1120
|
+
/** A multipart parameter that is not a file: the runtime composes it, the author does not fill it. */
|
|
1121
|
+
function isContainerPart(p) {
|
|
1122
|
+
return p?.type === 'multipart' && String(p?.dataType ?? 'string').toLowerCase() !== 'file';
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Every multipart part the operation declares, in declaration order, as the
|
|
1126
|
+
* platform lists them under `detail.multipartParameters` — the container part
|
|
1127
|
+
* (valueless) and the file parts (which take the author's value from `fields`).
|
|
1128
|
+
*/
|
|
1129
|
+
function readMultipartParts(entry) {
|
|
1130
|
+
const params = Array.isArray(entry?.operation?.parameters) ? entry.operation.parameters : [];
|
|
1131
|
+
const out = [];
|
|
1132
|
+
for (const p of params) {
|
|
1133
|
+
if (p?.type !== 'multipart' || typeof p?.name !== 'string' || !p.name)
|
|
1134
|
+
continue;
|
|
1135
|
+
if (out.some((x) => x.name === p.name))
|
|
1136
|
+
continue;
|
|
1137
|
+
out.push({ name: p.name, dataType: typeof p.dataType === 'string' && p.dataType ? p.dataType : 'string' });
|
|
1138
|
+
}
|
|
1139
|
+
return out;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* `operation.parameters[].type` → the wire slot. The whole vocabulary the
|
|
1143
|
+
* shipped library uses is `query` · `path` · `multipart`. Anything else is
|
|
1144
|
+
* skipped: an input we cannot place is an input we must not accept.
|
|
1145
|
+
*/
|
|
1146
|
+
const PARAM_LOCATIONS = {
|
|
1147
|
+
query: 'query',
|
|
1148
|
+
path: 'path',
|
|
1149
|
+
multipart: 'multipart',
|
|
1150
|
+
};
|
|
1151
|
+
/**
|
|
1152
|
+
* A parameter's `dataType` → the three scalar names `compile`'s literal
|
|
1153
|
+
* type-check understands. Measured across the curated library: `string` 2,009 ·
|
|
1154
|
+
* `integer` 230 · `boolean` 160 · `number` 3 · `double` 2.
|
|
1155
|
+
*/
|
|
1156
|
+
function scalarType(dataType) {
|
|
1157
|
+
switch (dataType) {
|
|
1158
|
+
case 'integer':
|
|
1159
|
+
case 'number':
|
|
1160
|
+
case 'double':
|
|
1161
|
+
case 'float':
|
|
1162
|
+
case 'long': return 'number';
|
|
1163
|
+
case 'boolean': return 'boolean';
|
|
1164
|
+
case 'string': return 'string';
|
|
1165
|
+
default: return typeof dataType === 'string' ? dataType : undefined;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
/** Depth-first search for the first value of `key` anywhere in `obj`. */
|
|
1169
|
+
function findKey(obj, key) {
|
|
1170
|
+
if (obj == null || typeof obj !== 'object')
|
|
1171
|
+
return undefined;
|
|
1172
|
+
if (key in obj) {
|
|
1173
|
+
const v = obj[key];
|
|
1174
|
+
if (typeof v === 'string')
|
|
1175
|
+
return v;
|
|
1176
|
+
}
|
|
1177
|
+
for (const val of Object.values(obj)) {
|
|
1178
|
+
const found = findKey(val, key);
|
|
1179
|
+
if (found)
|
|
1180
|
+
return found;
|
|
1181
|
+
}
|
|
1182
|
+
return undefined;
|
|
1183
|
+
}
|