@enricai/barnacle 1.12.63 → 1.12.65
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/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 +21 -18
- package/dist/scripts/recon-browser.d.ts.map +1 -1
- package/dist/scripts/recon-browser.js +155 -10
- package/dist/scripts/recon-browser.js.map +1 -1
- package/dist/scripts/recon-generate-multicall-fixture.d.ts +13 -0
- package/dist/scripts/recon-generate-multicall-fixture.d.ts.map +1 -1
- package/dist/scripts/recon-generate-multicall-fixture.js +44 -0
- package/dist/scripts/recon-generate-multicall-fixture.js.map +1 -1
- package/dist/scripts/recon-generate.d.ts.map +1 -1
- package/dist/scripts/recon-generate.js +211 -45
- package/dist/scripts/recon-generate.js.map +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
@@ -6067,11 +6077,27 @@ pascalName = null) {
|
|
|
6067
6077
|
itemVarRefPattern.test(paramBody)) {
|
|
6068
6078
|
referencesItemVar = true;
|
|
6069
6079
|
}
|
|
6070
|
-
|
|
6080
|
+
// Only bind this hop's response to a local when something actually
|
|
6081
|
+
// reads it back: the chain terminal (consumed by
|
|
6082
|
+
// emitFoldMatchAndMergeLines via `terminalStep.varName` below) or a
|
|
6083
|
+
// non-header `produces` entry some later step's request threads
|
|
6084
|
+
// (header threading goes through httpClient's own `bind` store, not
|
|
6085
|
+
// a local — see the header branch below). A hop chained purely to
|
|
6086
|
+
// unlock a later request (e.g. it sets server-side state via a
|
|
6087
|
+
// header token) has no reader for its own response and must stay
|
|
6088
|
+
// unbound, or the emitted loop declares a variable nothing
|
|
6089
|
+
// references (Biome noUnusedVariables).
|
|
6090
|
+
const isChainTerminal = chainIndex === target.chainTerminalIndex;
|
|
6091
|
+
const hasReferencedProduce = chainStep.produces.some((p) => p.kind !== "header" && referencedNames.has(p.name) && !chainDeclared.has(p.name));
|
|
6092
|
+
const bindsChainResponse = isChainTerminal || hasReferencedProduce;
|
|
6093
|
+
chainLines.push(bindsChainResponse
|
|
6094
|
+
? ` const ${chainStep.varName} = (await httpClient(\`${paramUrl}\`, {`
|
|
6095
|
+
: ` await httpClient(\`${paramUrl}\`, {`, ` method: ${JSON.stringify(chainRendered.method)},`);
|
|
6071
6096
|
const joined = [paramHeaders, paramBody].filter((s) => s !== "").join(" ");
|
|
6072
6097
|
if (joined !== "")
|
|
6073
6098
|
chainLines.push(` ${joined}`);
|
|
6074
|
-
chainLines.push(` schema: ${chainRendered.schemaExpr}
|
|
6099
|
+
chainLines.push(` schema: ${chainRendered.schemaExpr},`);
|
|
6100
|
+
chainLines.push(bindsChainResponse ? ` })) as Record<string, unknown>;` : ` });`);
|
|
6075
6101
|
for (const p of chainStep.produces) {
|
|
6076
6102
|
if (p.kind === "header") {
|
|
6077
6103
|
chainUsesHeaderThreading = true;
|
|
@@ -8174,7 +8200,26 @@ function compileFoldReturnResultsMatcher(spec) {
|
|
|
8174
8200
|
function buildFoldPlanFromSpec(actions, spec,
|
|
8175
8201
|
// See detectDrillDownFoldPlan's identical parameter — constrains this
|
|
8176
8202
|
// function's own primaryStepIndex loop to the same anchor identity.
|
|
8177
|
-
primaryIdentityAnchor = null
|
|
8203
|
+
primaryIdentityAnchor = null,
|
|
8204
|
+
// When given, restricts the loop to this ONE primaryStepIndex instead of
|
|
8205
|
+
// scanning every occurrence and keeping only the freshest. Used by
|
|
8206
|
+
// mergeSpecPlanOntoSamePrimary to resolve a declared joinFields override
|
|
8207
|
+
// independently for EACH re-issued/paginated primary occurrence, since the
|
|
8208
|
+
// freshest-wins scan below only ever resolves the LAST occurrence.
|
|
8209
|
+
exactPrimaryStepIndex = null,
|
|
8210
|
+
// When given, restricts the matching-drill scan below to candidates whose
|
|
8211
|
+
// OWN endpoint (not a later chain hop) matches this key. A single primary
|
|
8212
|
+
// occurrence can structurally resolve MULTIPLE independent per-item
|
|
8213
|
+
// targets — one per distinct drill endpoint (e.g. a genuinely per-item
|
|
8214
|
+
// drill loop where each item's own request lands on the SAME endpoint
|
|
8215
|
+
// pattern but a distinct path/endpoint identity) — and this function's own
|
|
8216
|
+
// freshest-first scan below `break`s after the FIRST endpoint it resolves,
|
|
8217
|
+
// so an unrestricted call can only ever return an override for ONE of
|
|
8218
|
+
// them. Used by mergeSpecPlanOntoSamePrimary to resolve the declared
|
|
8219
|
+
// joinFields override independently for EACH structural target, keyed by
|
|
8220
|
+
// its own drill endpoint, instead of only the one this function's
|
|
8221
|
+
// freshest-first scan happens to land on first.
|
|
8222
|
+
restrictToDrillEndpointKey = null) {
|
|
8178
8223
|
const primaryArrayPath = spec.resultsPath.split(".");
|
|
8179
8224
|
const matchesFoldReturnEndpoint = compileFoldReturnEndpointMatcher(spec);
|
|
8180
8225
|
// Built once and shared across every primaryStepIndex — see
|
|
@@ -8185,6 +8230,9 @@ primaryIdentityAnchor = null) {
|
|
|
8185
8230
|
const requestValueIndex = buildRequestValueIndex(actions);
|
|
8186
8231
|
let freshestPlan = null;
|
|
8187
8232
|
for (let primaryStepIndex = 0; primaryStepIndex < actions.length; primaryStepIndex++) {
|
|
8233
|
+
if (exactPrimaryStepIndex !== null && primaryStepIndex !== exactPrimaryStepIndex) {
|
|
8234
|
+
continue;
|
|
8235
|
+
}
|
|
8188
8236
|
if (primaryIdentityAnchor !== null &&
|
|
8189
8237
|
operationGroupKey(actions[primaryStepIndex].capture) !== primaryIdentityAnchor) {
|
|
8190
8238
|
continue;
|
|
@@ -8213,7 +8261,9 @@ primaryIdentityAnchor = null) {
|
|
|
8213
8261
|
// response-only fallback below exists to resolve.
|
|
8214
8262
|
const matchingDrillStepIndices = [];
|
|
8215
8263
|
for (let drillStepIndex = primaryStepIndex + 1; drillStepIndex < actions.length; drillStepIndex++) {
|
|
8216
|
-
if (matchesFoldReturnEndpoint(actions[drillStepIndex].capture)
|
|
8264
|
+
if (matchesFoldReturnEndpoint(actions[drillStepIndex].capture) &&
|
|
8265
|
+
(restrictToDrillEndpointKey === null ||
|
|
8266
|
+
endpointKey(actions[drillStepIndex].capture.url) === restrictToDrillEndpointKey)) {
|
|
8217
8267
|
matchingDrillStepIndices.push(drillStepIndex);
|
|
8218
8268
|
}
|
|
8219
8269
|
}
|
|
@@ -8347,6 +8397,26 @@ function isDescendantArrayPath(candidate, base) {
|
|
|
8347
8397
|
* spec's own drill indices look independent, so the heuristic's wrong-level
|
|
8348
8398
|
* guess never survives alongside the corrected one.
|
|
8349
8399
|
*/
|
|
8400
|
+
/**
|
|
8401
|
+
* The stable identity of the logical drill-down a {@link FoldTarget}
|
|
8402
|
+
* resolves onto: the endpoint of its chain's TERMINAL call (the response
|
|
8403
|
+
* actually folded — see {@link FoldTarget.chainTerminalIndex}) paired with
|
|
8404
|
+
* the specific array read off that response (see
|
|
8405
|
+
* {@link FoldTarget.chainArrayPath}). Neither piece alone is stable enough to
|
|
8406
|
+
* compare two independently-resolved targets by: `drillStepIndex` (the
|
|
8407
|
+
* chain's ENTRY point) can legitimately differ between the structural
|
|
8408
|
+
* heuristic's own request-threading resolution and a spec's
|
|
8409
|
+
* response-only/upstream-hop resolution even when both terminate at the same
|
|
8410
|
+
* call (see {@link buildFoldPlanFromSpec}'s `entryIndex` docstring); and the
|
|
8411
|
+
* terminal endpoint alone can collide when one response happens to carry TWO
|
|
8412
|
+
* unrelated per-item arrays for two entirely different primaries (e.g. a
|
|
8413
|
+
* combined status response naming both `entries` and `contracts`). Pairing
|
|
8414
|
+
* the terminal's endpoint with the specific array it reads disambiguates
|
|
8415
|
+
* both cases at once.
|
|
8416
|
+
*/
|
|
8417
|
+
function foldTargetDrillIdentity(actions, target) {
|
|
8418
|
+
return `${endpointKey(actions[target.chainTerminalIndex].capture.url)}\u0000${target.chainArrayPath.join(".")}`;
|
|
8419
|
+
}
|
|
8350
8420
|
function mergeSpecPlanOntoSamePrimary(structuralPlans, actions, foldReturnSpec,
|
|
8351
8421
|
// See detectDrillDownFoldPlan's identical parameter. Threaded into this
|
|
8352
8422
|
// function's own buildFoldPlanFromSpec call so a spec plan resolved here
|
|
@@ -8355,9 +8425,55 @@ function mergeSpecPlanOntoSamePrimary(structuralPlans, actions, foldReturnSpec,
|
|
|
8355
8425
|
primaryIdentityAnchor = null) {
|
|
8356
8426
|
if (foldReturnSpec === null)
|
|
8357
8427
|
return [...structuralPlans];
|
|
8428
|
+
// Override joinFields on EVERY structural primary occurrence the spec can
|
|
8429
|
+
// independently resolve at its OWN primaryStepIndex — not just whichever
|
|
8430
|
+
// single occurrence the freshest-wins `specPlan` resolution below happens
|
|
8431
|
+
// to land on. A re-issued/paginated primary (same endpoint, multiple
|
|
8432
|
+
// captures) produces one independent structural plan per occurrence; each
|
|
8433
|
+
// must get the declared override, not just the freshest one.
|
|
8434
|
+
const structuralPlansWithPerOccurrenceOverrides = structuralPlans.map((plan) => {
|
|
8435
|
+
// Resolved once per structural TARGET (keyed on that target's own drill
|
|
8436
|
+
// endpoint), not once for the whole primary occurrence: buildFoldPlanFromSpec's
|
|
8437
|
+
// freshest-first scan `break`s after the first drill endpoint it resolves, so a
|
|
8438
|
+
// single unrestricted call can only ever produce an override for ONE of a
|
|
8439
|
+
// primary's structural targets even when it has several (e.g. a genuinely
|
|
8440
|
+
// per-item drill loop resolving one independent target per item, each at its
|
|
8441
|
+
// own endpoint). See buildFoldPlanFromSpec's restrictToDrillEndpointKey docstring.
|
|
8442
|
+
const mergedTargets = plan.targets.map((target) => {
|
|
8443
|
+
const ownSpecPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor, plan.primaryStepIndex, endpointKey(actions[target.drillStepIndex].capture.url));
|
|
8444
|
+
if (ownSpecPlan === null ||
|
|
8445
|
+
JSON.stringify(ownSpecPlan.primaryArrayPath) !== JSON.stringify(plan.primaryArrayPath)) {
|
|
8446
|
+
return target;
|
|
8447
|
+
}
|
|
8448
|
+
// ownSpecPlan was resolved with restrictToDrillEndpointKey pinned to
|
|
8449
|
+
// this target's OWN drill endpoint, so its single target (see
|
|
8450
|
+
// buildFoldPlanFromSpec's `targets: [{...}]` literal — it never
|
|
8451
|
+
// returns more than one) IS the spec's resolution of this restricted
|
|
8452
|
+
// endpoint by construction. Matching it back to `target` via
|
|
8453
|
+
// foldTargetDrillIdentity is not just redundant but actively wrong: a
|
|
8454
|
+
// multi-hop drill chain can make the spec's forward walk
|
|
8455
|
+
// (buildFoldPlanFromSpec's computeFoldChain resolution) land on a
|
|
8456
|
+
// different chainTerminalIndex/chainArrayPath than the structural
|
|
8457
|
+
// heuristic did for the exact same restricted endpoint, so the
|
|
8458
|
+
// identity lookup misses and the declared joinFields override is
|
|
8459
|
+
// silently dropped in favor of the heuristic's structural guess.
|
|
8460
|
+
const specTarget = ownSpecPlan.targets[0];
|
|
8461
|
+
return specTarget === undefined ? target : { ...target, joinFields: specTarget.joinFields };
|
|
8462
|
+
});
|
|
8463
|
+
// A spec-declared target the heuristic missed entirely (an independent
|
|
8464
|
+
// drill-down at a NEW endpoint) is still found via the original
|
|
8465
|
+
// unrestricted resolution, which is free to land on any endpoint.
|
|
8466
|
+
const ownSpecPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor, plan.primaryStepIndex);
|
|
8467
|
+
const existingDrillIdentities = new Set(plan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
|
|
8468
|
+
const newTargets = ownSpecPlan !== null &&
|
|
8469
|
+
JSON.stringify(ownSpecPlan.primaryArrayPath) === JSON.stringify(plan.primaryArrayPath)
|
|
8470
|
+
? ownSpecPlan.targets.filter((target) => !existingDrillIdentities.has(foldTargetDrillIdentity(actions, target)))
|
|
8471
|
+
: [];
|
|
8472
|
+
return { ...plan, targets: [...mergedTargets, ...newTargets] };
|
|
8473
|
+
});
|
|
8358
8474
|
const specPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor);
|
|
8359
8475
|
if (specPlan === null)
|
|
8360
|
-
return
|
|
8476
|
+
return structuralPlansWithPerOccurrenceOverrides;
|
|
8361
8477
|
const specPrimaryEndpointKey = endpointKey(actions[specPlan.primaryStepIndex].capture.url);
|
|
8362
8478
|
// Deliberately keyed on the RAW primaryStepIndex (not endpoint identity):
|
|
8363
8479
|
// this branch trusts the structural target's own chain/drillArrayPath and
|
|
@@ -8367,46 +8483,73 @@ primaryIdentityAnchor = null) {
|
|
|
8367
8483
|
// different index) may have matched a different item entirely, so it must
|
|
8368
8484
|
// fall through to the wholesale-replace (sharedDrillPlan) or union
|
|
8369
8485
|
// (consumedPrimarySteps) handling below instead of merging by identity.
|
|
8370
|
-
const samePrimaryPlan =
|
|
8486
|
+
const samePrimaryPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => plan.primaryStepIndex === specPlan.primaryStepIndex &&
|
|
8371
8487
|
JSON.stringify(plan.primaryArrayPath) === JSON.stringify(specPlan.primaryArrayPath));
|
|
8372
8488
|
if (samePrimaryPlan !== undefined) {
|
|
8373
|
-
// A
|
|
8374
|
-
// structurally-resolved chain/drillArrayPath (already proven
|
|
8375
|
-
// real per-item data), but its `joinFields` is overridden to
|
|
8376
|
-
// declared value — the heuristic's own joinFields only prove
|
|
8377
|
-
// threads INTO the drill's REQUEST, which says nothing about
|
|
8378
|
-
// that same field can be found on the drill's RESPONSE to match
|
|
8379
|
-
// onto a primary item (see emitFoldMatchAndMergeLines). A flow
|
|
8380
|
-
// declaring `joinFields` on a foldReturn is asserting exactly
|
|
8381
|
-
// is the field their drill-down's RESPONSE actually carries.
|
|
8382
|
-
|
|
8383
|
-
|
|
8489
|
+
// A structural target serving the SAME drill-down the spec declares
|
|
8490
|
+
// keeps its structurally-resolved chain/drillArrayPath (already proven
|
|
8491
|
+
// to reach real per-item data), but its `joinFields` is overridden to
|
|
8492
|
+
// the spec's declared value — the heuristic's own joinFields only prove
|
|
8493
|
+
// a field threads INTO the drill's REQUEST, which says nothing about
|
|
8494
|
+
// whether that same field can be found on the drill's RESPONSE to match
|
|
8495
|
+
// it back onto a primary item (see emitFoldMatchAndMergeLines). A flow
|
|
8496
|
+
// author declaring `joinFields` on a foldReturn is asserting exactly
|
|
8497
|
+
// that: this is the field their drill-down's RESPONSE actually carries.
|
|
8498
|
+
// Matched by the drill's ENDPOINT IDENTITY (the same identity
|
|
8499
|
+
// scanPrimaryCandidateGroups' own `alreadyTargetedSameEndpoint` check
|
|
8500
|
+
// uses), not raw drillStepIndex equality: the spec's own entryIndex
|
|
8501
|
+
// resolution (resolveSpecMatchedPrimaryItemIndexAlongChain) and the
|
|
8502
|
+
// heuristic's own request-threading scan can each land on a different,
|
|
8503
|
+
// but equally valid, occurrence of the SAME re-issued/paginated drill
|
|
8504
|
+
// endpoint — raw-index equality only happened to override when both
|
|
8505
|
+
// resolutions picked the identical occurrence, silently leaving every
|
|
8506
|
+
// other occurrence's joinFields un-overridden. Keyed on each target's
|
|
8507
|
+
// {@link foldTargetDrillIdentity} — the terminal endpoint paired with the
|
|
8508
|
+
// specific array it reads — rather than `drillStepIndex`: a spec's
|
|
8509
|
+
// declared join value often resolves only through an upstream entry hop
|
|
8510
|
+
// (e.g. a header-threaded token) that isn't the endpointPattern-matched
|
|
8511
|
+
// call itself, so its `drillStepIndex` (== that entry hop) can
|
|
8512
|
+
// legitimately differ from the structural heuristic's own
|
|
8513
|
+
// `drillStepIndex` (its own, possibly different, threading entry point)
|
|
8514
|
+
// even when both chains terminate at the exact same logical drill-down
|
|
8515
|
+
// call and array.
|
|
8516
|
+
const specTargetsByDrillIdentity = new Map(specPlan.targets.map((target) => [foldTargetDrillIdentity(actions, target), target]));
|
|
8517
|
+
return structuralPlansWithPerOccurrenceOverrides.map((plan) => {
|
|
8384
8518
|
if (plan !== samePrimaryPlan)
|
|
8385
8519
|
return plan;
|
|
8386
8520
|
const mergedTargets = plan.targets.map((target) => {
|
|
8387
|
-
const specTarget =
|
|
8521
|
+
const specTarget = specTargetsByDrillIdentity.get(foldTargetDrillIdentity(actions, target));
|
|
8388
8522
|
return specTarget === undefined ? target : { ...target, joinFields: specTarget.joinFields };
|
|
8389
8523
|
});
|
|
8390
|
-
const
|
|
8391
|
-
const newTargets = specPlan.targets.filter((target) => !
|
|
8524
|
+
const existingDrillIdentities = new Set(plan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
|
|
8525
|
+
const newTargets = specPlan.targets.filter((target) => !existingDrillIdentities.has(foldTargetDrillIdentity(actions, target)));
|
|
8392
8526
|
return { ...plan, targets: [...mergedTargets, ...newTargets] };
|
|
8393
8527
|
});
|
|
8394
8528
|
}
|
|
8395
8529
|
// No exact (primaryStepIndex, primaryArrayPath) match, but a structural
|
|
8396
8530
|
// plan on the SAME primaryStepIndex may still have independently detected
|
|
8397
|
-
// a target at the SAME
|
|
8398
|
-
// wrong array level (e.g. a shallower structural guess that happens to
|
|
8399
|
-
// share the spec's drill-down call).
|
|
8400
|
-
//
|
|
8401
|
-
//
|
|
8402
|
-
//
|
|
8403
|
-
//
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8531
|
+
// a target at the SAME drill ENDPOINT+array a spec target names — just at
|
|
8532
|
+
// the wrong array level (e.g. a shallower structural guess that happens to
|
|
8533
|
+
// share the spec's drill-down call). Matched by {@link
|
|
8534
|
+
// foldTargetDrillIdentity} (see the samePrimaryPlan branch above for why
|
|
8535
|
+
// the terminal+array, not the entry `drillStepIndex` alone, is the stable
|
|
8536
|
+
// identity to compare on) — not raw drillStepIndex equality:
|
|
8537
|
+
// buildFoldPlanFromSpec's own entryIndex resolution and the structural
|
|
8538
|
+
// heuristic's request-threading scan can each land on a different, but
|
|
8539
|
+
// equally valid, occurrence/hop of the SAME endpoint (or the same single
|
|
8540
|
+
// occurrence reached via a different chain entry point), and raw-index
|
|
8541
|
+
// equality only happens to hold when both resolutions pick the identical
|
|
8542
|
+
// index. That structural target's own primaryArrayPath/drillArrayPath/
|
|
8543
|
+
// joinFields describe the heuristic's guess, not the flow author's
|
|
8544
|
+
// declaration, so the spec target replaces it wholesale rather than being
|
|
8545
|
+
// discarded by the mismatched-array-path branch below or partially merged
|
|
8546
|
+
// as if the array level matched.
|
|
8547
|
+
const specDrillIdentities = new Set(specPlan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
|
|
8548
|
+
const sharedDrillPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
|
|
8549
|
+
plan.targets.some((target) => specDrillIdentities.has(foldTargetDrillIdentity(actions, target))));
|
|
8407
8550
|
if (sharedDrillPlan !== undefined) {
|
|
8408
|
-
const remainingTargets = sharedDrillPlan.targets.filter((target) => !
|
|
8409
|
-
const otherPlans =
|
|
8551
|
+
const remainingTargets = sharedDrillPlan.targets.filter((target) => !specDrillIdentities.has(foldTargetDrillIdentity(actions, target)));
|
|
8552
|
+
const otherPlans = structuralPlansWithPerOccurrenceOverrides.filter((plan) => plan !== sharedDrillPlan);
|
|
8410
8553
|
const keptStructuralPlans = remainingTargets.length === 0
|
|
8411
8554
|
? otherPlans
|
|
8412
8555
|
: [...otherPlans, { ...sharedDrillPlan, targets: remainingTargets }];
|
|
@@ -8421,10 +8564,13 @@ primaryIdentityAnchor = null) {
|
|
|
8421
8564
|
// the independence guard below would otherwise do, since a
|
|
8422
8565
|
// level-correcting spec's own drill indices can look entirely
|
|
8423
8566
|
// independent) or silently dropped by the mismatched-array-path checks.
|
|
8424
|
-
const supersededShallowerPlan =
|
|
8567
|
+
const supersededShallowerPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
|
|
8425
8568
|
isDescendantArrayPath(specPlan.primaryArrayPath, plan.primaryArrayPath));
|
|
8426
8569
|
if (supersededShallowerPlan !== undefined) {
|
|
8427
|
-
return [
|
|
8570
|
+
return [
|
|
8571
|
+
...structuralPlansWithPerOccurrenceOverrides.filter((plan) => plan !== supersededShallowerPlan),
|
|
8572
|
+
specPlan,
|
|
8573
|
+
];
|
|
8428
8574
|
}
|
|
8429
8575
|
// Keyed by the (primaryStepIndex, primaryArrayPath) pair, not
|
|
8430
8576
|
// primaryStepIndex alone — a structural plan only ever consumed ITS OWN
|
|
@@ -8441,7 +8587,7 @@ primaryIdentityAnchor = null) {
|
|
|
8441
8587
|
// contends for.
|
|
8442
8588
|
const consumedIndices = new Set();
|
|
8443
8589
|
const consumedPrimarySteps = new Set();
|
|
8444
|
-
for (const plan of
|
|
8590
|
+
for (const plan of structuralPlansWithPerOccurrenceOverrides) {
|
|
8445
8591
|
const planPrimaryEndpointKey = endpointKey(actions[plan.primaryStepIndex].capture.url);
|
|
8446
8592
|
consumedPrimarySteps.add(`${planPrimaryEndpointKey}:${JSON.stringify(plan.primaryArrayPath)}`);
|
|
8447
8593
|
for (const target of plan.targets) {
|
|
@@ -8452,19 +8598,27 @@ primaryIdentityAnchor = null) {
|
|
|
8452
8598
|
!consumedPrimarySteps.has(`${specPrimaryEndpointKey}:${JSON.stringify(specPlan.primaryArrayPath)}`) &&
|
|
8453
8599
|
specPlan.targets.every((target) => target.chain.every((chainIndex) => !consumedIndices.has(chainIndex)));
|
|
8454
8600
|
if (specConsumesOnlyItsOwnIndices)
|
|
8455
|
-
return [...
|
|
8601
|
+
return [...structuralPlansWithPerOccurrenceOverrides, specPlan];
|
|
8456
8602
|
// The spec's primary endpoint/array is already consumed by a structural
|
|
8457
8603
|
// plan on a DIFFERENT capture of the same re-issued primary operation
|
|
8458
8604
|
// (paginated primary), but its own target chains touch no index that plan
|
|
8459
8605
|
// already depends on — this is the same identity, not a raw-index
|
|
8460
8606
|
// coincidence, so union the spec's targets onto it rather than silently
|
|
8461
|
-
// dropping a declaration the flow author explicitly wrote.
|
|
8462
|
-
|
|
8607
|
+
// dropping a declaration the flow author explicitly wrote. Membership in
|
|
8608
|
+
// "already covered" is decided by {@link foldTargetDrillIdentity} (see the
|
|
8609
|
+
// samePrimaryPlan branch above for why the terminal+array, not the entry
|
|
8610
|
+
// `drillStepIndex` alone, is the stable identity) — not raw drillStepIndex
|
|
8611
|
+
// equality. A spec target sharing a structural target's drill identity is
|
|
8612
|
+
// a re-declaration of that SAME drill-down (handled as a wholesale replace
|
|
8613
|
+
// by sharedDrillPlan above), not an independent one to union — so this
|
|
8614
|
+
// only ever unions genuinely new endpoints onto an already-identified
|
|
8615
|
+
// primary.
|
|
8616
|
+
const sameIdentityPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
|
|
8463
8617
|
JSON.stringify(plan.primaryArrayPath) === JSON.stringify(specPlan.primaryArrayPath) &&
|
|
8464
|
-
specPlan.targets.every((target) => !plan.targets.some((t) => t
|
|
8618
|
+
specPlan.targets.every((target) => !plan.targets.some((t) => foldTargetDrillIdentity(actions, t) === foldTargetDrillIdentity(actions, target))));
|
|
8465
8619
|
if (sameIdentityPlan === undefined)
|
|
8466
|
-
return [...
|
|
8467
|
-
return
|
|
8620
|
+
return [...structuralPlansWithPerOccurrenceOverrides];
|
|
8621
|
+
return structuralPlansWithPerOccurrenceOverrides.map((plan) => plan === sameIdentityPlan ? { ...plan, targets: [...plan.targets, ...specPlan.targets] } : plan);
|
|
8468
8622
|
}
|
|
8469
8623
|
/**
|
|
8470
8624
|
* The single fold-plan entry point: {@link detectDrillDownFoldPlan}'s
|
|
@@ -9836,7 +9990,19 @@ const httpClient = createHttpClient({ schema: ${pascal}ResponseSchema, bottlenec
|
|
|
9836
9990
|
chainStep.capture.responseBody,
|
|
9837
9991
|
]),
|
|
9838
9992
|
});
|
|
9839
|
-
|
|
9993
|
+
// This emitter never renders a per-hop `produces` accessor (unlike
|
|
9994
|
+
// emitMultiStepExecuteHttp's identical chain loop) — only the
|
|
9995
|
+
// chain terminal's response is ever read, via
|
|
9996
|
+
// `terminalStep.varName` inside emitFoldMatchAndMergeLines below.
|
|
9997
|
+
// A non-terminal hop chained purely to unlock a later request
|
|
9998
|
+
// (e.g. it sets server-side state via a threaded header) has no
|
|
9999
|
+
// reader for its own response, so binding it to a local declares a
|
|
10000
|
+
// variable nothing references (Biome noUnusedVariables).
|
|
10001
|
+
const bindsChainResponse = chainIndex === target.chainTerminalIndex;
|
|
10002
|
+
chainLines.push(bindsChainResponse
|
|
10003
|
+
? ` const ${chainStep.varName} = (await httpClient(\`${url}\`, {`
|
|
10004
|
+
: ` await httpClient(\`${url}\`, {`, ` method: ${JSON.stringify(chainStep.capture.method)},`, ` schema: ${schemaExpr},`);
|
|
10005
|
+
chainLines.push(bindsChainResponse ? ` })) as Record<string, unknown>;` : ` });`);
|
|
9840
10006
|
}
|
|
9841
10007
|
const terminalStep = actionSteps[target.chainTerminalIndex];
|
|
9842
10008
|
if (!terminalStep)
|