@uipath/maestro-builder-sdk 6.4.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Resolve a connector lookup field against a live connection.
|
|
3
|
-
|
|
4
|
-
`prepare --resolve channel:profile.email=dustin@example.com` turns a
|
|
5
|
-
human-meaningful value into the opaque id the connector wants, and records the
|
|
6
|
-
answer so `compile` can read it offline. This module is the retrieval half.
|
|
7
|
-
|
|
8
|
-
WHY IT LIVES HERE AND NOT IN COMPILE. Resolution needs the network; compile must
|
|
9
|
-
not have it. The eval harness runs `check` and `compile` with no `uip` login,
|
|
10
|
-
and the byte-lock gates require that the same source produces the same bytes —
|
|
11
|
-
a compile that made live calls would make those gates depend on tenant state and
|
|
12
|
-
they would stop meaning anything. So the expensive call happens once, in an
|
|
13
|
-
explicit command, and its result lands in a file that is diffable and reviewable.
|
|
14
|
-
|
|
15
|
-
THE STRATEGY IS DATA, NOT CODE. `reference` says how to retrieve the collection:
|
|
16
|
-
a `filterPattern` means one request; its absence means paging; `childPath` means
|
|
17
|
-
a tree; `dependsOn` means another field must resolve first. That is why this is a
|
|
18
|
-
generator reading metadata rather than 103 hand-written connector helpers.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
from __future__ import annotations
|
|
22
|
-
|
|
23
|
-
import json
|
|
24
|
-
import subprocess
|
|
25
|
-
import sys
|
|
26
|
-
|
|
27
|
-
__all__ = [
|
|
28
|
-
"LookupExhausted",
|
|
29
|
-
"ResolutionError",
|
|
30
|
-
"parse_resolve_flag",
|
|
31
|
-
"resolve_one",
|
|
32
|
-
"order_resolutions",
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
# A scan has to stop somewhere, and the caps must be set against the MEASURED
|
|
36
|
-
# page size rather than a guess. Integration Service serves **1,000 records per
|
|
37
|
-
# page** for a curated collection (verified against a live Slack connection:
|
|
38
|
-
# `curated_channels` returns 1000 with `HasMore: "true"`).
|
|
39
|
-
#
|
|
40
|
-
# The first version of this capped at 400 records — a number chosen for how many
|
|
41
|
-
# rows a person might scroll, which is the wrong unit entirely. It meant the loop
|
|
42
|
-
# broke partway through page ONE and never paginated at all. The eval task built
|
|
43
|
-
# to exercise this puts its target channel on page 3 (record ~2,001), so the
|
|
44
|
-
# resolver reported "not found" for a channel that is plainly there, and the
|
|
45
|
-
# agent fell back to hand-rolling the paging this exists to do for it.
|
|
46
|
-
#
|
|
47
|
-
# 25 pages is ~30s of requests in the worst case, spent once, in an explicit
|
|
48
|
-
# command whose whole job is this. A collection with a `filterPattern` never
|
|
49
|
-
# reaches the scan at all.
|
|
50
|
-
PAGE_SIZE_HINT = 1000
|
|
51
|
-
MAX_SCANNED = 25 * PAGE_SIZE_HINT
|
|
52
|
-
PAGE_GUARD = 50
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class ResolutionError(Exception):
|
|
56
|
-
"""A lookup that could not be resolved, with the reason a human needs."""
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class LookupExhausted(ResolutionError):
|
|
60
|
-
"""The collection was read to the end on THIS connection and had no match.
|
|
61
|
-
|
|
62
|
-
Split out from its parent because the two failures want opposite responses
|
|
63
|
-
and the caller cannot tell them apart from a message. A transport or
|
|
64
|
-
permission failure says nothing about whether the value exists — retrying it
|
|
65
|
-
on another connection would hide a broken call behind a confident "no
|
|
66
|
-
connection has it". An exhaustive miss is positive evidence about this
|
|
67
|
-
connection specifically, and is the signal `prepare` uses to demote it and
|
|
68
|
-
try the next candidate (flow-builder-sdk#744).
|
|
69
|
-
|
|
70
|
-
`capped` flips that: a scan stopped at the record cap did NOT reach the end,
|
|
71
|
-
so it is not evidence of absence and must not demote anything.
|
|
72
|
-
|
|
73
|
-
The attributes carry the counts so a caller can report per-candidate results
|
|
74
|
-
without re-parsing the sentence.
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
def __init__(self, message: str, *, field: str, collection: str, by: str,
|
|
78
|
-
value: str, scanned: int, pages: int, capped: bool) -> None:
|
|
79
|
-
super().__init__(message)
|
|
80
|
-
self.field = field
|
|
81
|
-
self.collection = collection
|
|
82
|
-
self.by = by
|
|
83
|
-
self.value = value
|
|
84
|
-
self.scanned = scanned
|
|
85
|
-
self.pages = pages
|
|
86
|
-
self.capped = capped
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def parse_resolve_flag(raw: str) -> tuple[str, str, str]:
|
|
90
|
-
"""`channel:profile.email=dustin@example.com` -> (field, by, value).
|
|
91
|
-
|
|
92
|
-
Split on the FIRST colon and the FIRST `=`. Both halves can legitimately
|
|
93
|
-
contain the other delimiter — `by` is a dotted path and a value can be
|
|
94
|
-
anything a person typed — so a greedy split silently mis-parses real input.
|
|
95
|
-
"""
|
|
96
|
-
field, sep, rest = raw.partition(":")
|
|
97
|
-
if not sep or not field:
|
|
98
|
-
raise ResolutionError(
|
|
99
|
-
f"--resolve {raw!r}: expected <field>:<by>=<value>, "
|
|
100
|
-
"e.g. --resolve channel:profile.email=someone@example.com"
|
|
101
|
-
)
|
|
102
|
-
by, sep, value = rest.partition("=")
|
|
103
|
-
if not sep or not by:
|
|
104
|
-
raise ResolutionError(
|
|
105
|
-
f"--resolve {raw!r}: expected <field>:<by>=<value>, "
|
|
106
|
-
f"but found no '=' after the search field {by or rest!r}."
|
|
107
|
-
)
|
|
108
|
-
return field, by, value
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def order_resolutions(
|
|
112
|
-
requested: list[tuple[str, str, str]], specs: dict,
|
|
113
|
-
) -> list[tuple[str, str, str]]:
|
|
114
|
-
"""Order the requests so a `dependsOn` prerequisite resolves first.
|
|
115
|
-
|
|
116
|
-
27 fields in the shipped library are not addressable until another field is
|
|
117
|
-
known — Jira's issue type needs the project, Amazon Connect's phone number
|
|
118
|
-
needs the instance. Resolving in the order the flags were typed leaves an
|
|
119
|
-
unsubstituted `{placeholder}` in a URL, and the error the connector returns
|
|
120
|
-
for that names neither field.
|
|
121
|
-
"""
|
|
122
|
-
by_field = {field: (field, by, value) for field, by, value in requested}
|
|
123
|
-
ordered: list[tuple[str, str, str]] = []
|
|
124
|
-
placed: set[str] = set()
|
|
125
|
-
|
|
126
|
-
def place(field: str, trail: tuple[str, ...] = ()) -> None:
|
|
127
|
-
if field in placed:
|
|
128
|
-
return
|
|
129
|
-
if field in trail:
|
|
130
|
-
cycle = " -> ".join([*trail, field])
|
|
131
|
-
raise ResolutionError(f"circular dependsOn among lookup fields: {cycle}")
|
|
132
|
-
for prerequisite in (specs.get(field, {}).get("dependsOn") or []):
|
|
133
|
-
if prerequisite in by_field:
|
|
134
|
-
place(prerequisite, (*trail, field))
|
|
135
|
-
else:
|
|
136
|
-
raise ResolutionError(
|
|
137
|
-
f"cannot resolve {field!r}: its collection is addressed through "
|
|
138
|
-
f"{prerequisite!r}, which has no --resolve of its own. Add "
|
|
139
|
-
f"--resolve {prerequisite}:<by>=<value> and re-run."
|
|
140
|
-
)
|
|
141
|
-
placed.add(field)
|
|
142
|
-
ordered.append(by_field[field])
|
|
143
|
-
|
|
144
|
-
for field, _by, _value in requested:
|
|
145
|
-
place(field)
|
|
146
|
-
return ordered
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _envelope_items(payload: dict) -> list:
|
|
150
|
-
"""The records out of a `uip is resources run list` reply.
|
|
151
|
-
|
|
152
|
-
The reply is `{"Result": "Success", "Code": ..., "Data": {"items": [...],
|
|
153
|
-
"Pagination": {...}}}` — records live under `Data`, and `Result` is the
|
|
154
|
-
STATUS STRING, not a container. Reading a top-level `items` finds nothing
|
|
155
|
-
and reports "scanned 0 records" against a collection that is full, which is
|
|
156
|
-
exactly what this did until an eval run walked it into the real CLI.
|
|
157
|
-
"""
|
|
158
|
-
data = payload.get("Data")
|
|
159
|
-
if isinstance(data, dict) and isinstance(data.get("items"), list):
|
|
160
|
-
return data["items"]
|
|
161
|
-
if isinstance(payload.get("items"), list):
|
|
162
|
-
return payload["items"]
|
|
163
|
-
return []
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def _envelope_pagination(payload: dict) -> dict:
|
|
167
|
-
"""The `Pagination` block, from under `Data`."""
|
|
168
|
-
data = payload.get("Data")
|
|
169
|
-
if isinstance(data, dict) and isinstance(data.get("Pagination"), dict):
|
|
170
|
-
return data["Pagination"]
|
|
171
|
-
if isinstance(payload.get("Pagination"), dict):
|
|
172
|
-
return payload["Pagination"]
|
|
173
|
-
return {}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def _truthy(value: object) -> bool:
|
|
177
|
-
"""`HasMore` arrives as the STRING "false" as often as the boolean.
|
|
178
|
-
|
|
179
|
-
`if payload["Pagination"]["HasMore"]` on the string "false" is True, which
|
|
180
|
-
pages forever on a collection that just said it has no more.
|
|
181
|
-
"""
|
|
182
|
-
if isinstance(value, str):
|
|
183
|
-
return value.strip().lower() in ("true", "1", "yes")
|
|
184
|
-
return bool(value)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def _uip_list(connector_key: str, collection: str, connection_id: str,
|
|
188
|
-
query: str | None) -> dict:
|
|
189
|
-
"""One `uip is resources run list` page, as parsed JSON."""
|
|
190
|
-
command = [
|
|
191
|
-
"uip", "is", "resources", "run", "list", connector_key, collection,
|
|
192
|
-
"--connection-id", connection_id, "--output", "json",
|
|
193
|
-
]
|
|
194
|
-
if query:
|
|
195
|
-
command += ["--query", query]
|
|
196
|
-
result = subprocess.run(command, capture_output=True, text=True)
|
|
197
|
-
if result.returncode != 0:
|
|
198
|
-
# `uip` prints a self-update failure to stderr before its real output,
|
|
199
|
-
# so stderr being non-empty is not evidence of anything. Judge the exit
|
|
200
|
-
# code and the payload.
|
|
201
|
-
raise ResolutionError(
|
|
202
|
-
f"listing {collection} failed: {result.stderr.strip() or 'no output'}"
|
|
203
|
-
)
|
|
204
|
-
try:
|
|
205
|
-
return json.loads(result.stdout)
|
|
206
|
-
except ValueError as error:
|
|
207
|
-
raise ResolutionError(
|
|
208
|
-
f"listing {collection} returned output that is not JSON: {error}"
|
|
209
|
-
) from error
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def _read_path(record: dict, dotted: str):
|
|
213
|
-
"""`profile.email` out of a record, or None."""
|
|
214
|
-
cursor = record
|
|
215
|
-
for part in dotted.split("."):
|
|
216
|
-
if not isinstance(cursor, dict):
|
|
217
|
-
return None
|
|
218
|
-
cursor = cursor.get(part)
|
|
219
|
-
return cursor
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def _substitute(template: str, resolved: dict[str, str]) -> str:
|
|
223
|
-
"""Fill `{fields.project.key}` placeholders from already-resolved fields."""
|
|
224
|
-
out = template
|
|
225
|
-
for field, value in resolved.items():
|
|
226
|
-
out = out.replace("{" + field + "}", str(value))
|
|
227
|
-
return out
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def resolve_one(
|
|
231
|
-
connector_key: str,
|
|
232
|
-
spec: dict,
|
|
233
|
-
field: str,
|
|
234
|
-
by: str,
|
|
235
|
-
value: str,
|
|
236
|
-
connection_id: str,
|
|
237
|
-
resolved: dict[str, str],
|
|
238
|
-
) -> str:
|
|
239
|
-
"""The connector-facing id for one lookup, or raise `ResolutionError`.
|
|
240
|
-
|
|
241
|
-
`resolved` carries the values of fields resolved earlier in this run, which
|
|
242
|
-
is what makes a `dependsOn` path addressable.
|
|
243
|
-
"""
|
|
244
|
-
if by not in (spec.get("by") or []):
|
|
245
|
-
searchable = ", ".join(repr(b) for b in (spec.get("by") or []))
|
|
246
|
-
raise ResolutionError(
|
|
247
|
-
f"{field}: cannot search by {by!r}. Searchable by {searchable or '(nothing declared)'}."
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
path = _substitute(spec.get("path") or "", resolved)
|
|
251
|
-
if "{" in path:
|
|
252
|
-
missing = path[path.index("{") + 1: path.index("}")] if "}" in path else "?"
|
|
253
|
-
raise ResolutionError(
|
|
254
|
-
f"{field}: the collection path still contains {{{missing}}}. "
|
|
255
|
-
f"Resolve {missing!r} first with --resolve {missing}:<by>=<value>."
|
|
256
|
-
)
|
|
257
|
-
collection = path.split("?", 1)[0].strip("/")
|
|
258
|
-
query = path.partition("?")[2] or None
|
|
259
|
-
send = spec.get("value") or "id"
|
|
260
|
-
|
|
261
|
-
# A server-side filter turns the whole scan into one request. Where the
|
|
262
|
-
# metadata declares one, use it; where it does not, page.
|
|
263
|
-
filter_pattern = spec.get("filterPattern")
|
|
264
|
-
if filter_pattern:
|
|
265
|
-
filtered = filter_pattern.replace("{filter}", value)
|
|
266
|
-
query = f"{query}&{filtered}" if query else filtered
|
|
267
|
-
|
|
268
|
-
scanned = 0
|
|
269
|
-
pages = 0
|
|
270
|
-
while True:
|
|
271
|
-
payload = _uip_list(connector_key, collection, connection_id, query)
|
|
272
|
-
items = _envelope_items(payload)
|
|
273
|
-
for record in items:
|
|
274
|
-
scanned += 1
|
|
275
|
-
if str(_read_path(record, by)) == value:
|
|
276
|
-
got = _read_path(record, send)
|
|
277
|
-
if got is None:
|
|
278
|
-
raise ResolutionError(
|
|
279
|
-
f"{field}: matched a record on {by}={value!r}, but it "
|
|
280
|
-
f"carries no {send!r} to send."
|
|
281
|
-
)
|
|
282
|
-
return str(got)
|
|
283
|
-
pagination = _envelope_pagination(payload)
|
|
284
|
-
token = pagination.get("NextPageToken")
|
|
285
|
-
if not _truthy(pagination.get("HasMore")) or not token or scanned >= MAX_SCANNED:
|
|
286
|
-
break
|
|
287
|
-
pages += 1
|
|
288
|
-
if pages > PAGE_GUARD:
|
|
289
|
-
break
|
|
290
|
-
# Exactly `nextPage`. Guessing `page` / `pageToken` / `nextPageToken`
|
|
291
|
-
# is a documented way to loop forever on page one.
|
|
292
|
-
query = f"{query}&nextPage={token}" if query else f"nextPage={token}"
|
|
293
|
-
|
|
294
|
-
capped = scanned >= MAX_SCANNED or pages > PAGE_GUARD
|
|
295
|
-
# This states the FACT and stops; `prepare` owns the remedy, because
|
|
296
|
-
# `prepare` is what knows the other candidate connections.
|
|
297
|
-
#
|
|
298
|
-
# It deliberately carries NO "check the value" and NO `uip is resources`
|
|
299
|
-
# pointer. The old text led with both: it named only the value as suspect
|
|
300
|
-
# when the connection is equally suspect, and then sent the reader to a
|
|
301
|
-
# tenant crawl the skill explicitly rules out ("Tenant discovery is not a
|
|
302
|
-
# phase of either loop", references/CLI-LOOP.md). An agent followed that
|
|
303
|
-
# advice into exactly what it had been told not to do — 50 of 94 Bash calls
|
|
304
|
-
# hand-crawling Integration Service, 41 of them paging this collection, and
|
|
305
|
-
# the run hit its ceiling without producing a flow (flow-builder-sdk#744).
|
|
306
|
-
#
|
|
307
|
-
# The listing command is still offered, once, by the caller's terminal
|
|
308
|
-
# report — where every connection has been tried and seeing what IS there
|
|
309
|
-
# is finally the next step rather than a detour.
|
|
310
|
-
raise LookupExhausted(
|
|
311
|
-
f"{field}: no record in {collection!r} has {by}={value!r} "
|
|
312
|
-
f"(scanned {scanned} record(s) across {pages + 1} page(s)"
|
|
313
|
-
+ (f"; STOPPED AT THE {MAX_SCANNED}-record cap, so the value may be "
|
|
314
|
-
f"further in" if capped
|
|
315
|
-
else "; collection exhausted")
|
|
316
|
-
+ f") on connection {connection_id}.",
|
|
317
|
-
field=field, collection=collection, by=by, value=value,
|
|
318
|
-
scanned=scanned, pages=pages + 1, capped=capped,
|
|
319
|
-
)
|