@enricai/barnacle 1.12.64 → 1.12.66
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 +104 -7
- package/dist/scraper/flow-runner.js.map +1 -1
- package/dist/scraper/http-client.d.ts.map +1 -1
- package/dist/scraper/http-client.js +11 -7
- package/dist/scraper/http-client.js.map +1 -1
- package/dist/scripts/recon-browser.d.ts +22 -0
- package/dist/scripts/recon-browser.d.ts.map +1 -1
- package/dist/scripts/recon-browser.js +65 -3
- package/dist/scripts/recon-browser.js.map +1 -1
- package/dist/scripts/recon-generate.d.ts.map +1 -1
- package/dist/scripts/recon-generate.js +249 -41
- package/dist/scripts/recon-generate.js.map +1 -1
- package/package.json +1 -1
|
@@ -4493,7 +4493,15 @@ function replaceGuardedAgainstExistingPlaceholders(text, pattern, bindingByValue
|
|
|
4493
4493
|
* first, and the literal is gone by the time the KV pass runs — masking
|
|
4494
4494
|
* payload precedence on every call after the one that scraped it.
|
|
4495
4495
|
*/
|
|
4496
|
-
function interpolateStateValues(template, priorSteps, targetCapture, payloadAccessorByValue = new Map(), isJsonBody = false, producerBoundaryBindings = new Map(), stepIndex = -1, topLevelPayloadKvValues = new Set()
|
|
4496
|
+
function interpolateStateValues(template, priorSteps, targetCapture, payloadAccessorByValue = new Map(), isJsonBody = false, producerBoundaryBindings = new Map(), stepIndex = -1, topLevelPayloadKvValues = new Set(),
|
|
4497
|
+
// A fold target's own join-field value (see `joinFieldValuesByStep` in
|
|
4498
|
+
// `emitMultiStepExecuteHttp`) must stay a literal here — binding it to a
|
|
4499
|
+
// prior step's produced state var (e.g. `${productId}`, always the FIRST
|
|
4500
|
+
// primary item's value) freezes every per-item drill call onto that one
|
|
4501
|
+
// item; the fold loop's own later `parameterize` pass is what rebinds the
|
|
4502
|
+
// literal to `${item.<field>}` per iteration, but only if the literal
|
|
4503
|
+
// text survives this pass untouched.
|
|
4504
|
+
excludedJoinFieldValues = new Set()) {
|
|
4497
4505
|
const stateBindings = deriveStateVarByValue(priorSteps, targetCapture);
|
|
4498
4506
|
const bindingByValue = new Map();
|
|
4499
4507
|
for (const [value, accessor] of payloadAccessorByValue) {
|
|
@@ -4508,6 +4516,8 @@ function interpolateStateValues(template, priorSteps, targetCapture, payloadAcce
|
|
|
4508
4516
|
continue;
|
|
4509
4517
|
if (topLevelPayloadKvValues.has(value) && !isProducerStep)
|
|
4510
4518
|
continue;
|
|
4519
|
+
if (excludedJoinFieldValues.has(value) && !isProducerStep)
|
|
4520
|
+
continue;
|
|
4511
4521
|
bindingByValue.set(value, `\${${binding.varName}}`);
|
|
4512
4522
|
sourceNameByValue.set(value, binding.sourceName);
|
|
4513
4523
|
if (binding.restricted)
|
|
@@ -5525,7 +5535,7 @@ pascalName = null) {
|
|
|
5525
5535
|
// length/chain-eligibility scoping doesn't happen to catch the coincidence.
|
|
5526
5536
|
const url = (0, capture_filters_1.isZeroVarianceRepeatCapture)(cap, actions.map((a) => a.capture))
|
|
5527
5537
|
? cap.url
|
|
5528
|
-
: interpolateStateValues(cap.url, prior, cap, payloadAccessorByValue, false, producerBoundaryBindings, i);
|
|
5538
|
+
: interpolateStateValues(cap.url, prior, cap, payloadAccessorByValue, false, producerBoundaryBindings, i, new Set(), joinFieldValuesByStep.get(i) ?? new Set());
|
|
5529
5539
|
// Form-schema substitution runs first on the raw recon body so its
|
|
5530
5540
|
// field-id-anchored matches see the original JSON. State-threading and
|
|
5531
5541
|
// payload key-value passes then run on top. Option-id substitution runs
|
|
@@ -5595,7 +5605,7 @@ pascalName = null) {
|
|
|
5595
5605
|
? applyUrlParamPayloadSubstitutions(rawBodyWithProducerBoundary, parsedBody, urlParamBindings)
|
|
5596
5606
|
: rawBodyWithProducerBoundary;
|
|
5597
5607
|
const bodyAfterStateAndKv = rawBodyWithUrlParams
|
|
5598
|
-
? applyPayloadKeyValueSubstitutions(interpolateStateValues(rawBodyWithUrlParams, prior, cap, payloadAccessorByValue, true, producerBoundaryBindings, i, topLevelPayloadKvValues), inputBody, additionalBodies, outDiscoveredAdditionalBodyKeys, allResponseBodies)
|
|
5608
|
+
? applyPayloadKeyValueSubstitutions(interpolateStateValues(rawBodyWithUrlParams, prior, cap, payloadAccessorByValue, true, producerBoundaryBindings, i, topLevelPayloadKvValues, joinFieldValuesByStep.get(i) ?? new Set()), inputBody, additionalBodies, outDiscoveredAdditionalBodyKeys, allResponseBodies)
|
|
5599
5609
|
: "";
|
|
5600
5610
|
// Mechanism A — generic (plain-JSON, wire-key-anchored) dropdown label→code
|
|
5601
5611
|
// rewrite. Runs AFTER interpolateStateValues + the payload-KV pass, not
|
|
@@ -7885,6 +7895,60 @@ function readValueAtPath(body, path) {
|
|
|
7885
7895
|
return node[segment];
|
|
7886
7896
|
}, body);
|
|
7887
7897
|
}
|
|
7898
|
+
/**
|
|
7899
|
+
* Turns a flow author's plain dot-separated {@link FoldReturnSpec.resultsPath}
|
|
7900
|
+
* / `drillResultsPath` into the SAME {@link ARRAY_WILDCARD_SEGMENT}-bearing
|
|
7901
|
+
* shape {@link findAllObjectArrayFields} would have discovered structurally,
|
|
7902
|
+
* by walking `rawSegments` against a REAL `body` and inserting the sentinel
|
|
7903
|
+
* wherever the path crosses an array the author's segment doesn't literally
|
|
7904
|
+
* index into. A JSON dot-path is ordinary object-property notation; it has
|
|
7905
|
+
* no convention of its own for "every element of this array" (that spelling
|
|
7906
|
+
* is this module's internal detail), so an author naming a genuinely nested
|
|
7907
|
+
* per-ancestor array — e.g. `"trips.legs"` for a response shaped
|
|
7908
|
+
* `{ trips: [{ legs: [...] }] }` — always omits it. Left unresolved, that
|
|
7909
|
+
* mismatch is silent and total: `objectItemsAtPath`'s plain (no-wildcard)
|
|
7910
|
+
* branch reads a property straight off the array object, resolves to
|
|
7911
|
+
* `undefined`, and the whole spec fails to match ANY primary — and even
|
|
7912
|
+
* where a structural target already exists to fall back onto, every
|
|
7913
|
+
* primaryArrayPath equality check the fold-join merge (
|
|
7914
|
+
* {@link mergeSpecPlanOntoSamePrimary}) runs is a bare `JSON.stringify`
|
|
7915
|
+
* comparison against the structural path's own always-wildcarded form, so a
|
|
7916
|
+
* declared path that's semantically identical but spelled without the
|
|
7917
|
+
* sentinel can never match it either — the structural guess silently wins
|
|
7918
|
+
* regardless of how many per-target override branches are added. Normalizing
|
|
7919
|
+
* at the one point every one of those comparisons ultimately reads from
|
|
7920
|
+
* fixes them all at once, rather than teaching each comparison site its own
|
|
7921
|
+
* tolerance.
|
|
7922
|
+
*
|
|
7923
|
+
* A literal numeric segment (see {@link ARRAY_INDEX_KEY_PATTERN}) is
|
|
7924
|
+
* left as-is — an author who names one exact array element that way already
|
|
7925
|
+
* resolves correctly via `readValueAtPath`'s ordinary bracket access, and
|
|
7926
|
+
* rewriting it to the wildcard would silently widen "this one element" into
|
|
7927
|
+
* "every element". Uses the array's own first object-array item as the
|
|
7928
|
+
* representative to keep walking deeper (mirroring
|
|
7929
|
+
* {@link findAllObjectArrayFieldsUncached}'s own DFS-first-match precedent);
|
|
7930
|
+
* a path that turns out to disagree across sibling elements is exactly what
|
|
7931
|
+
* {@link objectItemsAtPath}'s later wildcard-driven `.flatMap` — not this
|
|
7932
|
+
* one-shot shape walk — is responsible for reconciling.
|
|
7933
|
+
*/
|
|
7934
|
+
function resolveDeclaredArrayPath(rawSegments, body) {
|
|
7935
|
+
const resolved = [];
|
|
7936
|
+
let node = body;
|
|
7937
|
+
for (const segment of rawSegments) {
|
|
7938
|
+
if (Array.isArray(node) &&
|
|
7939
|
+
segment !== ARRAY_WILDCARD_SEGMENT &&
|
|
7940
|
+
!ARRAY_INDEX_KEY_PATTERN.test(segment)) {
|
|
7941
|
+
resolved.push(ARRAY_WILDCARD_SEGMENT);
|
|
7942
|
+
node = node.find(isObjectArrayItem);
|
|
7943
|
+
}
|
|
7944
|
+
resolved.push(segment);
|
|
7945
|
+
node =
|
|
7946
|
+
node !== null && typeof node === "object"
|
|
7947
|
+
? node[segment]
|
|
7948
|
+
: undefined;
|
|
7949
|
+
}
|
|
7950
|
+
return resolved;
|
|
7951
|
+
}
|
|
7888
7952
|
/** The object items of the array at `path` — the same subset
|
|
7889
7953
|
* {@link findObjectArrayField} exposes as `items`, but anchored to a
|
|
7890
7954
|
* caller-supplied path instead of discovered by DFS. `null` when `path`
|
|
@@ -8184,14 +8248,33 @@ function compileFoldReturnEndpointMatcher(spec) {
|
|
|
8184
8248
|
function compileFoldReturnResultsMatcher(spec) {
|
|
8185
8249
|
if (spec === null)
|
|
8186
8250
|
return () => false;
|
|
8187
|
-
const
|
|
8188
|
-
return (capture) => objectItemsAtPath(capture.responseBody,
|
|
8251
|
+
const rawResultsPath = spec.resultsPath.split(".");
|
|
8252
|
+
return (capture) => objectItemsAtPath(capture.responseBody, resolveDeclaredArrayPath(rawResultsPath, capture.responseBody)) !== null;
|
|
8189
8253
|
}
|
|
8190
8254
|
function buildFoldPlanFromSpec(actions, spec,
|
|
8191
8255
|
// See detectDrillDownFoldPlan's identical parameter — constrains this
|
|
8192
8256
|
// function's own primaryStepIndex loop to the same anchor identity.
|
|
8193
|
-
primaryIdentityAnchor = null
|
|
8194
|
-
|
|
8257
|
+
primaryIdentityAnchor = null,
|
|
8258
|
+
// When given, restricts the loop to this ONE primaryStepIndex instead of
|
|
8259
|
+
// scanning every occurrence and keeping only the freshest. Used by
|
|
8260
|
+
// mergeSpecPlanOntoSamePrimary to resolve a declared joinFields override
|
|
8261
|
+
// independently for EACH re-issued/paginated primary occurrence, since the
|
|
8262
|
+
// freshest-wins scan below only ever resolves the LAST occurrence.
|
|
8263
|
+
exactPrimaryStepIndex = null,
|
|
8264
|
+
// When given, restricts the matching-drill scan below to candidates whose
|
|
8265
|
+
// OWN endpoint (not a later chain hop) matches this key. A single primary
|
|
8266
|
+
// occurrence can structurally resolve MULTIPLE independent per-item
|
|
8267
|
+
// targets — one per distinct drill endpoint (e.g. a genuinely per-item
|
|
8268
|
+
// drill loop where each item's own request lands on the SAME endpoint
|
|
8269
|
+
// pattern but a distinct path/endpoint identity) — and this function's own
|
|
8270
|
+
// freshest-first scan below `break`s after the FIRST endpoint it resolves,
|
|
8271
|
+
// so an unrestricted call can only ever return an override for ONE of
|
|
8272
|
+
// them. Used by mergeSpecPlanOntoSamePrimary to resolve the declared
|
|
8273
|
+
// joinFields override independently for EACH structural target, keyed by
|
|
8274
|
+
// its own drill endpoint, instead of only the one this function's
|
|
8275
|
+
// freshest-first scan happens to land on first.
|
|
8276
|
+
restrictToDrillEndpointKey = null) {
|
|
8277
|
+
const rawPrimaryArrayPath = spec.resultsPath.split(".");
|
|
8195
8278
|
const matchesFoldReturnEndpoint = compileFoldReturnEndpointMatcher(spec);
|
|
8196
8279
|
// Built once and shared across every primaryStepIndex — see
|
|
8197
8280
|
// buildRequestValueIndex's docstring. Lets each primary immediately tell
|
|
@@ -8201,10 +8284,14 @@ primaryIdentityAnchor = null) {
|
|
|
8201
8284
|
const requestValueIndex = buildRequestValueIndex(actions);
|
|
8202
8285
|
let freshestPlan = null;
|
|
8203
8286
|
for (let primaryStepIndex = 0; primaryStepIndex < actions.length; primaryStepIndex++) {
|
|
8287
|
+
if (exactPrimaryStepIndex !== null && primaryStepIndex !== exactPrimaryStepIndex) {
|
|
8288
|
+
continue;
|
|
8289
|
+
}
|
|
8204
8290
|
if (primaryIdentityAnchor !== null &&
|
|
8205
8291
|
operationGroupKey(actions[primaryStepIndex].capture) !== primaryIdentityAnchor) {
|
|
8206
8292
|
continue;
|
|
8207
8293
|
}
|
|
8294
|
+
const primaryArrayPath = resolveDeclaredArrayPath(rawPrimaryArrayPath, actions[primaryStepIndex].capture.responseBody);
|
|
8208
8295
|
const primaryItems = objectItemsAtPath(actions[primaryStepIndex].capture.responseBody, primaryArrayPath);
|
|
8209
8296
|
if (!primaryItems)
|
|
8210
8297
|
continue;
|
|
@@ -8229,7 +8316,9 @@ primaryIdentityAnchor = null) {
|
|
|
8229
8316
|
// response-only fallback below exists to resolve.
|
|
8230
8317
|
const matchingDrillStepIndices = [];
|
|
8231
8318
|
for (let drillStepIndex = primaryStepIndex + 1; drillStepIndex < actions.length; drillStepIndex++) {
|
|
8232
|
-
if (matchesFoldReturnEndpoint(actions[drillStepIndex].capture)
|
|
8319
|
+
if (matchesFoldReturnEndpoint(actions[drillStepIndex].capture) &&
|
|
8320
|
+
(restrictToDrillEndpointKey === null ||
|
|
8321
|
+
endpointKey(actions[drillStepIndex].capture.url) === restrictToDrillEndpointKey)) {
|
|
8233
8322
|
matchingDrillStepIndices.push(drillStepIndex);
|
|
8234
8323
|
}
|
|
8235
8324
|
}
|
|
@@ -8291,7 +8380,7 @@ primaryIdentityAnchor = null) {
|
|
|
8291
8380
|
if (spec.drillResultsPath === undefined) {
|
|
8292
8381
|
return findObjectArrayFieldOrWholeObject(drill.capture.responseBody)?.path ?? null;
|
|
8293
8382
|
}
|
|
8294
|
-
const path = spec.drillResultsPath.split(".");
|
|
8383
|
+
const path = resolveDeclaredArrayPath(spec.drillResultsPath.split("."), drill.capture.responseBody);
|
|
8295
8384
|
return objectItemsAtPath(drill.capture.responseBody, path) ? path : null;
|
|
8296
8385
|
})();
|
|
8297
8386
|
const { chain, chainArrayPath, chainTerminalIndex } = computeFoldChain(actions, entryIndex, drillArrayPath ?? []);
|
|
@@ -8363,6 +8452,26 @@ function isDescendantArrayPath(candidate, base) {
|
|
|
8363
8452
|
* spec's own drill indices look independent, so the heuristic's wrong-level
|
|
8364
8453
|
* guess never survives alongside the corrected one.
|
|
8365
8454
|
*/
|
|
8455
|
+
/**
|
|
8456
|
+
* The stable identity of the logical drill-down a {@link FoldTarget}
|
|
8457
|
+
* resolves onto: the endpoint of its chain's TERMINAL call (the response
|
|
8458
|
+
* actually folded — see {@link FoldTarget.chainTerminalIndex}) paired with
|
|
8459
|
+
* the specific array read off that response (see
|
|
8460
|
+
* {@link FoldTarget.chainArrayPath}). Neither piece alone is stable enough to
|
|
8461
|
+
* compare two independently-resolved targets by: `drillStepIndex` (the
|
|
8462
|
+
* chain's ENTRY point) can legitimately differ between the structural
|
|
8463
|
+
* heuristic's own request-threading resolution and a spec's
|
|
8464
|
+
* response-only/upstream-hop resolution even when both terminate at the same
|
|
8465
|
+
* call (see {@link buildFoldPlanFromSpec}'s `entryIndex` docstring); and the
|
|
8466
|
+
* terminal endpoint alone can collide when one response happens to carry TWO
|
|
8467
|
+
* unrelated per-item arrays for two entirely different primaries (e.g. a
|
|
8468
|
+
* combined status response naming both `entries` and `contracts`). Pairing
|
|
8469
|
+
* the terminal's endpoint with the specific array it reads disambiguates
|
|
8470
|
+
* both cases at once.
|
|
8471
|
+
*/
|
|
8472
|
+
function foldTargetDrillIdentity(actions, target) {
|
|
8473
|
+
return `${endpointKey(actions[target.chainTerminalIndex].capture.url)}\u0000${target.chainArrayPath.join(".")}`;
|
|
8474
|
+
}
|
|
8366
8475
|
function mergeSpecPlanOntoSamePrimary(structuralPlans, actions, foldReturnSpec,
|
|
8367
8476
|
// See detectDrillDownFoldPlan's identical parameter. Threaded into this
|
|
8368
8477
|
// function's own buildFoldPlanFromSpec call so a spec plan resolved here
|
|
@@ -8371,9 +8480,82 @@ function mergeSpecPlanOntoSamePrimary(structuralPlans, actions, foldReturnSpec,
|
|
|
8371
8480
|
primaryIdentityAnchor = null) {
|
|
8372
8481
|
if (foldReturnSpec === null)
|
|
8373
8482
|
return [...structuralPlans];
|
|
8483
|
+
// Override joinFields on EVERY structural primary occurrence the spec can
|
|
8484
|
+
// independently resolve at its OWN primaryStepIndex — not just whichever
|
|
8485
|
+
// single occurrence the freshest-wins `specPlan` resolution below happens
|
|
8486
|
+
// to land on. A re-issued/paginated primary (same endpoint, multiple
|
|
8487
|
+
// captures) produces one independent structural plan per occurrence; each
|
|
8488
|
+
// must get the declared override, not just the freshest one.
|
|
8489
|
+
const structuralPlansWithPerOccurrenceOverrides = structuralPlans.map((plan) => {
|
|
8490
|
+
// Resolved once per structural TARGET (keyed on that target's own drill
|
|
8491
|
+
// endpoint), not once for the whole primary occurrence: buildFoldPlanFromSpec's
|
|
8492
|
+
// freshest-first scan `break`s after the first drill endpoint it resolves, so a
|
|
8493
|
+
// single unrestricted call can only ever produce an override for ONE of a
|
|
8494
|
+
// primary's structural targets even when it has several (e.g. a genuinely
|
|
8495
|
+
// per-item drill loop resolving one independent target per item, each at its
|
|
8496
|
+
// own endpoint). See buildFoldPlanFromSpec's restrictToDrillEndpointKey docstring.
|
|
8497
|
+
const matchesDeclaredEndpoint = compileFoldReturnEndpointMatcher(foldReturnSpec);
|
|
8498
|
+
const declaredPrimaryArrayPath = resolveDeclaredArrayPath(foldReturnSpec.resultsPath.split("."), actions[plan.primaryStepIndex].capture.responseBody);
|
|
8499
|
+
const mergedTargets = plan.targets.map((target) => {
|
|
8500
|
+
const ownSpecPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor, plan.primaryStepIndex, endpointKey(actions[target.drillStepIndex].capture.url));
|
|
8501
|
+
if (ownSpecPlan !== null &&
|
|
8502
|
+
JSON.stringify(ownSpecPlan.primaryArrayPath) === JSON.stringify(plan.primaryArrayPath)) {
|
|
8503
|
+
// ownSpecPlan was resolved with restrictToDrillEndpointKey pinned to
|
|
8504
|
+
// this target's OWN drill endpoint, so its single target (see
|
|
8505
|
+
// buildFoldPlanFromSpec's `targets: [{...}]` literal — it never
|
|
8506
|
+
// returns more than one) IS the spec's resolution of this restricted
|
|
8507
|
+
// endpoint by construction. Matching it back to `target` via
|
|
8508
|
+
// foldTargetDrillIdentity is not just redundant but actively wrong: a
|
|
8509
|
+
// multi-hop drill chain can make the spec's forward walk
|
|
8510
|
+
// (buildFoldPlanFromSpec's computeFoldChain resolution) land on a
|
|
8511
|
+
// different chainTerminalIndex/chainArrayPath than the structural
|
|
8512
|
+
// heuristic did for the exact same restricted endpoint, so the
|
|
8513
|
+
// identity lookup misses and the declared joinFields override is
|
|
8514
|
+
// silently dropped in favor of the heuristic's structural guess.
|
|
8515
|
+
const specTarget = ownSpecPlan.targets[0];
|
|
8516
|
+
return specTarget === undefined ? target : { ...target, joinFields: specTarget.joinFields };
|
|
8517
|
+
}
|
|
8518
|
+
// buildFoldPlanFromSpec came back null (or landed on a different
|
|
8519
|
+
// primaryArrayPath) because it couldn't resolve a
|
|
8520
|
+
// primaryMatchedItemIndex for this drill — the join value it names
|
|
8521
|
+
// sits on the drill response but isn't discoverable at that same
|
|
8522
|
+
// literal path on any primary item (a generation-time-only schema-
|
|
8523
|
+
// inference concern; see resolveSpecMatchedPrimaryItemIndexFromResponse).
|
|
8524
|
+
// That failure has nothing to do with whether the declared joinFields
|
|
8525
|
+
// should be trusted at RUNTIME: emitFoldMatchAndMergeLines only ever
|
|
8526
|
+
// reads target.joinFields (a field-name string), never
|
|
8527
|
+
// primaryMatchedItemIndex. Once the declared spec's own endpoint
|
|
8528
|
+
// pattern and resultsPath demonstrably match THIS already
|
|
8529
|
+
// structurally-resolved target, the declared joinFields must win over
|
|
8530
|
+
// the structural guess regardless of whether a representative item
|
|
8531
|
+
// could be picked.
|
|
8532
|
+
// Test against the chain's TERMINAL capture (where the fold array —
|
|
8533
|
+
// and thus the declared identifier field — actually lives), not the
|
|
8534
|
+
// structural heuristic's threading-entry `drillStepIndex`: on a
|
|
8535
|
+
// multi-hop chain the two can diverge (see foldTargetDrillIdentity's
|
|
8536
|
+
// docstring above), and a declared endpointPattern names the call
|
|
8537
|
+
// that holds the array, not whichever earlier hop some other field
|
|
8538
|
+
// happened to be threaded into.
|
|
8539
|
+
const targetMatchesDeclaredEndpoint = matchesDeclaredEndpoint(actions[target.chainTerminalIndex].capture);
|
|
8540
|
+
const primaryArrayPathMatchesSpec = JSON.stringify(declaredPrimaryArrayPath) === JSON.stringify(plan.primaryArrayPath);
|
|
8541
|
+
return targetMatchesDeclaredEndpoint && primaryArrayPathMatchesSpec
|
|
8542
|
+
? { ...target, joinFields: foldReturnSpec.joinFields }
|
|
8543
|
+
: target;
|
|
8544
|
+
});
|
|
8545
|
+
// A spec-declared target the heuristic missed entirely (an independent
|
|
8546
|
+
// drill-down at a NEW endpoint) is still found via the original
|
|
8547
|
+
// unrestricted resolution, which is free to land on any endpoint.
|
|
8548
|
+
const ownSpecPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor, plan.primaryStepIndex);
|
|
8549
|
+
const existingDrillIdentities = new Set(plan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
|
|
8550
|
+
const newTargets = ownSpecPlan !== null &&
|
|
8551
|
+
JSON.stringify(ownSpecPlan.primaryArrayPath) === JSON.stringify(plan.primaryArrayPath)
|
|
8552
|
+
? ownSpecPlan.targets.filter((target) => !existingDrillIdentities.has(foldTargetDrillIdentity(actions, target)))
|
|
8553
|
+
: [];
|
|
8554
|
+
return { ...plan, targets: [...mergedTargets, ...newTargets] };
|
|
8555
|
+
});
|
|
8374
8556
|
const specPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor);
|
|
8375
8557
|
if (specPlan === null)
|
|
8376
|
-
return
|
|
8558
|
+
return structuralPlansWithPerOccurrenceOverrides;
|
|
8377
8559
|
const specPrimaryEndpointKey = endpointKey(actions[specPlan.primaryStepIndex].capture.url);
|
|
8378
8560
|
// Deliberately keyed on the RAW primaryStepIndex (not endpoint identity):
|
|
8379
8561
|
// this branch trusts the structural target's own chain/drillArrayPath and
|
|
@@ -8383,7 +8565,7 @@ primaryIdentityAnchor = null) {
|
|
|
8383
8565
|
// different index) may have matched a different item entirely, so it must
|
|
8384
8566
|
// fall through to the wholesale-replace (sharedDrillPlan) or union
|
|
8385
8567
|
// (consumedPrimarySteps) handling below instead of merging by identity.
|
|
8386
|
-
const samePrimaryPlan =
|
|
8568
|
+
const samePrimaryPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => plan.primaryStepIndex === specPlan.primaryStepIndex &&
|
|
8387
8569
|
JSON.stringify(plan.primaryArrayPath) === JSON.stringify(specPlan.primaryArrayPath));
|
|
8388
8570
|
if (samePrimaryPlan !== undefined) {
|
|
8389
8571
|
// A structural target serving the SAME drill-down the spec declares
|
|
@@ -8403,38 +8585,53 @@ primaryIdentityAnchor = null) {
|
|
|
8403
8585
|
// but equally valid, occurrence of the SAME re-issued/paginated drill
|
|
8404
8586
|
// endpoint — raw-index equality only happened to override when both
|
|
8405
8587
|
// resolutions picked the identical occurrence, silently leaving every
|
|
8406
|
-
// other occurrence's joinFields un-overridden.
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8588
|
+
// other occurrence's joinFields un-overridden. Keyed on each target's
|
|
8589
|
+
// {@link foldTargetDrillIdentity} — the terminal endpoint paired with the
|
|
8590
|
+
// specific array it reads — rather than `drillStepIndex`: a spec's
|
|
8591
|
+
// declared join value often resolves only through an upstream entry hop
|
|
8592
|
+
// (e.g. a header-threaded token) that isn't the endpointPattern-matched
|
|
8593
|
+
// call itself, so its `drillStepIndex` (== that entry hop) can
|
|
8594
|
+
// legitimately differ from the structural heuristic's own
|
|
8595
|
+
// `drillStepIndex` (its own, possibly different, threading entry point)
|
|
8596
|
+
// even when both chains terminate at the exact same logical drill-down
|
|
8597
|
+
// call and array.
|
|
8598
|
+
const specTargetsByDrillIdentity = new Map(specPlan.targets.map((target) => [foldTargetDrillIdentity(actions, target), target]));
|
|
8599
|
+
return structuralPlansWithPerOccurrenceOverrides.map((plan) => {
|
|
8412
8600
|
if (plan !== samePrimaryPlan)
|
|
8413
8601
|
return plan;
|
|
8414
8602
|
const mergedTargets = plan.targets.map((target) => {
|
|
8415
|
-
const specTarget =
|
|
8603
|
+
const specTarget = specTargetsByDrillIdentity.get(foldTargetDrillIdentity(actions, target));
|
|
8416
8604
|
return specTarget === undefined ? target : { ...target, joinFields: specTarget.joinFields };
|
|
8417
8605
|
});
|
|
8418
|
-
const
|
|
8419
|
-
const newTargets = specPlan.targets.filter((target) => !
|
|
8606
|
+
const existingDrillIdentities = new Set(plan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
|
|
8607
|
+
const newTargets = specPlan.targets.filter((target) => !existingDrillIdentities.has(foldTargetDrillIdentity(actions, target)));
|
|
8420
8608
|
return { ...plan, targets: [...mergedTargets, ...newTargets] };
|
|
8421
8609
|
});
|
|
8422
8610
|
}
|
|
8423
8611
|
// No exact (primaryStepIndex, primaryArrayPath) match, but a structural
|
|
8424
8612
|
// plan on the SAME primaryStepIndex may still have independently detected
|
|
8425
|
-
// a target at the SAME
|
|
8426
|
-
// wrong array level (e.g. a shallower structural guess that happens to
|
|
8427
|
-
// share the spec's drill-down call).
|
|
8428
|
-
//
|
|
8429
|
-
//
|
|
8430
|
-
//
|
|
8431
|
-
//
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8613
|
+
// a target at the SAME drill ENDPOINT+array a spec target names — just at
|
|
8614
|
+
// the wrong array level (e.g. a shallower structural guess that happens to
|
|
8615
|
+
// share the spec's drill-down call). Matched by {@link
|
|
8616
|
+
// foldTargetDrillIdentity} (see the samePrimaryPlan branch above for why
|
|
8617
|
+
// the terminal+array, not the entry `drillStepIndex` alone, is the stable
|
|
8618
|
+
// identity to compare on) — not raw drillStepIndex equality:
|
|
8619
|
+
// buildFoldPlanFromSpec's own entryIndex resolution and the structural
|
|
8620
|
+
// heuristic's request-threading scan can each land on a different, but
|
|
8621
|
+
// equally valid, occurrence/hop of the SAME endpoint (or the same single
|
|
8622
|
+
// occurrence reached via a different chain entry point), and raw-index
|
|
8623
|
+
// equality only happens to hold when both resolutions pick the identical
|
|
8624
|
+
// index. That structural target's own primaryArrayPath/drillArrayPath/
|
|
8625
|
+
// joinFields describe the heuristic's guess, not the flow author's
|
|
8626
|
+
// declaration, so the spec target replaces it wholesale rather than being
|
|
8627
|
+
// discarded by the mismatched-array-path branch below or partially merged
|
|
8628
|
+
// as if the array level matched.
|
|
8629
|
+
const specDrillIdentities = new Set(specPlan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
|
|
8630
|
+
const sharedDrillPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
|
|
8631
|
+
plan.targets.some((target) => specDrillIdentities.has(foldTargetDrillIdentity(actions, target))));
|
|
8435
8632
|
if (sharedDrillPlan !== undefined) {
|
|
8436
|
-
const remainingTargets = sharedDrillPlan.targets.filter((target) => !
|
|
8437
|
-
const otherPlans =
|
|
8633
|
+
const remainingTargets = sharedDrillPlan.targets.filter((target) => !specDrillIdentities.has(foldTargetDrillIdentity(actions, target)));
|
|
8634
|
+
const otherPlans = structuralPlansWithPerOccurrenceOverrides.filter((plan) => plan !== sharedDrillPlan);
|
|
8438
8635
|
const keptStructuralPlans = remainingTargets.length === 0
|
|
8439
8636
|
? otherPlans
|
|
8440
8637
|
: [...otherPlans, { ...sharedDrillPlan, targets: remainingTargets }];
|
|
@@ -8449,10 +8646,13 @@ primaryIdentityAnchor = null) {
|
|
|
8449
8646
|
// the independence guard below would otherwise do, since a
|
|
8450
8647
|
// level-correcting spec's own drill indices can look entirely
|
|
8451
8648
|
// independent) or silently dropped by the mismatched-array-path checks.
|
|
8452
|
-
const supersededShallowerPlan =
|
|
8649
|
+
const supersededShallowerPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
|
|
8453
8650
|
isDescendantArrayPath(specPlan.primaryArrayPath, plan.primaryArrayPath));
|
|
8454
8651
|
if (supersededShallowerPlan !== undefined) {
|
|
8455
|
-
return [
|
|
8652
|
+
return [
|
|
8653
|
+
...structuralPlansWithPerOccurrenceOverrides.filter((plan) => plan !== supersededShallowerPlan),
|
|
8654
|
+
specPlan,
|
|
8655
|
+
];
|
|
8456
8656
|
}
|
|
8457
8657
|
// Keyed by the (primaryStepIndex, primaryArrayPath) pair, not
|
|
8458
8658
|
// primaryStepIndex alone — a structural plan only ever consumed ITS OWN
|
|
@@ -8469,7 +8669,7 @@ primaryIdentityAnchor = null) {
|
|
|
8469
8669
|
// contends for.
|
|
8470
8670
|
const consumedIndices = new Set();
|
|
8471
8671
|
const consumedPrimarySteps = new Set();
|
|
8472
|
-
for (const plan of
|
|
8672
|
+
for (const plan of structuralPlansWithPerOccurrenceOverrides) {
|
|
8473
8673
|
const planPrimaryEndpointKey = endpointKey(actions[plan.primaryStepIndex].capture.url);
|
|
8474
8674
|
consumedPrimarySteps.add(`${planPrimaryEndpointKey}:${JSON.stringify(plan.primaryArrayPath)}`);
|
|
8475
8675
|
for (const target of plan.targets) {
|
|
@@ -8480,19 +8680,27 @@ primaryIdentityAnchor = null) {
|
|
|
8480
8680
|
!consumedPrimarySteps.has(`${specPrimaryEndpointKey}:${JSON.stringify(specPlan.primaryArrayPath)}`) &&
|
|
8481
8681
|
specPlan.targets.every((target) => target.chain.every((chainIndex) => !consumedIndices.has(chainIndex)));
|
|
8482
8682
|
if (specConsumesOnlyItsOwnIndices)
|
|
8483
|
-
return [...
|
|
8683
|
+
return [...structuralPlansWithPerOccurrenceOverrides, specPlan];
|
|
8484
8684
|
// The spec's primary endpoint/array is already consumed by a structural
|
|
8485
8685
|
// plan on a DIFFERENT capture of the same re-issued primary operation
|
|
8486
8686
|
// (paginated primary), but its own target chains touch no index that plan
|
|
8487
8687
|
// already depends on — this is the same identity, not a raw-index
|
|
8488
8688
|
// coincidence, so union the spec's targets onto it rather than silently
|
|
8489
|
-
// dropping a declaration the flow author explicitly wrote.
|
|
8490
|
-
|
|
8689
|
+
// dropping a declaration the flow author explicitly wrote. Membership in
|
|
8690
|
+
// "already covered" is decided by {@link foldTargetDrillIdentity} (see the
|
|
8691
|
+
// samePrimaryPlan branch above for why the terminal+array, not the entry
|
|
8692
|
+
// `drillStepIndex` alone, is the stable identity) — not raw drillStepIndex
|
|
8693
|
+
// equality. A spec target sharing a structural target's drill identity is
|
|
8694
|
+
// a re-declaration of that SAME drill-down (handled as a wholesale replace
|
|
8695
|
+
// by sharedDrillPlan above), not an independent one to union — so this
|
|
8696
|
+
// only ever unions genuinely new endpoints onto an already-identified
|
|
8697
|
+
// primary.
|
|
8698
|
+
const sameIdentityPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
|
|
8491
8699
|
JSON.stringify(plan.primaryArrayPath) === JSON.stringify(specPlan.primaryArrayPath) &&
|
|
8492
|
-
specPlan.targets.every((target) => !plan.targets.some((t) => t
|
|
8700
|
+
specPlan.targets.every((target) => !plan.targets.some((t) => foldTargetDrillIdentity(actions, t) === foldTargetDrillIdentity(actions, target))));
|
|
8493
8701
|
if (sameIdentityPlan === undefined)
|
|
8494
|
-
return [...
|
|
8495
|
-
return
|
|
8702
|
+
return [...structuralPlansWithPerOccurrenceOverrides];
|
|
8703
|
+
return structuralPlansWithPerOccurrenceOverrides.map((plan) => plan === sameIdentityPlan ? { ...plan, targets: [...plan.targets, ...specPlan.targets] } : plan);
|
|
8496
8704
|
}
|
|
8497
8705
|
/**
|
|
8498
8706
|
* The single fold-plan entry point: {@link detectDrillDownFoldPlan}'s
|