@enricai/barnacle 1.12.75 → 1.12.77
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/scraper/flow-runner.d.ts.map +1 -1
- package/dist/scraper/flow-runner.js +5 -2
- package/dist/scraper/flow-runner.js.map +1 -1
- package/dist/scraper/phantom-click.d.ts +38 -0
- package/dist/scraper/phantom-click.d.ts.map +1 -1
- package/dist/scraper/phantom-click.js +112 -2
- package/dist/scraper/phantom-click.js.map +1 -1
- package/dist/scripts/recon-browser.d.ts +4 -1
- package/dist/scripts/recon-browser.d.ts.map +1 -1
- package/dist/scripts/recon-browser.js +7 -13
- package/dist/scripts/recon-browser.js.map +1 -1
- package/dist/scripts/recon-generate.d.ts +53 -2
- package/dist/scripts/recon-generate.d.ts.map +1 -1
- package/dist/scripts/recon-generate.js +415 -21
- package/dist/scripts/recon-generate.js.map +1 -1
- package/package.json +2 -2
|
@@ -27,6 +27,7 @@ exports.extractStepPersonaValue = extractStepPersonaValue;
|
|
|
27
27
|
exports.extractNavigateToHashFragmentValue = extractNavigateToHashFragmentValue;
|
|
28
28
|
exports.deriveFillLabelField = deriveFillLabelField;
|
|
29
29
|
exports.harvestPersonaBindings = harvestPersonaBindings;
|
|
30
|
+
exports.extractNavigateToFacetOrder = extractNavigateToFacetOrder;
|
|
30
31
|
exports.resolveCompositePersonaFields = resolveCompositePersonaFields;
|
|
31
32
|
exports.collectConditionalGraphQLFieldNames = collectConditionalGraphQLFieldNames;
|
|
32
33
|
exports.inferZodSchemaFromSamples = inferZodSchemaFromSamples;
|
|
@@ -65,6 +66,7 @@ exports.collectUnboundLiterals = collectUnboundLiterals;
|
|
|
65
66
|
exports.emitMultiStepExecuteHttp = emitMultiStepExecuteHttp;
|
|
66
67
|
exports.countSpliceableFacets = countSpliceableFacets;
|
|
67
68
|
exports.spliceFacetsIntoStringVariable = spliceFacetsIntoStringVariable;
|
|
69
|
+
exports.spliceFacetsIntoArrayVariable = spliceFacetsIntoArrayVariable;
|
|
68
70
|
exports.findAllObjectArrayFields = findAllObjectArrayFields;
|
|
69
71
|
exports.findAllObjectArrayFieldsOrWholeObject = findAllObjectArrayFieldsOrWholeObject;
|
|
70
72
|
exports.applyDrillParamBindings = applyDrillParamBindings;
|
|
@@ -481,6 +483,36 @@ function harvestPersonaBindings(flowSteps, vocabulary, env) {
|
|
|
481
483
|
}
|
|
482
484
|
return bindings;
|
|
483
485
|
}
|
|
486
|
+
/**
|
|
487
|
+
* Re-walks the flow for navigateTo+payloadField steps in isolation, in flow
|
|
488
|
+
* order, independent of whether {@link harvestPersonaBindings} could anchor
|
|
489
|
+
* each one's literal anywhere in the capture. `harvestPersonaBindings` only
|
|
490
|
+
* sees flow steps (not captures), so it cannot tell which of its bindings
|
|
491
|
+
* will turn out to have zero recurrence downstream — this lets the emitter,
|
|
492
|
+
* which DOES see the captures, re-derive the same (value, field) pairs and
|
|
493
|
+
* try the causally-adjacent-action fallback (see `emitMultiStepExecuteHttp`)
|
|
494
|
+
* for whichever ones the recurrence-anchored path left unbound.
|
|
495
|
+
*/
|
|
496
|
+
function extractNavigateToFacetOrder(flowSteps) {
|
|
497
|
+
const order = [];
|
|
498
|
+
let previousNavigateToHash;
|
|
499
|
+
for (const step of flowSteps) {
|
|
500
|
+
if (typeof step === "string" || step.navigateTo === undefined)
|
|
501
|
+
continue;
|
|
502
|
+
const hashIndex = step.navigateTo.indexOf("#");
|
|
503
|
+
const currentHash = hashIndex === -1 ? undefined : step.navigateTo.slice(hashIndex + 1);
|
|
504
|
+
if (!step.payloadField) {
|
|
505
|
+
previousNavigateToHash = currentHash;
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const value = extractNavigateToHashFragmentValue(step.navigateTo, previousNavigateToHash);
|
|
509
|
+
previousNavigateToHash = currentHash;
|
|
510
|
+
if (value === null || value.length === 0)
|
|
511
|
+
continue;
|
|
512
|
+
order.push({ value, field: step.payloadField, optional: step.optional === true });
|
|
513
|
+
}
|
|
514
|
+
return order;
|
|
515
|
+
}
|
|
484
516
|
/**
|
|
485
517
|
* Detects a flow step whose quoted VALUE is the space-joined concatenation of
|
|
486
518
|
* two already-known field values (either order), e.g. a signature step whose
|
|
@@ -3940,7 +3972,12 @@ function locateFormEnvelopePath(parsedBody) {
|
|
|
3940
3972
|
* and swap for `${item.<field>}`. Freezing either into an opaque
|
|
3941
3973
|
* `${JSON.stringify(payload.tokens)}` blob here would silently drop the
|
|
3942
3974
|
* value the request depends on, breaking the fold at exactly the case an
|
|
3943
|
-
* ARRAY/OBJECT-wrapped join field represents.
|
|
3975
|
+
* ARRAY/OBJECT-wrapped join field represents. A candidate whose leaves
|
|
3976
|
+
* include a value already registered as a payload-accessor literal (e.g. a
|
|
3977
|
+
* facet value a prior step derived and already wired for splicing elsewhere,
|
|
3978
|
+
* such as a `departmentCode` recurring inside a `tags` array) is excluded on
|
|
3979
|
+
* the same unconditional basis — it is already-threaded text waiting on the
|
|
3980
|
+
* substring-splice pass, not caller-supplied history data.
|
|
3944
3981
|
*
|
|
3945
3982
|
* A body with no detected form envelope worth swallowing into (a facet/
|
|
3946
3983
|
* search body — all scalars, if any, sit directly at the root) still needs
|
|
@@ -3951,7 +3988,7 @@ function locateFormEnvelopePath(parsedBody) {
|
|
|
3951
3988
|
*
|
|
3952
3989
|
* Site-agnostic: operates only on the recon body's own shape.
|
|
3953
3990
|
*
|
|
3954
|
-
* A top-level-ARRAY-shaped body (e.g. a
|
|
3991
|
+
* A top-level-ARRAY-shaped body (e.g. a travel-brand multi-room search that
|
|
3955
3992
|
* batches per-room criteria as `[{...}, {...}]`) is walked element by
|
|
3956
3993
|
* element, applying this same envelope logic to each object element in
|
|
3957
3994
|
* textual order — otherwise a structured field living inside an array
|
|
@@ -3966,7 +4003,35 @@ function locateFormEnvelopePath(parsedBody) {
|
|
|
3966
4003
|
* search there stops an earlier element's already-rewritten (or still-frozen)
|
|
3967
4004
|
* span from swallowing a later element's identically-named key.
|
|
3968
4005
|
*/
|
|
3969
|
-
function
|
|
4006
|
+
function isTokenBoundaryChar(ch) {
|
|
4007
|
+
return ch === undefined || !/[A-Za-z0-9]/.test(ch);
|
|
4008
|
+
}
|
|
4009
|
+
/**
|
|
4010
|
+
* True when `needle` occurs in `haystack` at a token boundary (flanked only
|
|
4011
|
+
* by non-alphanumeric characters or the string's own edges) — e.g. a
|
|
4012
|
+
* navigateTo facet's bare literal recurring inside a captured array element
|
|
4013
|
+
* that suffixes it with a constant delimiter string (`"<literal>;filterId=
|
|
4014
|
+
* urlFriendlyId"`). Exact equality alone misses this recurrence shape since
|
|
4015
|
+
* the leaf carries the delimiter-joined text, not the bare literal. Mirrors
|
|
4016
|
+
* the same boundary discipline `bindsWithoutCollision` already applies when
|
|
4017
|
+
* registering a value for substring splicing in the first place.
|
|
4018
|
+
*/
|
|
4019
|
+
function containsValueAtTokenBoundary(haystack, needle) {
|
|
4020
|
+
if (needle.length === 0 || needle === haystack)
|
|
4021
|
+
return false;
|
|
4022
|
+
let from = 0;
|
|
4023
|
+
while (true) {
|
|
4024
|
+
const at = haystack.indexOf(needle, from);
|
|
4025
|
+
if (at === -1)
|
|
4026
|
+
return false;
|
|
4027
|
+
if (isTokenBoundaryChar(haystack[at - 1]) &&
|
|
4028
|
+
isTokenBoundaryChar(haystack[at + needle.length])) {
|
|
4029
|
+
return true;
|
|
4030
|
+
}
|
|
4031
|
+
from = at + needle.length;
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, searchFrom, envelopePath, rootBody, payloadAccessorExcludePattern) {
|
|
3970
4035
|
let result = template;
|
|
3971
4036
|
let cursor = searchFrom;
|
|
3972
4037
|
// The body root's own entries are eligible too, whenever the located
|
|
@@ -3988,7 +4053,9 @@ function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope,
|
|
|
3988
4053
|
Object.keys(value).length > 0;
|
|
3989
4054
|
if (!isNonEmptyArray && !isNestedObject)
|
|
3990
4055
|
continue;
|
|
3991
|
-
if (unconditionalExcludeValues.size > 0 ||
|
|
4056
|
+
if (unconditionalExcludeValues.size > 0 ||
|
|
4057
|
+
restrictedExcludeSourceByValue.size > 0 ||
|
|
4058
|
+
payloadAccessorExcludePattern !== null) {
|
|
3992
4059
|
// A name-free join/chain value (unconditionalExcludeValues) excludes the
|
|
3993
4060
|
// whole field on ANY leaf coincidence, same as before. A RESTRICTED prior-
|
|
3994
4061
|
// step value (one with a real source field name) additionally requires
|
|
@@ -3999,12 +4066,39 @@ function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope,
|
|
|
3999
4066
|
// count/id) merely equalling SOME other step's produced value gets the
|
|
4000
4067
|
// ENTIRE array/object excluded from threading, even though nothing about
|
|
4001
4068
|
// that leaf's key names the same concept as the matched value's source.
|
|
4069
|
+
// A registered payload-accessor literal (payloadAccessorExcludePattern)
|
|
4070
|
+
// excludes unconditionally too, but on a DELIMITED-SUBSTRING match, not
|
|
4071
|
+
// exact leaf equality — the reported shape is a facet value recurring
|
|
4072
|
+
// inside a captured array element with a constant suffix still attached
|
|
4073
|
+
// (e.g. a hash-derived facet embedded in a `tags` entry alongside a
|
|
4074
|
+
// trailing marker), so requiring the whole leaf to equal the bare facet
|
|
4075
|
+
// literal would miss exactly the case this exclusion exists for. Reuses
|
|
4076
|
+
// {@link buildValueAlternationPattern}'s own token-boundary rule so a
|
|
4077
|
+
// facet value that merely happens to be a substring of an unrelated,
|
|
4078
|
+
// dash/dot-concatenated compound token is still correctly left alone.
|
|
4079
|
+
//
|
|
4080
|
+
// Beyond exact equality, a leaf that carries an excluded value as a
|
|
4081
|
+
// token-bounded SUBSTRING (e.g. a navigateTo facet literal recurring
|
|
4082
|
+
// inside an array element suffixed by a constant delimiter string, like
|
|
4083
|
+
// `"<literal>;filterId=urlFriendlyId"`) must exclude the field too —
|
|
4084
|
+
// otherwise the field gets frozen wholesale before the later
|
|
4085
|
+
// literal-value interpolation pass (which operates on text, not parsed
|
|
4086
|
+
// values) ever gets a chance to splice the facet out of that element.
|
|
4002
4087
|
const carriesThreadedValue = [...walkAllPrimitiveLeaves(value)].some(({ value: leaf, path: leafPath }) => {
|
|
4003
4088
|
if (leaf === null)
|
|
4004
4089
|
return false;
|
|
4005
4090
|
const leafValue = String(leaf);
|
|
4006
4091
|
if (unconditionalExcludeValues.has(leafValue))
|
|
4007
4092
|
return true;
|
|
4093
|
+
if (payloadAccessorExcludePattern !== null) {
|
|
4094
|
+
payloadAccessorExcludePattern.lastIndex = 0;
|
|
4095
|
+
if (payloadAccessorExcludePattern.test(leafValue))
|
|
4096
|
+
return true;
|
|
4097
|
+
}
|
|
4098
|
+
for (const excluded of unconditionalExcludeValues) {
|
|
4099
|
+
if (containsValueAtTokenBoundary(leafValue, excluded))
|
|
4100
|
+
return true;
|
|
4101
|
+
}
|
|
4008
4102
|
const sourceName = restrictedExcludeSourceByValue.get(leafValue);
|
|
4009
4103
|
if (sourceName === undefined)
|
|
4010
4104
|
return false;
|
|
@@ -4052,6 +4146,19 @@ function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope,
|
|
|
4052
4146
|
}
|
|
4053
4147
|
if (spanEnd === -1)
|
|
4054
4148
|
continue;
|
|
4149
|
+
// An earlier pass in this same pipeline run (e.g. the optional-facet
|
|
4150
|
+
// array-element splice) may have already spliced a `${payload.<field>}`
|
|
4151
|
+
// accessor into this exact span — e.g. turning one element of a
|
|
4152
|
+
// search/filter array into `...(payload.sortOrder ? [...] : [])`. The
|
|
4153
|
+
// exclusion-source checks above only ever see the ORIGINAL parsed leaf
|
|
4154
|
+
// values, so they can't detect this; inspecting the located span's
|
|
4155
|
+
// CURRENT text catches it and leaves the pre-spliced accessor intact
|
|
4156
|
+
// instead of clobbering it with a wholesale replacement. Matching on the
|
|
4157
|
+
// literal template-placeholder prefix `${payload.` (rather than the bare
|
|
4158
|
+
// substring `payload.`) avoids false-positiving on scraped leaf data that
|
|
4159
|
+
// merely happens to contain the word "payload." as ordinary text.
|
|
4160
|
+
if (result.slice(spanStart, spanEnd).includes("${payload."))
|
|
4161
|
+
continue;
|
|
4055
4162
|
const replacement = `$${"{"}JSON.stringify(payload.${key})${"}"}`;
|
|
4056
4163
|
result = result.slice(0, spanStart) + replacement + result.slice(spanEnd);
|
|
4057
4164
|
cursor = Math.max(cursor, spanStart + replacement.length);
|
|
@@ -4067,7 +4174,7 @@ function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope,
|
|
|
4067
4174
|
* pass to it. Shared by the top-level-object and top-level-array branches of
|
|
4068
4175
|
* {@link applyStructuredValuePayloadSubstitutions} below.
|
|
4069
4176
|
*/
|
|
4070
|
-
function applyStructuredValuePayloadSubstitutionsForObjectBody(template, objectBody, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, searchFrom) {
|
|
4177
|
+
function applyStructuredValuePayloadSubstitutionsForObjectBody(template, objectBody, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, searchFrom, payloadAccessorExcludePattern) {
|
|
4071
4178
|
const envelopePath = locateFormEnvelopePath(objectBody);
|
|
4072
4179
|
let envelope = objectBody;
|
|
4073
4180
|
for (const seg of envelopePath) {
|
|
@@ -4078,7 +4185,7 @@ function applyStructuredValuePayloadSubstitutionsForObjectBody(template, objectB
|
|
|
4078
4185
|
if (envelope === null || typeof envelope !== "object" || Array.isArray(envelope)) {
|
|
4079
4186
|
return { result: template, nextSearchFrom: searchFrom };
|
|
4080
4187
|
}
|
|
4081
|
-
return applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, searchFrom, envelopePath, objectBody);
|
|
4188
|
+
return applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, searchFrom, envelopePath, objectBody, payloadAccessorExcludePattern);
|
|
4082
4189
|
}
|
|
4083
4190
|
/**
|
|
4084
4191
|
* `priorStepStateBindings` carries the SAME value→binding map
|
|
@@ -4087,13 +4194,23 @@ function applyStructuredValuePayloadSubstitutionsForObjectBody(template, objectB
|
|
|
4087
4194
|
* field when its own leaf's key {@link keyNamesCorrelate}s with that source
|
|
4088
4195
|
* name — an unrestricted binding (name-free, e.g. a bare array-index chain
|
|
4089
4196
|
* hop) or a `joinFieldValues` entry excludes unconditionally on any leaf
|
|
4090
|
-
* match, same as before.
|
|
4197
|
+
* match, same as before. A `payloadAccessorExcludeValues` key excludes
|
|
4198
|
+
* unconditionally too, but on a delimited-SUBSTRING match of the leaf (see
|
|
4199
|
+
* {@link applyStructuredValuePayloadSubstitutionsForEnvelope}), not exact
|
|
4200
|
+
* equality — the reported shape is a facet value recurring inside a captured
|
|
4201
|
+
* array element with a constant suffix still attached (e.g. a hash-derived
|
|
4202
|
+
* facet embedded in a `tags` entry alongside a trailing marker), so a
|
|
4203
|
+
* candidate field whose leaf merely CONTAINS one of those literals at a token
|
|
4204
|
+
* boundary is left alone here too, instead of being wholesale-swallowed into
|
|
4205
|
+
* an opaque `${JSON.stringify(payload.<key>)}` blob before the substring-
|
|
4206
|
+
* splice pass ({@link interpolateStateValues}) gets a chance to thread it.
|
|
4207
|
+
* Without the correlation requirement on the restricted source, an array/
|
|
4091
4208
|
* object field whose own unrelated short scalar leaf (a count/id) merely
|
|
4092
4209
|
* EQUALS some other step's produced value gets the whole field wrongly
|
|
4093
4210
|
* excluded from threading — the same value-coincidence class of bug
|
|
4094
4211
|
* {@link keyNamesCorrelate}'s other call sites already guard against.
|
|
4095
4212
|
*/
|
|
4096
|
-
function applyStructuredValuePayloadSubstitutions(template, parsedBody, outStructuredKeys, priorStepStateBindings = new Map(), joinFieldValues = new Set()) {
|
|
4213
|
+
function applyStructuredValuePayloadSubstitutions(template, parsedBody, outStructuredKeys, priorStepStateBindings = new Map(), joinFieldValues = new Set(), payloadAccessorExcludeValues = new Map()) {
|
|
4097
4214
|
if (parsedBody === null || typeof parsedBody !== "object") {
|
|
4098
4215
|
return template;
|
|
4099
4216
|
}
|
|
@@ -4105,7 +4222,10 @@ function applyStructuredValuePayloadSubstitutions(template, parsedBody, outStruc
|
|
|
4105
4222
|
else
|
|
4106
4223
|
unconditionalExcludeValues.add(value);
|
|
4107
4224
|
}
|
|
4108
|
-
|
|
4225
|
+
const payloadAccessorExcludePattern = payloadAccessorExcludeValues.size > 0
|
|
4226
|
+
? buildValueAlternationPattern([...payloadAccessorExcludeValues.keys()].sort((a, b) => b.length - a.length))
|
|
4227
|
+
: null;
|
|
4228
|
+
// A travel-brand-style multi-room/multi-guest search body commonly batches
|
|
4109
4229
|
// per-element criteria as a top-level JSON ARRAY rather than a single
|
|
4110
4230
|
// object. Walk each element in textual order (via the growing searchFrom
|
|
4111
4231
|
// cursor) instead of bailing out here — otherwise any structured field
|
|
@@ -4117,13 +4237,13 @@ function applyStructuredValuePayloadSubstitutions(template, parsedBody, outStruc
|
|
|
4117
4237
|
for (const element of parsedBody) {
|
|
4118
4238
|
if (element === null || typeof element !== "object" || Array.isArray(element))
|
|
4119
4239
|
continue;
|
|
4120
|
-
const { result: nextResult, nextSearchFrom } = applyStructuredValuePayloadSubstitutionsForObjectBody(result, element, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, cursor);
|
|
4240
|
+
const { result: nextResult, nextSearchFrom } = applyStructuredValuePayloadSubstitutionsForObjectBody(result, element, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, cursor, payloadAccessorExcludePattern);
|
|
4121
4241
|
result = nextResult;
|
|
4122
4242
|
cursor = nextSearchFrom;
|
|
4123
4243
|
}
|
|
4124
4244
|
return result;
|
|
4125
4245
|
}
|
|
4126
|
-
const { result } = applyStructuredValuePayloadSubstitutionsForObjectBody(template, parsedBody, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, 0);
|
|
4246
|
+
const { result } = applyStructuredValuePayloadSubstitutionsForObjectBody(template, parsedBody, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, 0, payloadAccessorExcludePattern);
|
|
4127
4247
|
return result;
|
|
4128
4248
|
}
|
|
4129
4249
|
/** Maximum length to guard against indexing massive blobs (HTML fragments,
|
|
@@ -5750,6 +5870,63 @@ responseBodiesForCapacitySignal = []) {
|
|
|
5750
5870
|
* steps declared any field to correlate against) or when a leaf's value has
|
|
5751
5871
|
* no `key:value` segment matching one of them.
|
|
5752
5872
|
*/
|
|
5873
|
+
/** Yields every array-valued leaf in `value` (at any object nesting depth) —
|
|
5874
|
+
* `{@link applyOptionalArrayFacetSplicePayloadSubstitutions}`'s raw-array
|
|
5875
|
+
* search surface. Does not recurse INTO an array's own elements (a facet
|
|
5876
|
+
* array is always a flat array of primitives in every observed recon shape),
|
|
5877
|
+
* matching {@link walkAllPrimitiveLeaves}'s depth-first walk of everything
|
|
5878
|
+
* else. */
|
|
5879
|
+
function* walkArrayLeaves(value, path = []) {
|
|
5880
|
+
if (value === null || typeof value !== "object")
|
|
5881
|
+
return;
|
|
5882
|
+
if (Array.isArray(value)) {
|
|
5883
|
+
yield { value, path };
|
|
5884
|
+
return;
|
|
5885
|
+
}
|
|
5886
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
5887
|
+
yield* walkArrayLeaves(inner, [...path, key]);
|
|
5888
|
+
}
|
|
5889
|
+
}
|
|
5890
|
+
/**
|
|
5891
|
+
* REST-body counterpart to {@link spliceFacetsIntoArrayVariable}'s GraphQL
|
|
5892
|
+
* use: a navigateTo facet's literal recurring as one array element (suffixed
|
|
5893
|
+
* by a constant delimiter) inside a REST JSON body gets its element text
|
|
5894
|
+
* swapped for `${payload.<field>}` by the plain `interpolateStateValues`
|
|
5895
|
+
* splice later in the pipeline — but that pass only rewrites literal text
|
|
5896
|
+
* IN PLACE, so when the matched facet is optional it would leave the array's
|
|
5897
|
+
* frozen `undefined`-shaped element (and its JSON delimiter/comma) in the
|
|
5898
|
+
* output whenever the caller omits the field, instead of omitting the
|
|
5899
|
+
* element entirely.
|
|
5900
|
+
*
|
|
5901
|
+
* This pass runs first and, for any array that matches at least one OPTIONAL
|
|
5902
|
+
* facet, rewrites the array's own frozen `JSON.stringify(...)` occurrence in
|
|
5903
|
+
* the body template to a single `${JSON.stringify([...])}` splice point
|
|
5904
|
+
* built from {@link spliceFacetsIntoArrayVariable}'s per-element conditional-
|
|
5905
|
+
* emission shape — the same `...(payload.<field> ? [...] : [])` convention
|
|
5906
|
+
* {@link spliceFacetsIntoStringVariable} already uses for the `key:value`
|
|
5907
|
+
* grammar. A required-only match (no optional facet in that array) is left
|
|
5908
|
+
* for the later plain splice to handle exactly as before — this pass is a
|
|
5909
|
+
* no-op whenever nothing it rewrites would differ, so the existing
|
|
5910
|
+
* required-only REST array-element shape (recon-generate-navigateto-array-
|
|
5911
|
+
* element-facet-splice-e2e.test.ts) never changes.
|
|
5912
|
+
*/
|
|
5913
|
+
function applyOptionalArrayFacetSplicePayloadSubstitutions(template, inputBody, navigateToFacets) {
|
|
5914
|
+
if (!navigateToFacets.some((facet) => facet.optional))
|
|
5915
|
+
return template;
|
|
5916
|
+
if (inputBody === null || typeof inputBody !== "object")
|
|
5917
|
+
return template;
|
|
5918
|
+
let result = template;
|
|
5919
|
+
for (const { value } of walkArrayLeaves(inputBody)) {
|
|
5920
|
+
const spliced = spliceFacetsIntoArrayVariable(value, navigateToFacets);
|
|
5921
|
+
if (spliced === null || !spliced.includes("...(payload."))
|
|
5922
|
+
continue;
|
|
5923
|
+
const target = JSON.stringify(value);
|
|
5924
|
+
if (!result.includes(target))
|
|
5925
|
+
continue;
|
|
5926
|
+
result = result.split(target).join(`\${JSON.stringify(${spliced})}`);
|
|
5927
|
+
}
|
|
5928
|
+
return result;
|
|
5929
|
+
}
|
|
5753
5930
|
function applyFacetSplicePayloadSubstitutions(template, inputBody, payloadFieldNames, optionalFieldNames) {
|
|
5754
5931
|
if (payloadFieldNames.size === 0)
|
|
5755
5932
|
return template;
|
|
@@ -6012,6 +6189,109 @@ function emitFoldMatchAndMergeLines(terminalStep, target, itemVar, suffix, joinA
|
|
|
6012
6189
|
` Object.assign(${itemVar}, Object.fromEntries(Object.entries(foldMatch${suffix} ?? {}).filter(([k]) => !(k in ${itemVar}))));`,
|
|
6013
6190
|
];
|
|
6014
6191
|
}
|
|
6192
|
+
/** Parses a URL's query string into a key→value map. Returns an empty map on
|
|
6193
|
+
* a malformed URL or when there is no query string. */
|
|
6194
|
+
function parseUrlQueryParams(url) {
|
|
6195
|
+
const params = new Map();
|
|
6196
|
+
try {
|
|
6197
|
+
for (const [key, value] of new URL(url).searchParams)
|
|
6198
|
+
params.set(key, value);
|
|
6199
|
+
}
|
|
6200
|
+
catch {
|
|
6201
|
+
// malformed URL — treat as having no query params
|
|
6202
|
+
}
|
|
6203
|
+
return params;
|
|
6204
|
+
}
|
|
6205
|
+
/** Parses a captured JSON request body into its string leaf values. Returns
|
|
6206
|
+
* an empty array when the body is absent or not valid JSON. */
|
|
6207
|
+
function parseBodyLeafValues(requestPostData) {
|
|
6208
|
+
if (!requestPostData)
|
|
6209
|
+
return [];
|
|
6210
|
+
try {
|
|
6211
|
+
return [...walkStringLeaves(JSON.parse(requestPostData))].map((leaf) => leaf.value);
|
|
6212
|
+
}
|
|
6213
|
+
catch {
|
|
6214
|
+
return [];
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
/**
|
|
6218
|
+
* Walks `actions` in order and groups the query-string/body leaf values that
|
|
6219
|
+
* appear in an action's own capture but did NOT appear in the immediately-
|
|
6220
|
+
* preceding action's capture, PER TRANSITION (one entry per adjacent action
|
|
6221
|
+
* pair that introduced at least one such value). This is the generic
|
|
6222
|
+
* "what's new here" diff {@link correlateUnreachableNavigateToFacets} uses —
|
|
6223
|
+
* kept per-transition (rather than flattened) so a transition that
|
|
6224
|
+
* introduces more than one new value, or multiple transitions that fire
|
|
6225
|
+
* between two facets' real actions, can be told apart from the single clean
|
|
6226
|
+
* transition a lone facet causes.
|
|
6227
|
+
*
|
|
6228
|
+
* The first action never contributes: with no predecessor to diff against,
|
|
6229
|
+
* every one of its values would trivially count as "new", which is nearly
|
|
6230
|
+
* always the baseline/listing call a navigateTo-declared filter does NOT
|
|
6231
|
+
* affect (the already-working recurrence-anchored path exists precisely
|
|
6232
|
+
* because a real facet DOES recur in a later request; nothing here changes
|
|
6233
|
+
* that — this only supplies values for facets that are not found at all).
|
|
6234
|
+
*/
|
|
6235
|
+
function collectNewlyAppearingRequestValueTransitions(actions) {
|
|
6236
|
+
const seenGlobally = new Set();
|
|
6237
|
+
const transitions = [];
|
|
6238
|
+
for (let i = 1; i < actions.length; i++) {
|
|
6239
|
+
const prior = actions[i - 1].capture;
|
|
6240
|
+
const current = actions[i].capture;
|
|
6241
|
+
const seenThisTransition = new Set();
|
|
6242
|
+
const values = [];
|
|
6243
|
+
const record = (value) => {
|
|
6244
|
+
if (value.length === 0 || seenGlobally.has(value) || seenThisTransition.has(value))
|
|
6245
|
+
return;
|
|
6246
|
+
seenThisTransition.add(value);
|
|
6247
|
+
values.push(value);
|
|
6248
|
+
};
|
|
6249
|
+
const priorParams = parseUrlQueryParams(prior.url);
|
|
6250
|
+
for (const [key, value] of parseUrlQueryParams(current.url)) {
|
|
6251
|
+
if (priorParams.get(key) !== value)
|
|
6252
|
+
record(value);
|
|
6253
|
+
}
|
|
6254
|
+
const priorLeaves = new Set(parseBodyLeafValues(prior.requestPostData));
|
|
6255
|
+
for (const value of parseBodyLeafValues(current.requestPostData)) {
|
|
6256
|
+
if (!priorLeaves.has(value))
|
|
6257
|
+
record(value);
|
|
6258
|
+
}
|
|
6259
|
+
for (const value of values)
|
|
6260
|
+
seenGlobally.add(value);
|
|
6261
|
+
if (values.length > 0)
|
|
6262
|
+
transitions.push(values);
|
|
6263
|
+
}
|
|
6264
|
+
return transitions;
|
|
6265
|
+
}
|
|
6266
|
+
/**
|
|
6267
|
+
* Resolves each zero-recurrence navigateTo facet (in flow order) to the real
|
|
6268
|
+
* request value it causally affects, or returns `null` when the correlation
|
|
6269
|
+
* cannot be trusted.
|
|
6270
|
+
*
|
|
6271
|
+
* Pairing is positional — facet N gets the Nth qualifying transition — which
|
|
6272
|
+
* is only safe when every transition in the pool is itself unambiguous (an
|
|
6273
|
+
* action introduced EXACTLY ONE new value, so there is no question which
|
|
6274
|
+
* value within it is "the" new one) AND the pool's size exactly matches the
|
|
6275
|
+
* number of facets awaiting a value. A transition that introduces more than
|
|
6276
|
+
* one new value is dropped from the pool entirely rather than guessing which
|
|
6277
|
+
* of its values is real; if that drops the pool below (or an unrelated
|
|
6278
|
+
* single-value transition pushes it above) the facet count, the pairing is
|
|
6279
|
+
* no longer guaranteed to line facets up with the RIGHT transition, so
|
|
6280
|
+
* nothing is bound — leaving a facet declared-but-unbound is preferable to
|
|
6281
|
+
* silently wiring it to another facet's (or an unrelated transition's) value.
|
|
6282
|
+
*/
|
|
6283
|
+
function correlateUnreachableNavigateToFacets(facets, actions) {
|
|
6284
|
+
const unambiguousValues = collectNewlyAppearingRequestValueTransitions(actions)
|
|
6285
|
+
.filter((transition) => transition.length === 1)
|
|
6286
|
+
.map((transition) => transition[0]);
|
|
6287
|
+
if (unambiguousValues.length !== facets.length)
|
|
6288
|
+
return null;
|
|
6289
|
+
const byField = new Map();
|
|
6290
|
+
facets.forEach(({ field }, index) => {
|
|
6291
|
+
byField.set(field, unambiguousValues[index]);
|
|
6292
|
+
});
|
|
6293
|
+
return byField;
|
|
6294
|
+
}
|
|
6015
6295
|
/** Exported for unit testing — lets tests drive the multipart-upload code path directly
|
|
6016
6296
|
* without going through the full emitContractTs pipeline. */
|
|
6017
6297
|
function emitMultiStepExecuteHttp(actions, inputBody, errorSignals, fieldNameMap, outDiscoveredFields, fieldOptionsMap, outDiscoveredOptionFields, outDiscoveredRawOptionFields, outDiscoveredAdditionalBodyKeys, baseUrl, baseUrlDerivedHeaders, tenantSubdomainHeaders, formSchema = null, personaBindings = new Map(), entryUrlParams = new Map(), shieldedUuids = new Set(), selectResolutions = [], outStructuredKeys = new Map(), rawCodeFields = new Map(), foldReturnSpec = null,
|
|
@@ -6127,6 +6407,53 @@ flowSteps = [], vocabulary = undefined) {
|
|
|
6127
6407
|
if (field !== null && /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field))
|
|
6128
6408
|
outDiscoveredFields.add(field);
|
|
6129
6409
|
}
|
|
6410
|
+
// Fallback for a navigateTo step's declared `payloadField` whose extracted
|
|
6411
|
+
// hash literal has ZERO recurrence anywhere in the capture — not merely
|
|
6412
|
+
// blocked by the collision guard above, but genuinely absent from every
|
|
6413
|
+
// action's URL, body, AND headers. `harvestPersonaBindings` can only ever
|
|
6414
|
+
// register the one raw literal it extracted; when the site never replays
|
|
6415
|
+
// that literal verbatim (it's transformed, mapped to a backend code, or
|
|
6416
|
+
// otherwise re-shaped before the real request), the field is structurally
|
|
6417
|
+
// unreachable by the recurrence-anchored path above, exactly like a
|
|
6418
|
+
// declared-but-never-bound schema field.
|
|
6419
|
+
//
|
|
6420
|
+
// Site-agnostic, position/causality-based correlation: a navigateTo step
|
|
6421
|
+
// always precedes the real request its facet affects, so each such
|
|
6422
|
+
// unreachable facet (in flow order) is paired with the next "new" value —
|
|
6423
|
+
// a query-string or body leaf that a later action's capture carries but
|
|
6424
|
+
// its immediately-preceding action's capture does not — found by walking
|
|
6425
|
+
// the actions in order. This is a best-effort structural guarantee, not a
|
|
6426
|
+
// completeness proof: when the adjacent action's own capture carries no
|
|
6427
|
+
// new value at all (the facet's effect is purely server-side / implicit),
|
|
6428
|
+
// or when the correlation is ambiguous (see
|
|
6429
|
+
// `correlateUnreachableNavigateToFacets`), there is nothing safe to splice
|
|
6430
|
+
// and the field(s) stay unbound rather than risk a wrong binding.
|
|
6431
|
+
const appearsAnywhereInCapture = (value) => actions.some((action) => action.capture.url.includes(value) ||
|
|
6432
|
+
(action.capture.requestPostData?.includes(value) ?? false) ||
|
|
6433
|
+
Object.values(action.capture.requestHeaders).some((h) => h.includes(value)));
|
|
6434
|
+
// Hoisted once so both the unreachable-facet fallback above and the
|
|
6435
|
+
// REST array-element optional splice below (applyOptionalArrayFacetSplice
|
|
6436
|
+
// PayloadSubstitutions) share the exact same (value, field, optional)
|
|
6437
|
+
// triples — the array splice must see each binding's `optional` flag
|
|
6438
|
+
// precisely as the flow declared it, same as the already-fixed GraphQL
|
|
6439
|
+
// array-variable path ({@link spliceFacetsIntoArrayVariable}).
|
|
6440
|
+
const navigateToFacetOrder = extractNavigateToFacetOrder(flowSteps);
|
|
6441
|
+
const unreachableNavigateToFacets = navigateToFacetOrder.filter(({ value }) => !appearsAnywhereInCapture(value));
|
|
6442
|
+
if (unreachableNavigateToFacets.length > 0) {
|
|
6443
|
+
const correlated = correlateUnreachableNavigateToFacets(unreachableNavigateToFacets, actions);
|
|
6444
|
+
for (const { field } of correlated === null ? [] : unreachableNavigateToFacets) {
|
|
6445
|
+
const diffed = correlated?.get(field);
|
|
6446
|
+
if (diffed === undefined)
|
|
6447
|
+
continue;
|
|
6448
|
+
if (!bindsWithoutCollision(diffed))
|
|
6449
|
+
continue;
|
|
6450
|
+
if (payloadAccessorByValue.has(diffed))
|
|
6451
|
+
continue;
|
|
6452
|
+
payloadAccessorByValue.set(diffed, `payload.${field}`);
|
|
6453
|
+
if (isValidJsIdentifier(field))
|
|
6454
|
+
outDiscoveredFields.add(field);
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6130
6457
|
// Job coordinates from the recon entry URL's query string (e.g.
|
|
6131
6458
|
// `?jobSeqNo=...`). Registered the same way as BaseUrl so every verbatim
|
|
6132
6459
|
// occurrence — and, via length-descending order, embedded substrings like a
|
|
@@ -6380,6 +6707,16 @@ flowSteps = [], vocabulary = undefined) {
|
|
|
6380
6707
|
const rawBodyWithFacetSplices = parsedBody !== null
|
|
6381
6708
|
? applyFacetSplicePayloadSubstitutions(rawBodyWithFormSubs, parsedBody, restPayloadFieldNames, restOptionalFieldNames)
|
|
6382
6709
|
: rawBodyWithFormSubs;
|
|
6710
|
+
// Optional-facet array-element splice — a navigateTo facet literal
|
|
6711
|
+
// recurring as one element of an array field (not the `key:value`
|
|
6712
|
+
// grammar above) must drop its own element, not freeze an `undefined`,
|
|
6713
|
+
// when its binding is optional and the caller omits it. Runs BEFORE
|
|
6714
|
+
// Mechanism B (below) can swallow the whole array wholesale as an
|
|
6715
|
+
// unparameterized structured value — see
|
|
6716
|
+
// {@link applyOptionalArrayFacetSplicePayloadSubstitutions}.
|
|
6717
|
+
const rawBodyWithOptionalArrayFacetSplices = parsedBody !== null
|
|
6718
|
+
? applyOptionalArrayFacetSplicePayloadSubstitutions(rawBodyWithFacetSplices, parsedBody, navigateToFacetOrder)
|
|
6719
|
+
: rawBodyWithFacetSplices;
|
|
6383
6720
|
// Mechanism B — parameterize whole nested caller structures
|
|
6384
6721
|
// (experienceData/educationData history, opaque eventData) BEFORE value
|
|
6385
6722
|
// substitution reaches inside them: swallowing the entire array/object first
|
|
@@ -6390,7 +6727,7 @@ flowSteps = [], vocabulary = undefined) {
|
|
|
6390
6727
|
// dependent-drill-down join field (e.g. a bulk `{"tokens":[...]}` lookup)
|
|
6391
6728
|
// must stay reachable for state threading, not get frozen as caller data.
|
|
6392
6729
|
const rawBodyWithStructuredSubs = parsedBody !== null
|
|
6393
|
-
? applyStructuredValuePayloadSubstitutions(
|
|
6730
|
+
? applyStructuredValuePayloadSubstitutions(rawBodyWithOptionalArrayFacetSplices, parsedBody, outStructuredKeys, deriveStateVarByValue(prior, cap), joinFieldValuesByStep.get(i) ?? new Set(), payloadAccessorByValue)
|
|
6394
6731
|
: rawBodyWithFormSubs;
|
|
6395
6732
|
// Whole-value caller coordinates bind here — after structured subs, BEFORE
|
|
6396
6733
|
// state threading — so a composite coordinate (a jobLocation or jobSeqNo
|
|
@@ -7313,6 +7650,50 @@ function spliceFacetsIntoStringVariable(value, fields, optionalFields = new Set(
|
|
|
7313
7650
|
}
|
|
7314
7651
|
return `[${elements.join(", ")}].join("")`;
|
|
7315
7652
|
}
|
|
7653
|
+
/**
|
|
7654
|
+
* Splices a captured array-valued GraphQL variable's elements against
|
|
7655
|
+
* navigateTo facet bindings instead of the `key:value` segment grammar
|
|
7656
|
+
* {@link spliceFacetsIntoStringVariable} matches — an array element like
|
|
7657
|
+
* `widget-x;filterId=urlFriendlyId` carries a bare literal plus a constant
|
|
7658
|
+
* trailing delimiter, with no `key:` prefix to key off of. Matching is
|
|
7659
|
+
* token-boundary guarded the same way {@link bindsWithoutCollision} (recon-
|
|
7660
|
+
* generate.ts ~7223) guards persona bindings elsewhere in this file, so a
|
|
7661
|
+
* facet literal that's merely a substring of an unrelated element never
|
|
7662
|
+
* misfires. A matched binding whose `optional` flag is set is emitted as a
|
|
7663
|
+
* conditionally-included array element (`...(payload.<field> ? [...] : [])`)
|
|
7664
|
+
* instead of inline, the same optional-splice convention
|
|
7665
|
+
* {@link spliceFacetsIntoStringVariable} uses, so an absent optional facet
|
|
7666
|
+
* drops its element rather than emitting `undefined`.
|
|
7667
|
+
*/
|
|
7668
|
+
function spliceFacetsIntoArrayVariable(value, facets) {
|
|
7669
|
+
if (!Array.isArray(value))
|
|
7670
|
+
return null;
|
|
7671
|
+
const isAlnum = (ch) => ch !== undefined && /[A-Za-z0-9]/.test(ch);
|
|
7672
|
+
const matchFacet = (element) => facets.find(({ value: literal }) => {
|
|
7673
|
+
if (element === literal)
|
|
7674
|
+
return true;
|
|
7675
|
+
if (!element.startsWith(literal))
|
|
7676
|
+
return false;
|
|
7677
|
+
return !isAlnum(element[literal.length]);
|
|
7678
|
+
});
|
|
7679
|
+
let matchCount = 0;
|
|
7680
|
+
const elements = value.map((element) => {
|
|
7681
|
+
if (typeof element !== "string")
|
|
7682
|
+
return JSON.stringify(element);
|
|
7683
|
+
const matched = matchFacet(element);
|
|
7684
|
+
if (!matched)
|
|
7685
|
+
return JSON.stringify(element);
|
|
7686
|
+
matchCount++;
|
|
7687
|
+
const suffix = element.slice(matched.value.length);
|
|
7688
|
+
const elementLiteral = `\`\${payload.${matched.field}}${escapeForTemplateLiteral(suffix)}\``;
|
|
7689
|
+
return matched.optional
|
|
7690
|
+
? `...(payload.${matched.field} ? [${elementLiteral}] : [])`
|
|
7691
|
+
: elementLiteral;
|
|
7692
|
+
});
|
|
7693
|
+
if (matchCount === 0)
|
|
7694
|
+
return null;
|
|
7695
|
+
return `[${elements.join(", ")}]`;
|
|
7696
|
+
}
|
|
7316
7697
|
/**
|
|
7317
7698
|
* Renders the variables literal for the primary-operation getGql() call —
|
|
7318
7699
|
* each key from the selected capture's own recorded variables is bound to
|
|
@@ -7324,9 +7705,12 @@ function spliceFacetsIntoStringVariable(value, fields, optionalFields = new Set(
|
|
|
7324
7705
|
* other value is emitted verbatim via JSON.stringify. `optionalFieldNames`
|
|
7325
7706
|
* marks which of `payloadFieldNames` a facet-string splice should treat as
|
|
7326
7707
|
* optional (see {@link spliceFacetsIntoStringVariable}); it has no effect on
|
|
7327
|
-
* top-level key correlation.
|
|
7708
|
+
* top-level key correlation. When a value is an array (not itself a string
|
|
7709
|
+
* key correlation candidate), its elements are matched against
|
|
7710
|
+
* `navigateToFacets` instead — see {@link spliceFacetsIntoArrayVariable} for
|
|
7711
|
+
* why that's a distinct grammar from the `key:value` one above.
|
|
7328
7712
|
*/
|
|
7329
|
-
function renderGqlVariablesExpr(variables, payloadFieldNames, optionalFieldNames = new Set()) {
|
|
7713
|
+
function renderGqlVariablesExpr(variables, payloadFieldNames, optionalFieldNames = new Set(), navigateToFacets = []) {
|
|
7330
7714
|
if (variables === null || typeof variables !== "object" || Array.isArray(variables))
|
|
7331
7715
|
return "{}";
|
|
7332
7716
|
const fields = payloadFieldNames ? [...payloadFieldNames] : [];
|
|
@@ -7334,7 +7718,8 @@ function renderGqlVariablesExpr(variables, payloadFieldNames, optionalFieldNames
|
|
|
7334
7718
|
const matchedField = fields.find((field) => field.toLowerCase() === key.toLowerCase());
|
|
7335
7719
|
const facetSpliceExpr = matchedField
|
|
7336
7720
|
? null
|
|
7337
|
-
: spliceFacetsIntoStringVariable(value, fields, optionalFieldNames)
|
|
7721
|
+
: (spliceFacetsIntoStringVariable(value, fields, optionalFieldNames) ??
|
|
7722
|
+
spliceFacetsIntoArrayVariable(value, navigateToFacets));
|
|
7338
7723
|
const valueExpr = matchedField
|
|
7339
7724
|
? `payload.${matchedField}`
|
|
7340
7725
|
: (facetSpliceExpr ?? JSON.stringify(value));
|
|
@@ -7771,7 +8156,7 @@ function isExplainedByCorrelatedSiblingField(path, parsedBody, otherBodies, sibl
|
|
|
7771
8156
|
* iteration's request, exactly the defect described in
|
|
7772
8157
|
* docs/recon-generate-nested-fold-flatmaps-away-the-parent-so-drill-params-freeze.md
|
|
7773
8158
|
* (a `packageCode`/`groupId`/`sailDate` triple that provably varies per
|
|
7774
|
-
*
|
|
8159
|
+
* voyage, silently frozen because no threaded field explained it). Path
|
|
7775
8160
|
* segments are deliberately not checked: {@link endpointKey} requires an
|
|
7776
8161
|
* identical pathname to group two captures at all, so no path segment can
|
|
7777
8162
|
* ever be observed to vary within a matched group.
|
|
@@ -10348,7 +10733,7 @@ function buildContractChecklist(opts) {
|
|
|
10348
10733
|
/** Generates a complete contract.ts source string for a plugin — exported so
|
|
10349
10734
|
* unit tests can drive the emitter directly without spawning the CLI. */
|
|
10350
10735
|
function emitContractTs(opts) {
|
|
10351
|
-
const { siteId, displayName, pascal, baseUrl, baseHeaders, minTime, safeRps, hasRateLimitProbeData = false, responseBody, responseBodySamples = [responseBody], gql, gqlQuery, endpointPath, gqlOperationName, gqlVariables, allCaptures = [], auxFiles, multiStepBody, omitExecuteHttp = false, isSubmissionFlow = false, inputBody, hasMultipartStep = false, actionSteps = [], foldReturnSpec = null, discoveredFormFields, fieldOptionsMap, discoveredOptionFields, discoveredRawOptionFields, discoveredAdditionalBodyKeys, discoveredStructuredKeys, valueConstraints = value_constraints_1.EMPTY_VALUE_CONSTRAINTS, payloadFieldNames, optionalPayloadFieldNames = new Set(), headerBindings = [], unpopulatedDeclaredVariables = [], fallbackGateSpec = {}, } = opts;
|
|
10736
|
+
const { siteId, displayName, pascal, baseUrl, baseHeaders, minTime, safeRps, hasRateLimitProbeData = false, responseBody, responseBodySamples = [responseBody], gql, gqlQuery, endpointPath, gqlOperationName, gqlVariables, allCaptures = [], auxFiles, multiStepBody, omitExecuteHttp = false, isSubmissionFlow = false, inputBody, hasMultipartStep = false, actionSteps = [], foldReturnSpec = null, discoveredFormFields, fieldOptionsMap, discoveredOptionFields, discoveredRawOptionFields, discoveredAdditionalBodyKeys, discoveredStructuredKeys, valueConstraints = value_constraints_1.EMPTY_VALUE_CONSTRAINTS, payloadFieldNames, optionalPayloadFieldNames = new Set(), flowSteps: contractFlowSteps = [], headerBindings = [], unpopulatedDeclaredVariables = [], fallbackGateSpec = {}, } = opts;
|
|
10352
10737
|
const { browserFallbackGate, httpTimeoutMs } = fallbackGateSpec;
|
|
10353
10738
|
/** Single source of truth for every GraphQL-emission decision below
|
|
10354
10739
|
* (clientImport, gqlCacheBlock, queryConst, and both executeHttpBody
|
|
@@ -10858,10 +11243,11 @@ const httpClient = createHttpClient({ schema: ${pascal}ResponseSchema, bottlenec
|
|
|
10858
11243
|
// the same captured request-variables object the signal was itself
|
|
10859
11244
|
// detected from (see paginationOperationIdentity above) — the default
|
|
10860
11245
|
// `{ q: payload.query }` REST body has no skip/count container to advance.
|
|
11246
|
+
const navigateToFacets = extractNavigateToFacetOrder(contractFlowSteps);
|
|
10861
11247
|
const gqlVariablesExpr = gqlOperationName
|
|
10862
|
-
? renderGqlVariablesExpr(gqlVariables, payloadFieldNames, optionalPayloadFieldNames)
|
|
11248
|
+
? renderGqlVariablesExpr(gqlVariables, payloadFieldNames, optionalPayloadFieldNames, navigateToFacets)
|
|
10863
11249
|
: paginationSignal
|
|
10864
|
-
? renderGqlVariablesExpr(gqlVariables, payloadFieldNames, optionalPayloadFieldNames)
|
|
11250
|
+
? renderGqlVariablesExpr(gqlVariables, payloadFieldNames, optionalPayloadFieldNames, navigateToFacets)
|
|
10865
11251
|
: "{ q: payload.query }";
|
|
10866
11252
|
/** Builds the nested `for` loop block(s) — see {@link pathToFoldLoopLines}
|
|
10867
11253
|
* — that fold every resolved plan's drill-down data onto `dataVarName`'s
|
|
@@ -11610,7 +11996,10 @@ function computeFlowPayloadFieldNames(flowSteps, vocabulary, env) {
|
|
|
11610
11996
|
if (isObj && step.navigateTo !== undefined) {
|
|
11611
11997
|
if (step.payloadField) {
|
|
11612
11998
|
payloadFieldNames.add(step.payloadField);
|
|
11613
|
-
|
|
11999
|
+
// An explicitly declared payloadField is always required at the
|
|
12000
|
+
// schema level, independent of step.optional's execution-skip
|
|
12001
|
+
// semantics (which the browser-flow step record still honors as-is).
|
|
12002
|
+
registerFieldOptionality(step.payloadField, false);
|
|
11614
12003
|
}
|
|
11615
12004
|
continue;
|
|
11616
12005
|
}
|
|
@@ -11623,9 +12012,13 @@ function computeFlowPayloadFieldNames(flowSteps, vocabulary, env) {
|
|
|
11623
12012
|
? resolveCompositePersonaFields(instruction, knownFieldValues)
|
|
11624
12013
|
: null;
|
|
11625
12014
|
const optional = isObj ? step.optional === true : false;
|
|
12015
|
+
const explicit = isObj ? step.payloadField : undefined;
|
|
11626
12016
|
if (field !== null) {
|
|
11627
12017
|
payloadFieldNames.add(field);
|
|
11628
|
-
|
|
12018
|
+
// An explicit payloadField annotation is always required at the
|
|
12019
|
+
// schema level; only the vocabulary/composite-persona heuristic falls
|
|
12020
|
+
// back to step.optional's execution-skip semantics.
|
|
12021
|
+
registerFieldOptionality(field, explicit ? false : optional);
|
|
11629
12022
|
}
|
|
11630
12023
|
if (composite !== null) {
|
|
11631
12024
|
payloadFieldNames.add(composite.fieldA);
|
|
@@ -12725,6 +13118,7 @@ async function main() {
|
|
|
12725
13118
|
isSubmissionFlow,
|
|
12726
13119
|
inputBody,
|
|
12727
13120
|
hasMultipartStep,
|
|
13121
|
+
flowSteps,
|
|
12728
13122
|
actionSteps,
|
|
12729
13123
|
foldReturnSpec,
|
|
12730
13124
|
discoveredFormFields,
|