@enricai/barnacle 1.12.19 → 1.12.21
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/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/lib/merge-folded-primary-bodies.d.ts +10 -0
- package/dist/lib/merge-folded-primary-bodies.d.ts.map +1 -0
- package/dist/lib/merge-folded-primary-bodies.js +29 -0
- package/dist/lib/merge-folded-primary-bodies.js.map +1 -0
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +4 -1
- package/dist/plugins/loader.js.map +1 -1
- package/dist/scraper/flow-runner.d.ts.map +1 -1
- package/dist/scraper/flow-runner.js +11 -13
- package/dist/scraper/flow-runner.js.map +1 -1
- package/dist/scripts/recon-browser.d.ts +7 -0
- package/dist/scripts/recon-browser.d.ts.map +1 -1
- package/dist/scripts/recon-browser.js +33 -0
- package/dist/scripts/recon-browser.js.map +1 -1
- package/dist/scripts/recon-generate-multicall-fixture.d.ts +634 -0
- package/dist/scripts/recon-generate-multicall-fixture.d.ts.map +1 -1
- package/dist/scripts/recon-generate-multicall-fixture.js +1793 -3
- package/dist/scripts/recon-generate-multicall-fixture.js.map +1 -1
- package/dist/scripts/recon-generate.d.ts +195 -9
- package/dist/scripts/recon-generate.d.ts.map +1 -1
- package/dist/scripts/recon-generate.js +1820 -61
- package/dist/scripts/recon-generate.js.map +1 -1
- package/dist/site-plugin.d.ts +4 -3
- package/dist/site-plugin.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -4,17 +4,63 @@ exports.buildCapture = buildCapture;
|
|
|
4
4
|
exports.buildStep = buildStep;
|
|
5
5
|
exports.buildMulticallHeterogeneousActionSteps = buildMulticallHeterogeneousActionSteps;
|
|
6
6
|
exports.buildMulticallHeterogeneousActionStepsWithDrillDown = buildMulticallHeterogeneousActionStepsWithDrillDown;
|
|
7
|
+
exports.buildMulticallDependentDrillDownActionSteps = buildMulticallDependentDrillDownActionSteps;
|
|
7
8
|
exports.buildWizardCheckoutCaptures = buildWizardCheckoutCaptures;
|
|
9
|
+
exports.buildMulticallSingleShotSearchDrillDownActionSteps = buildMulticallSingleShotSearchDrillDownActionSteps;
|
|
10
|
+
exports.buildMulticallSingleShotSearchDrillDownDrillDecoyActionSteps = buildMulticallSingleShotSearchDrillDownDrillDecoyActionSteps;
|
|
11
|
+
exports.buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps = buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps;
|
|
12
|
+
exports.buildMulticallSingleShotSearchDrillDownNestedJoinFieldMultiItemActionSteps = buildMulticallSingleShotSearchDrillDownNestedJoinFieldMultiItemActionSteps;
|
|
13
|
+
exports.buildMulticallSingleShotSearchDrillDownRicherFlatOutranksNestedArrayActionSteps = buildMulticallSingleShotSearchDrillDownRicherFlatOutranksNestedArrayActionSteps;
|
|
14
|
+
exports.buildMulticallSingleShotSearchTwoIndependentDrillDownsActionSteps = buildMulticallSingleShotSearchTwoIndependentDrillDownsActionSteps;
|
|
15
|
+
exports.buildMulticallSingleShotSearchTwoIndependentDrillDownsSharedJoinFieldActionSteps = buildMulticallSingleShotSearchTwoIndependentDrillDownsSharedJoinFieldActionSteps;
|
|
16
|
+
exports.buildMulticallTwoIndependentPrimariesActionSteps = buildMulticallTwoIndependentPrimariesActionSteps;
|
|
17
|
+
exports.buildMulticallTwoIndependentPrimariesSecondHeaderThreadedActionSteps = buildMulticallTwoIndependentPrimariesSecondHeaderThreadedActionSteps;
|
|
18
|
+
exports.buildMulticallSingleShotSearchHeuristicAndSpecTwoTargetActionSteps = buildMulticallSingleShotSearchHeuristicAndSpecTwoTargetActionSteps;
|
|
19
|
+
exports.buildMulticallTwoOccurrencesSamePrimaryDistinctDrillsActionSteps = buildMulticallTwoOccurrencesSamePrimaryDistinctDrillsActionSteps;
|
|
20
|
+
exports.buildMulticallNestedGroupedDrillDownMultiGroupActionSteps = buildMulticallNestedGroupedDrillDownMultiGroupActionSteps;
|
|
21
|
+
exports.buildMulticallSingleShotSearchDrillDownNumericJoinActionSteps = buildMulticallSingleShotSearchDrillDownNumericJoinActionSteps;
|
|
22
|
+
exports.buildMulticallSingleShotSearchDrillDownPathThreadedJoinActionSteps = buildMulticallSingleShotSearchDrillDownPathThreadedJoinActionSteps;
|
|
23
|
+
exports.buildMulticallSingleShotSearchDrillDownNestedJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownNestedJoinFieldActionSteps;
|
|
24
|
+
exports.buildMulticallSingleShotSearchDrillDownCompositeNumericJoinActionSteps = buildMulticallSingleShotSearchDrillDownCompositeNumericJoinActionSteps;
|
|
25
|
+
exports.buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps = buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps;
|
|
26
|
+
exports.buildMulticallSingleShotSearchDrillDownCompositeNumericJoinNonFirstItemActionSteps = buildMulticallSingleShotSearchDrillDownCompositeNumericJoinNonFirstItemActionSteps;
|
|
27
|
+
exports.buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps = buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps;
|
|
28
|
+
exports.buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinRequeriedPrimaryOverlapActionSteps = buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinRequeriedPrimaryOverlapActionSteps;
|
|
29
|
+
exports.buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinMultiItemActionSteps = buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinMultiItemActionSteps;
|
|
30
|
+
exports.buildMulticallSingleShotSearchDrillDownOutOfOrderItemActionSteps = buildMulticallSingleShotSearchDrillDownOutOfOrderItemActionSteps;
|
|
31
|
+
exports.buildMulticallSingleShotSearchDrillDownMultiMatchActionSteps = buildMulticallSingleShotSearchDrillDownMultiMatchActionSteps;
|
|
32
|
+
exports.buildMulticallSingleShotSearchDrillDownTypeMismatchJoinActionSteps = buildMulticallSingleShotSearchDrillDownTypeMismatchJoinActionSteps;
|
|
33
|
+
exports.buildMulticallSingleShotSearchDrillDownChainedDependentMultipartChainStepActionSteps = buildMulticallSingleShotSearchDrillDownChainedDependentMultipartChainStepActionSteps;
|
|
34
|
+
exports.buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps;
|
|
35
|
+
exports.buildMulticallSingleShotSearchDrillDownHeaderMintedChainedResponseValueActionSteps = buildMulticallSingleShotSearchDrillDownHeaderMintedChainedResponseValueActionSteps;
|
|
36
|
+
exports.buildMulticallSingleShotSearchDrillDownBooleanChainedResponseValueActionSteps = buildMulticallSingleShotSearchDrillDownBooleanChainedResponseValueActionSteps;
|
|
37
|
+
exports.buildMulticallSingleShotSearchDrillDownNestedJoinFieldChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownNestedJoinFieldChainedDependentActionSteps;
|
|
38
|
+
exports.buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps = buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps;
|
|
39
|
+
exports.buildMulticallSingleShotSearchTwoIndependentArraysActionSteps = buildMulticallSingleShotSearchTwoIndependentArraysActionSteps;
|
|
40
|
+
exports.buildMulticallStructuralPlusSpecOnlySameStepActionSteps = buildMulticallStructuralPlusSpecOnlySameStepActionSteps;
|
|
41
|
+
exports.buildMulticallSingleShotSearchDrillDownChainedDecoyOnChainTerminalActionSteps = buildMulticallSingleShotSearchDrillDownChainedDecoyOnChainTerminalActionSteps;
|
|
42
|
+
exports.buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps;
|
|
43
|
+
exports.buildMulticallSingleShotSearchDrillDownRichnessTiedConfirmationHopChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownRichnessTiedConfirmationHopChainedDependentActionSteps;
|
|
44
|
+
exports.buildMulticallSingleShotSearchDrillDownArrayWrappedChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedChainedDependentActionSteps;
|
|
45
|
+
exports.buildMulticallSingleShotSearchDrillDownArrayWrappedNumericChainedJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedNumericChainedJoinFieldActionSteps;
|
|
46
|
+
exports.buildMulticallSingleShotSearchDrillDownShortNumericChainedJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownShortNumericChainedJoinFieldActionSteps;
|
|
47
|
+
exports.buildMulticallSingleShotSearchDrillDownShortCookieChainedJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownShortCookieChainedJoinFieldActionSteps;
|
|
48
|
+
exports.buildMulticallSingleShotSearchDrillDownArrayWrappedImmediateJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedImmediateJoinFieldActionSteps;
|
|
49
|
+
exports.buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps;
|
|
50
|
+
exports.buildMulticallSingleShotSearchDrillDownArrayWrappedBooleanImmediateJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedBooleanImmediateJoinFieldActionSteps;
|
|
51
|
+
exports.buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinChainedDependentActionSteps;
|
|
52
|
+
exports.buildMulticallSingleShotSearchDrillDownGetEntryHopChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownGetEntryHopChainedDependentActionSteps;
|
|
53
|
+
exports.buildMulticallSingleShotSearchDrillDownResponseHeaderThreadedJoinChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownResponseHeaderThreadedJoinChainedDependentActionSteps;
|
|
8
54
|
function buildCapture(overrides) {
|
|
9
55
|
return {
|
|
10
56
|
timestamp: overrides.timestamp,
|
|
11
57
|
phase: "action",
|
|
12
|
-
method: "POST",
|
|
58
|
+
method: overrides.method ?? "POST",
|
|
13
59
|
url: overrides.url,
|
|
14
60
|
status: 200,
|
|
15
|
-
requestHeaders: { "Content-Type": "application/json" },
|
|
61
|
+
requestHeaders: overrides.requestHeaders ?? { "Content-Type": "application/json" },
|
|
16
62
|
requestPostData: overrides.requestPostData,
|
|
17
|
-
responseHeaders: { "content-type": "application/json" },
|
|
63
|
+
responseHeaders: overrides.responseHeaders ?? { "content-type": "application/json" },
|
|
18
64
|
responseBody: overrides.responseBody,
|
|
19
65
|
operationName: null,
|
|
20
66
|
query: null,
|
|
@@ -102,6 +148,53 @@ function buildMulticallHeterogeneousActionStepsWithDrillDown() {
|
|
|
102
148
|
}),
|
|
103
149
|
];
|
|
104
150
|
}
|
|
151
|
+
const CATALOG_SEARCH_URL = "https://api.example.com/catalog/search/";
|
|
152
|
+
const CATALOG_ITEM_DETAIL_URL = "https://api.example.com/catalog/item-detail/";
|
|
153
|
+
/**
|
|
154
|
+
* Reproduces a search-then-per-item-drill-down flow whose primary page
|
|
155
|
+
* carries multiple results, each followed by its own drill-down call — the
|
|
156
|
+
* shape {@link detectDrillDownFoldPlan} must resolve by join key rather than
|
|
157
|
+
* by position. `search`/`page 2` is re-queried (distinct body from `page 1`)
|
|
158
|
+
* so it satisfies {@link findRequeriedActions}'s relevance signal, and its
|
|
159
|
+
* response holds two items (`i-b` then `i-a`, deliberately not alphabetical)
|
|
160
|
+
* so a later merge can't assume primary-array order. The two drill-down
|
|
161
|
+
* calls are fired in the OPPOSITE order of the primary array (`i-a`'s
|
|
162
|
+
* drill-down first, `i-b`'s second) — a positional/index-based fold would
|
|
163
|
+
* pair the wrong item with the wrong drill-down response, while a join-key
|
|
164
|
+
* fold (matching each drill request's `itemId` back to the primary item that
|
|
165
|
+
* produced it) pairs them correctly regardless of call order.
|
|
166
|
+
*/
|
|
167
|
+
function buildMulticallDependentDrillDownActionSteps() {
|
|
168
|
+
return [
|
|
169
|
+
buildStep("r0", {
|
|
170
|
+
url: CATALOG_SEARCH_URL,
|
|
171
|
+
requestPostData: '{"page":1}',
|
|
172
|
+
responseBody: { totalPages: 2, items: [{ itemId: "solo" }] },
|
|
173
|
+
timestamp: "2024-03-01T00:00:00Z",
|
|
174
|
+
}),
|
|
175
|
+
buildStep("r1", {
|
|
176
|
+
url: CATALOG_SEARCH_URL,
|
|
177
|
+
requestPostData: '{"page":2}',
|
|
178
|
+
responseBody: {
|
|
179
|
+
totalPages: 2,
|
|
180
|
+
items: [{ itemId: "i-b" }, { itemId: "i-a" }],
|
|
181
|
+
},
|
|
182
|
+
timestamp: "2024-03-01T00:00:01Z",
|
|
183
|
+
}),
|
|
184
|
+
buildStep("r2", {
|
|
185
|
+
url: CATALOG_ITEM_DETAIL_URL,
|
|
186
|
+
requestPostData: '{"itemId":"i-a"}',
|
|
187
|
+
responseBody: { details: [{ detailId: "d-a" }] },
|
|
188
|
+
timestamp: "2024-03-01T00:00:02Z",
|
|
189
|
+
}),
|
|
190
|
+
buildStep("r3", {
|
|
191
|
+
url: CATALOG_ITEM_DETAIL_URL,
|
|
192
|
+
requestPostData: '{"itemId":"i-b"}',
|
|
193
|
+
responseBody: { details: [{ detailId: "d-b" }] },
|
|
194
|
+
timestamp: "2024-03-01T00:00:03Z",
|
|
195
|
+
}),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
105
198
|
const CHECKOUT_HOST = "https://api.example.com";
|
|
106
199
|
/**
|
|
107
200
|
* Reproduces a wizard-style checkout submission on one host: a create-order
|
|
@@ -146,4 +239,1701 @@ function buildWizardCheckoutCaptures() {
|
|
|
146
239
|
}),
|
|
147
240
|
];
|
|
148
241
|
}
|
|
242
|
+
const CATALOG_PRICING_URL = "https://api.example.com/catalog/pricing/";
|
|
243
|
+
/**
|
|
244
|
+
* A search → per-item drill-down flow whose search endpoint fires exactly
|
|
245
|
+
* ONCE. {@link detectDrillDownFoldPlan} now scans every prior action as a
|
|
246
|
+
* fold-primary candidate, so a single-shot search is just as eligible as a
|
|
247
|
+
* re-queried one — but the primary response here also carries a decoy
|
|
248
|
+
* `facets[]` array ahead of the real `results[]`, so `findObjectArrayField`'s
|
|
249
|
+
* DFS first-match deliberately disagrees with the array a caller would fold
|
|
250
|
+
* onto. `resolveFoldPlan(steps)` on this fixture still returns `null`, but
|
|
251
|
+
* only because of that decoy, not because the search fires once. That
|
|
252
|
+
* decoy-vs-declared-path gap is the shape the flow-declared `foldReturn`
|
|
253
|
+
* (`FoldReturnSpec`/`resolveFoldPlan`) exists to reach.
|
|
254
|
+
*
|
|
255
|
+
* The decoy is load-bearing for a `resolveFoldPlan`/`foldReturn` test
|
|
256
|
+
* elsewhere; {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps}
|
|
257
|
+
* is the decoy-free sibling used to prove `detectDrillDownFoldPlan` alone
|
|
258
|
+
* detects a single-shot primary.
|
|
259
|
+
*/
|
|
260
|
+
function buildMulticallSingleShotSearchDrillDownActionSteps() {
|
|
261
|
+
return [
|
|
262
|
+
buildStep("r0", {
|
|
263
|
+
url: CATALOG_SEARCH_URL,
|
|
264
|
+
requestPostData: '{"page":1}',
|
|
265
|
+
responseBody: {
|
|
266
|
+
facets: [{ name: "brand" }],
|
|
267
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
268
|
+
},
|
|
269
|
+
timestamp: "2024-04-01T00:00:00Z",
|
|
270
|
+
}),
|
|
271
|
+
buildStep("r1", {
|
|
272
|
+
url: CATALOG_PRICING_URL,
|
|
273
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
274
|
+
responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
|
|
275
|
+
timestamp: "2024-04-01T00:00:01Z",
|
|
276
|
+
}),
|
|
277
|
+
];
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* A search → per-item drill-down flow whose search response is unambiguous
|
|
281
|
+
* (a single `results[]` array, no decoy), but whose DRILL step's response
|
|
282
|
+
* carries a decoy `errors[]` array ahead of the real per-item `details[]`
|
|
283
|
+
* array. `findObjectArrayField`'s DFS first-match lands on `errors[]`
|
|
284
|
+
* instead of `details[]` when resolving the drill side of the fold, so a
|
|
285
|
+
* `foldReturn` declaration that only names the primary's `resultsPath`
|
|
286
|
+
* (see {@link FoldReturnSpec}) cannot fix this — the gap is on the drill
|
|
287
|
+
* side, not the primary side. Proves a drill-side results-path declaration
|
|
288
|
+
* (as opposed to {@link buildMulticallSingleShotSearchDrillDownActionSteps}'s
|
|
289
|
+
* primary-side decoy) is what a `drillResultsPath`-style flow declaration
|
|
290
|
+
* exists to reach.
|
|
291
|
+
*/
|
|
292
|
+
function buildMulticallSingleShotSearchDrillDownDrillDecoyActionSteps() {
|
|
293
|
+
return [
|
|
294
|
+
buildStep("r0", {
|
|
295
|
+
url: CATALOG_SEARCH_URL,
|
|
296
|
+
requestPostData: '{"page":1}',
|
|
297
|
+
responseBody: {
|
|
298
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
299
|
+
},
|
|
300
|
+
timestamp: "2024-04-01T00:00:00Z",
|
|
301
|
+
}),
|
|
302
|
+
buildStep("r1", {
|
|
303
|
+
url: CATALOG_PRICING_URL,
|
|
304
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
305
|
+
responseBody: {
|
|
306
|
+
errors: [{ code: "none" }],
|
|
307
|
+
details: [{ sku: "sku-a", price: 19.99 }],
|
|
308
|
+
},
|
|
309
|
+
timestamp: "2024-04-01T00:00:01Z",
|
|
310
|
+
}),
|
|
311
|
+
];
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* A decoy-free sibling of {@link buildMulticallSingleShotSearchDrillDownActionSteps}:
|
|
315
|
+
* the search endpoint still fires exactly once, but `results[]` is the sole
|
|
316
|
+
* object-array field in its response, so `findObjectArrayField`'s DFS
|
|
317
|
+
* first-match lands on the real results array with no declared `foldReturn`
|
|
318
|
+
* needed. Proves the single-shot primary is detected on structure alone.
|
|
319
|
+
*/
|
|
320
|
+
function buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps() {
|
|
321
|
+
return [
|
|
322
|
+
buildStep("r0", {
|
|
323
|
+
url: CATALOG_SEARCH_URL,
|
|
324
|
+
requestPostData: '{"page":1}',
|
|
325
|
+
responseBody: {
|
|
326
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
327
|
+
},
|
|
328
|
+
timestamp: "2024-04-01T00:00:00Z",
|
|
329
|
+
}),
|
|
330
|
+
buildStep("r1", {
|
|
331
|
+
url: CATALOG_PRICING_URL,
|
|
332
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
333
|
+
responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
|
|
334
|
+
timestamp: "2024-04-01T00:00:01Z",
|
|
335
|
+
}),
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* A nested-join-key sibling of {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps}
|
|
340
|
+
* with a SECOND primary item, so a per-item fold loop is actually exercised
|
|
341
|
+
* (not just a single-item detection check): each primary `results[]` item
|
|
342
|
+
* carries its join key under a nested `identifiers` object
|
|
343
|
+
* (`{ identifiers: { sku } }`) instead of as a top-level field, while the
|
|
344
|
+
* drill-down request is still keyed by the plain `sku` value pulled out of
|
|
345
|
+
* that nested field. Proves the fold loop threads a join key found by
|
|
346
|
+
* walking INTO an item's nested objects, not only its own top-level
|
|
347
|
+
* `Object.entries`, across every primary item.
|
|
348
|
+
*/
|
|
349
|
+
function buildMulticallSingleShotSearchDrillDownNestedJoinFieldMultiItemActionSteps() {
|
|
350
|
+
return [
|
|
351
|
+
buildStep("r0", {
|
|
352
|
+
url: CATALOG_SEARCH_URL,
|
|
353
|
+
requestPostData: '{"page":1}',
|
|
354
|
+
responseBody: {
|
|
355
|
+
results: [{ identifiers: { sku: "sku-a" } }, { identifiers: { sku: "sku-b" } }],
|
|
356
|
+
},
|
|
357
|
+
timestamp: "2024-04-01T00:00:00Z",
|
|
358
|
+
}),
|
|
359
|
+
buildStep("r1", {
|
|
360
|
+
url: CATALOG_PRICING_URL,
|
|
361
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
362
|
+
responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
|
|
363
|
+
timestamp: "2024-04-01T00:00:01Z",
|
|
364
|
+
}),
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* A search → per-item drill-down flow whose DRILL step's response carries
|
|
369
|
+
* BOTH a small real nested object-array field (`tags[]`, one primitive
|
|
370
|
+
* field per item, no join echo) AND richer flat top-level per-item fields
|
|
371
|
+
* (`name`/`price`/`brand`, none of them echoing the join either) — the
|
|
372
|
+
* shape {@link findAllObjectArrayFieldsOrWholeObject}'s old docstring
|
|
373
|
+
* ("a response that already has one [a real object-array field] is never
|
|
374
|
+
* second-guessed") resolved to the tiny `tags[]` array instead of the
|
|
375
|
+
* richer flat object. Neither candidate threads the join value in its
|
|
376
|
+
* response body (only the drill REQUEST carries `sku-a`), so selection must
|
|
377
|
+
* fall through to comparing per-item primitive-field richness, which the
|
|
378
|
+
* flat object wins 3 (`name`/`price`/`brand`) to 1 (`tags[0].id`).
|
|
379
|
+
*/
|
|
380
|
+
function buildMulticallSingleShotSearchDrillDownRicherFlatOutranksNestedArrayActionSteps() {
|
|
381
|
+
return [
|
|
382
|
+
buildStep("r0", {
|
|
383
|
+
url: CATALOG_SEARCH_URL,
|
|
384
|
+
requestPostData: '{"page":1}',
|
|
385
|
+
responseBody: {
|
|
386
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
387
|
+
},
|
|
388
|
+
timestamp: "2024-04-01T00:00:00Z",
|
|
389
|
+
}),
|
|
390
|
+
buildStep("r1", {
|
|
391
|
+
url: CATALOG_PRICING_URL,
|
|
392
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
393
|
+
responseBody: {
|
|
394
|
+
tags: [{ id: 1 }, { id: 2 }],
|
|
395
|
+
name: "Widget A",
|
|
396
|
+
price: 19.99,
|
|
397
|
+
brand: "Acme",
|
|
398
|
+
},
|
|
399
|
+
timestamp: "2024-04-01T00:00:01Z",
|
|
400
|
+
}),
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
const CATALOG_INVENTORY_URL = "https://api.example.com/catalog/inventory/";
|
|
404
|
+
/**
|
|
405
|
+
* A decoy-free sibling of {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps}
|
|
406
|
+
* with a SECOND, independently-threaded per-item drill-down appended: the
|
|
407
|
+
* primary `results[]` items carry both a `sku` (joined to the pricing drill)
|
|
408
|
+
* and an `itemId` (joined to this inventory drill), and neither drill
|
|
409
|
+
* step's response overlaps the other's fields. Proves the fold loop merges
|
|
410
|
+
* fields from every independent target onto each item, not just the first.
|
|
411
|
+
*/
|
|
412
|
+
function buildMulticallSingleShotSearchTwoIndependentDrillDownsActionSteps() {
|
|
413
|
+
return [
|
|
414
|
+
buildStep("r0", {
|
|
415
|
+
url: CATALOG_SEARCH_URL,
|
|
416
|
+
requestPostData: '{"page":1}',
|
|
417
|
+
responseBody: {
|
|
418
|
+
results: [
|
|
419
|
+
{ sku: "sku-a", itemId: "item-a" },
|
|
420
|
+
{ sku: "sku-b", itemId: "item-b" },
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
timestamp: "2024-04-01T00:00:00Z",
|
|
424
|
+
}),
|
|
425
|
+
buildStep("r1", {
|
|
426
|
+
url: CATALOG_PRICING_URL,
|
|
427
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
428
|
+
responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
|
|
429
|
+
timestamp: "2024-04-01T00:00:01Z",
|
|
430
|
+
}),
|
|
431
|
+
buildStep("r2", {
|
|
432
|
+
url: CATALOG_INVENTORY_URL,
|
|
433
|
+
requestPostData: '{"itemId":"item-a"}',
|
|
434
|
+
responseBody: { stock: [{ itemId: "item-a", qty: 7 }] },
|
|
435
|
+
timestamp: "2024-04-01T00:00:02Z",
|
|
436
|
+
}),
|
|
437
|
+
];
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* A sibling of {@link buildMulticallSingleShotSearchTwoIndependentDrillDownsActionSteps}
|
|
441
|
+
* where BOTH per-item drills are threaded off the SAME primary join field
|
|
442
|
+
* (`sku`), rather than disjoint fields (`sku`/`itemId`). Each drill's
|
|
443
|
+
* response echoes that shared `sku` value back alongside its own field —
|
|
444
|
+
* an echo of the primary's own join value, not evidence the second drill
|
|
445
|
+
* depends on the first drill's response. Proves the fold loop still emits
|
|
446
|
+
* and merges BOTH independent drills onto every primary item even when
|
|
447
|
+
* their echoed join values collide, rather than collapsing the second
|
|
448
|
+
* drill into a chain under the first.
|
|
449
|
+
*/
|
|
450
|
+
function buildMulticallSingleShotSearchTwoIndependentDrillDownsSharedJoinFieldActionSteps() {
|
|
451
|
+
return [
|
|
452
|
+
buildStep("r0", {
|
|
453
|
+
url: CATALOG_SEARCH_URL,
|
|
454
|
+
requestPostData: '{"page":1}',
|
|
455
|
+
responseBody: {
|
|
456
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
457
|
+
},
|
|
458
|
+
timestamp: "2024-04-01T00:00:00Z",
|
|
459
|
+
}),
|
|
460
|
+
buildStep("r1", {
|
|
461
|
+
url: CATALOG_PRICING_URL,
|
|
462
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
463
|
+
responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
|
|
464
|
+
timestamp: "2024-04-01T00:00:01Z",
|
|
465
|
+
}),
|
|
466
|
+
buildStep("r2", {
|
|
467
|
+
url: CATALOG_INVENTORY_URL,
|
|
468
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
469
|
+
responseBody: { stock: [{ sku: "sku-a", qty: 7 }] },
|
|
470
|
+
timestamp: "2024-04-01T00:00:02Z",
|
|
471
|
+
}),
|
|
472
|
+
];
|
|
473
|
+
}
|
|
474
|
+
const CATALOG_VENDORS_SEARCH_URL = "https://api.example.com/catalog/vendors/search";
|
|
475
|
+
const CATALOG_VENDOR_CONTRACTS_URL = "https://api.example.com/catalog/vendors/contracts/";
|
|
476
|
+
/**
|
|
477
|
+
* TWO independent primary/drill-down pairs, fully disjoint in both endpoint
|
|
478
|
+
* and step range: a products search folded by a per-product reviews drill
|
|
479
|
+
* (r0/r1), and an UNRELATED vendors search folded by a per-vendor contracts
|
|
480
|
+
* drill (r2/r3). Neither primary's items reference the other pair's join key
|
|
481
|
+
* (`productId` vs `vendorId`), so this proves the fold emitter resolves and
|
|
482
|
+
* loops over every independent plan, not only the first one it finds.
|
|
483
|
+
*/
|
|
484
|
+
function buildMulticallTwoIndependentPrimariesActionSteps() {
|
|
485
|
+
return [
|
|
486
|
+
buildStep("r0", {
|
|
487
|
+
url: CATALOG_SEARCH_URL,
|
|
488
|
+
requestPostData: '{"page":1}',
|
|
489
|
+
responseBody: {
|
|
490
|
+
products: [{ productId: "p1" }, { productId: "p2" }],
|
|
491
|
+
},
|
|
492
|
+
timestamp: "2024-05-01T00:00:00Z",
|
|
493
|
+
}),
|
|
494
|
+
buildStep("r1", {
|
|
495
|
+
url: CATALOG_PRICING_URL,
|
|
496
|
+
requestPostData: '{"productId":"p1"}',
|
|
497
|
+
responseBody: { reviews: [{ productId: "p1", rating: 5 }] },
|
|
498
|
+
timestamp: "2024-05-01T00:00:01Z",
|
|
499
|
+
}),
|
|
500
|
+
buildStep("r2", {
|
|
501
|
+
url: CATALOG_VENDORS_SEARCH_URL,
|
|
502
|
+
requestPostData: '{"page":1}',
|
|
503
|
+
responseBody: {
|
|
504
|
+
vendors: [{ vendorId: "v1" }, { vendorId: "v2" }],
|
|
505
|
+
},
|
|
506
|
+
timestamp: "2024-05-01T00:00:02Z",
|
|
507
|
+
}),
|
|
508
|
+
buildStep("r3", {
|
|
509
|
+
url: CATALOG_VENDOR_CONTRACTS_URL,
|
|
510
|
+
requestPostData: '{"vendorId":"v1"}',
|
|
511
|
+
responseBody: { contracts: [{ vendorId: "v1", contractId: "c1" }] },
|
|
512
|
+
timestamp: "2024-05-01T00:00:03Z",
|
|
513
|
+
}),
|
|
514
|
+
];
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Same disjoint two-pair shape as
|
|
518
|
+
* {@link buildMulticallTwoIndependentPrimariesActionSteps}, except the SECOND
|
|
519
|
+
* pair's join value threads only through a request HEADER
|
|
520
|
+
* (`collectRequestStringValues` never scans headers), so
|
|
521
|
+
* `detectDrillDownFoldPlan`'s structural heuristic resolves only the FIRST
|
|
522
|
+
* pair (r0/r1) on its own; the second pair (r2/r3) is only reachable via a
|
|
523
|
+
* flow-declared `foldReturn` spec naming `vendors` as its `resultsPath`.
|
|
524
|
+
* Since the second pair's primary/drill steps (2, 3) are entirely outside the
|
|
525
|
+
* first pair's own consumed indices ({@link buildMulticallTwoIndependentPrimariesActionSteps}'s
|
|
526
|
+
* r0/r1), a correctly-behaving `resolveFoldPlan` must APPEND the spec's plan
|
|
527
|
+
* as a second, independent entry rather than discarding it just because its
|
|
528
|
+
* primary differs from the one the heuristic already resolved.
|
|
529
|
+
*/
|
|
530
|
+
function buildMulticallTwoIndependentPrimariesSecondHeaderThreadedActionSteps() {
|
|
531
|
+
return [
|
|
532
|
+
buildStep("r0", {
|
|
533
|
+
url: CATALOG_SEARCH_URL,
|
|
534
|
+
requestPostData: '{"page":1}',
|
|
535
|
+
responseBody: {
|
|
536
|
+
products: [{ productId: "p1" }, { productId: "p2" }],
|
|
537
|
+
},
|
|
538
|
+
timestamp: "2024-05-01T00:00:00Z",
|
|
539
|
+
}),
|
|
540
|
+
buildStep("r1", {
|
|
541
|
+
url: CATALOG_PRICING_URL,
|
|
542
|
+
requestPostData: '{"productId":"p1"}',
|
|
543
|
+
responseBody: { reviews: [{ productId: "p1", rating: 5 }] },
|
|
544
|
+
timestamp: "2024-05-01T00:00:01Z",
|
|
545
|
+
}),
|
|
546
|
+
buildStep("r2", {
|
|
547
|
+
url: CATALOG_VENDORS_SEARCH_URL,
|
|
548
|
+
requestPostData: '{"page":1}',
|
|
549
|
+
responseBody: {
|
|
550
|
+
vendors: [{ vendorId: "v1" }, { vendorId: "v2" }],
|
|
551
|
+
},
|
|
552
|
+
timestamp: "2024-05-01T00:00:02Z",
|
|
553
|
+
}),
|
|
554
|
+
buildStep("r3", {
|
|
555
|
+
url: CATALOG_VENDOR_CONTRACTS_URL,
|
|
556
|
+
requestPostData: '{"lookup":true}',
|
|
557
|
+
responseBody: { contracts: [{ vendorId: "v1", contractId: "c1" }] },
|
|
558
|
+
timestamp: "2024-05-01T00:00:03Z",
|
|
559
|
+
requestHeaders: { "Content-Type": "application/json", "X-Vendor-Id": "v1" },
|
|
560
|
+
}),
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
const CATALOG_STOCK_URL = "https://api.example.com/catalog/stock/";
|
|
564
|
+
/** A single-shot search drilled by two INDEPENDENT later calls whose join
|
|
565
|
+
* values thread through different surfaces: the pricing step's `sku` lands
|
|
566
|
+
* in its JSON body (heuristically detectable by `collectRequestStringValues`),
|
|
567
|
+
* while the stock step's `itemId` is carried ONLY in a request header —
|
|
568
|
+
* `collectRequestStringValues` never scans headers, so that fold can only be
|
|
569
|
+
* resolved via a flow-declared `foldReturn` spec. Every downstream unit and
|
|
570
|
+
* runtime test that needs one mixed-source, two-target fold scenario should
|
|
571
|
+
* share this fixture rather than inventing its own. */
|
|
572
|
+
function buildMulticallSingleShotSearchHeuristicAndSpecTwoTargetActionSteps() {
|
|
573
|
+
return [
|
|
574
|
+
buildStep("r0", {
|
|
575
|
+
url: CATALOG_SEARCH_URL,
|
|
576
|
+
requestPostData: '{"page":1}',
|
|
577
|
+
responseBody: {
|
|
578
|
+
results: [
|
|
579
|
+
{ sku: "sku-a", itemId: "item-a" },
|
|
580
|
+
{ sku: "sku-b", itemId: "item-b" },
|
|
581
|
+
],
|
|
582
|
+
},
|
|
583
|
+
timestamp: "2024-04-01T00:00:00Z",
|
|
584
|
+
}),
|
|
585
|
+
buildStep("r1", {
|
|
586
|
+
url: CATALOG_PRICING_URL,
|
|
587
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
588
|
+
responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
|
|
589
|
+
timestamp: "2024-04-01T00:00:01Z",
|
|
590
|
+
}),
|
|
591
|
+
buildStep("r2", {
|
|
592
|
+
url: CATALOG_STOCK_URL,
|
|
593
|
+
requestPostData: '{"lookup":true}',
|
|
594
|
+
responseBody: { stock: [{ itemId: "item-a", qty: 7 }] },
|
|
595
|
+
timestamp: "2024-04-01T00:00:02Z",
|
|
596
|
+
requestHeaders: { "Content-Type": "application/json", "X-Item-Id": "item-a" },
|
|
597
|
+
}),
|
|
598
|
+
];
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* TWO occurrences of the SAME primary endpoint (same origin+pathname,
|
|
602
|
+
* differing only by query string), each independently drilling a
|
|
603
|
+
* DIFFERENT item into a DIFFERENT dependent call — unlike
|
|
604
|
+
* {@link buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps}
|
|
605
|
+
* (whose two occurrences thread the SAME drill), these thread two distinct
|
|
606
|
+
* drills, so {@link detectDrillDownFoldPlan}'s freshest-wins collapse never
|
|
607
|
+
* applies and both occurrences resolve as their own independent
|
|
608
|
+
* {@link FoldPlan}. Both plans' primary responses share the SAME top-level
|
|
609
|
+
* array key (`results`) — the exact shape a plain `{ ...a, ...b }`
|
|
610
|
+
* object-spread silently collapses to the later plan's array alone, since
|
|
611
|
+
* the emitted return statement folds every resolved plan's own primary var
|
|
612
|
+
* together into the runtime response.
|
|
613
|
+
*/
|
|
614
|
+
function buildMulticallTwoOccurrencesSamePrimaryDistinctDrillsActionSteps() {
|
|
615
|
+
return [
|
|
616
|
+
buildStep("r0", {
|
|
617
|
+
url: `${CATALOG_SEARCH_URL}?q=widgets`,
|
|
618
|
+
requestPostData: '{"page":1}',
|
|
619
|
+
responseBody: { results: [{ sku: "sku-a" }] },
|
|
620
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
621
|
+
}),
|
|
622
|
+
buildStep("r1", {
|
|
623
|
+
url: CATALOG_PRICING_URL,
|
|
624
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
625
|
+
responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
|
|
626
|
+
timestamp: "2025-01-01T00:00:01Z",
|
|
627
|
+
}),
|
|
628
|
+
buildStep("r2", {
|
|
629
|
+
url: `${CATALOG_SEARCH_URL}?q=gadgets`,
|
|
630
|
+
requestPostData: '{"page":1}',
|
|
631
|
+
responseBody: { results: [{ sku: "sku-c" }] },
|
|
632
|
+
timestamp: "2025-01-01T00:00:02Z",
|
|
633
|
+
}),
|
|
634
|
+
buildStep("r3", {
|
|
635
|
+
url: CATALOG_INVENTORY_URL,
|
|
636
|
+
requestPostData: '{"sku":"sku-c"}',
|
|
637
|
+
responseBody: { stock: [{ sku: "sku-c", qty: 4 }] },
|
|
638
|
+
timestamp: "2025-01-01T00:00:03Z",
|
|
639
|
+
}),
|
|
640
|
+
];
|
|
641
|
+
}
|
|
642
|
+
const CATALOG_SECTIONS_URL = "https://api.example.com/catalog/sections";
|
|
643
|
+
const CATALOG_ENTRY_DETAIL_URL = "https://api.example.com/catalog/entries/e2/details";
|
|
644
|
+
/**
|
|
645
|
+
* A grouped, nested-primary drill-down whose primary response carries TWO
|
|
646
|
+
* outer `sections[]` groups rather than the single group every other nested
|
|
647
|
+
* fixture in this file uses, with the drilled entry living in the SECOND
|
|
648
|
+
* group's `entries[]`, not the first. A fold that assumes the matched item
|
|
649
|
+
* always lives in group 0 (as a single-group fixture can never disprove)
|
|
650
|
+
* would resolve the fold onto `sections[0]` and silently attach the drill
|
|
651
|
+
* response's `description` to the wrong entry.
|
|
652
|
+
*/
|
|
653
|
+
function buildMulticallNestedGroupedDrillDownMultiGroupActionSteps() {
|
|
654
|
+
return [
|
|
655
|
+
buildStep("r0", {
|
|
656
|
+
url: CATALOG_SECTIONS_URL,
|
|
657
|
+
requestPostData: null,
|
|
658
|
+
responseBody: {
|
|
659
|
+
sections: [
|
|
660
|
+
{
|
|
661
|
+
label: "featured",
|
|
662
|
+
entries: [
|
|
663
|
+
{ entryId: "e1", name: "Widget" },
|
|
664
|
+
{ entryId: "e3", name: "Doohickey" },
|
|
665
|
+
],
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
label: "clearance",
|
|
669
|
+
entries: [
|
|
670
|
+
{ entryId: "e2", name: "Gadget" },
|
|
671
|
+
{ entryId: "e4", name: "Thingamajig" },
|
|
672
|
+
],
|
|
673
|
+
},
|
|
674
|
+
],
|
|
675
|
+
},
|
|
676
|
+
timestamp: "2024-11-01T00:00:00Z",
|
|
677
|
+
}),
|
|
678
|
+
buildStep("r1", {
|
|
679
|
+
url: CATALOG_ENTRY_DETAIL_URL,
|
|
680
|
+
requestPostData: null,
|
|
681
|
+
responseBody: { details: [{ entryId: "e2", description: "A gadget." }] },
|
|
682
|
+
timestamp: "2024-11-01T00:00:01Z",
|
|
683
|
+
}),
|
|
684
|
+
];
|
|
685
|
+
}
|
|
686
|
+
const ACCOUNT_SEARCH_URL = "https://api.example.com/accounts/search";
|
|
687
|
+
const ACCOUNT_DETAIL_URL = "https://api.example.com/accounts/detail";
|
|
688
|
+
/**
|
|
689
|
+
* A single-shot search whose primary item join field (`accountId`) is a
|
|
690
|
+
* NUMBER rather than a string, threaded into the drill-down call via a URL
|
|
691
|
+
* query parameter (`?accountId=42`). `URLSearchParams` always stringifies its
|
|
692
|
+
* values, so the request-collection side already captures `"42"` as a
|
|
693
|
+
* string; the item side of the join is what must widen to accept a numeric
|
|
694
|
+
* leaf for {@link detectDrillDownFoldPlan} to resolve `accountId` as a join
|
|
695
|
+
* field at all.
|
|
696
|
+
*/
|
|
697
|
+
function buildMulticallSingleShotSearchDrillDownNumericJoinActionSteps() {
|
|
698
|
+
return [
|
|
699
|
+
buildStep("r0", {
|
|
700
|
+
url: ACCOUNT_SEARCH_URL,
|
|
701
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
702
|
+
responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
|
|
703
|
+
timestamp: "2024-05-01T00:00:00Z",
|
|
704
|
+
}),
|
|
705
|
+
buildStep("r1", {
|
|
706
|
+
url: `${ACCOUNT_DETAIL_URL}?accountId=42`,
|
|
707
|
+
requestPostData: null,
|
|
708
|
+
responseBody: { transactions: [{ transactionId: "t1" }] },
|
|
709
|
+
timestamp: "2024-05-01T00:00:01Z",
|
|
710
|
+
}),
|
|
711
|
+
];
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* A single-shot search whose primary item join field (`accountId`) is
|
|
715
|
+
* threaded into the drill-down call ONLY as a URL path segment
|
|
716
|
+
* (`/accounts/42/transactions`) — never as a query param or a JSON body
|
|
717
|
+
* value. REST-style APIs commonly shape drill-down calls this way, but
|
|
718
|
+
* `collectRequestStringValues` only harvested query params and JSON body
|
|
719
|
+
* leaves, so this join field was invisible to `findThreadedJoinFields` and
|
|
720
|
+
* {@link detectDrillDownFoldPlan} returned null for this shape.
|
|
721
|
+
*/
|
|
722
|
+
function buildMulticallSingleShotSearchDrillDownPathThreadedJoinActionSteps() {
|
|
723
|
+
return [
|
|
724
|
+
buildStep("r0", {
|
|
725
|
+
url: ACCOUNT_SEARCH_URL,
|
|
726
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
727
|
+
responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
|
|
728
|
+
timestamp: "2024-07-01T00:00:00Z",
|
|
729
|
+
}),
|
|
730
|
+
buildStep("r1", {
|
|
731
|
+
url: "https://api.example.com/accounts/42/transactions",
|
|
732
|
+
requestPostData: null,
|
|
733
|
+
responseBody: { transactions: [{ transactionId: "t1" }] },
|
|
734
|
+
timestamp: "2024-07-01T00:00:01Z",
|
|
735
|
+
}),
|
|
736
|
+
];
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* A single-shot search whose primary item join field (`sku`) sits inside a
|
|
740
|
+
* NESTED object (`{ identifiers: { sku } }`) rather than as a top-level
|
|
741
|
+
* property. `findThreadedJoinFields` must walk into nested plain objects to
|
|
742
|
+
* find it, and the resulting joinFields entry is the dot-separated path
|
|
743
|
+
* `"identifiers.sku"`, not the bare leaf name `"sku"`.
|
|
744
|
+
*/
|
|
745
|
+
function buildMulticallSingleShotSearchDrillDownNestedJoinFieldActionSteps() {
|
|
746
|
+
return [
|
|
747
|
+
buildStep("r0", {
|
|
748
|
+
url: ACCOUNT_SEARCH_URL,
|
|
749
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
750
|
+
responseBody: { accounts: [{ identifiers: { sku: "SKU-1" }, name: "Acme" }] },
|
|
751
|
+
timestamp: "2024-08-01T00:00:00Z",
|
|
752
|
+
}),
|
|
753
|
+
buildStep("r1", {
|
|
754
|
+
url: ACCOUNT_DETAIL_URL,
|
|
755
|
+
requestPostData: JSON.stringify({ sku: "SKU-1" }),
|
|
756
|
+
responseBody: { transactions: [{ transactionId: "t1" }] },
|
|
757
|
+
timestamp: "2024-08-01T00:00:01Z",
|
|
758
|
+
}),
|
|
759
|
+
];
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* A single-shot search whose primary items carry a COMPOSITE join key mixing
|
|
763
|
+
* a string field (`region`) and a numeric field (`accountId`), both threaded
|
|
764
|
+
* into the drill-down request. `findThreadedJoinFields` filters candidate
|
|
765
|
+
* fields independently per entry, so a numeric field dropped alongside a
|
|
766
|
+
* present string field would silently degrade the fold to a partial
|
|
767
|
+
* (string-only) join key rather than failing outright — this fixture proves
|
|
768
|
+
* both survive together, in the item's own key order.
|
|
769
|
+
*/
|
|
770
|
+
function buildMulticallSingleShotSearchDrillDownCompositeNumericJoinActionSteps() {
|
|
771
|
+
return [
|
|
772
|
+
buildStep("r0", {
|
|
773
|
+
url: ACCOUNT_SEARCH_URL,
|
|
774
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
775
|
+
responseBody: { accounts: [{ region: "us", accountId: 7, name: "Acme" }] },
|
|
776
|
+
timestamp: "2024-06-01T00:00:00Z",
|
|
777
|
+
}),
|
|
778
|
+
buildStep("r1", {
|
|
779
|
+
url: `${ACCOUNT_DETAIL_URL}?region=us&accountId=7`,
|
|
780
|
+
requestPostData: null,
|
|
781
|
+
responseBody: { transactions: [{ transactionId: "t1" }] },
|
|
782
|
+
timestamp: "2024-06-01T00:00:01Z",
|
|
783
|
+
}),
|
|
784
|
+
];
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* A decoy-free single-shot search sibling of
|
|
788
|
+
* {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps} whose
|
|
789
|
+
* `results[]` has TWO items, but whose only captured drill-down call was
|
|
790
|
+
* made for the SECOND item (`sku-b`), not the first. A heuristic that always
|
|
791
|
+
* pairs the primary's `results[0]` with the sole drill step would thread
|
|
792
|
+
* `sku-a` into the fold even though the captured request body only ever
|
|
793
|
+
* mentions `sku-b` — proving the fold must select the drill call's item by
|
|
794
|
+
* matching the join field's actual value, not by array position.
|
|
795
|
+
*/
|
|
796
|
+
function buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps() {
|
|
797
|
+
return [
|
|
798
|
+
buildStep("r0", {
|
|
799
|
+
url: CATALOG_SEARCH_URL,
|
|
800
|
+
requestPostData: '{"page":1}',
|
|
801
|
+
responseBody: {
|
|
802
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
803
|
+
},
|
|
804
|
+
timestamp: "2024-09-01T00:00:00Z",
|
|
805
|
+
}),
|
|
806
|
+
buildStep("r1", {
|
|
807
|
+
url: CATALOG_PRICING_URL,
|
|
808
|
+
requestPostData: '{"sku":"sku-b"}',
|
|
809
|
+
responseBody: { prices: [{ sku: "sku-b", amount: 24.99 }] },
|
|
810
|
+
timestamp: "2024-09-01T00:00:01Z",
|
|
811
|
+
}),
|
|
812
|
+
];
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* A composite-join sibling of
|
|
816
|
+
* {@link buildMulticallSingleShotSearchDrillDownCompositeNumericJoinActionSteps}
|
|
817
|
+
* whose `accounts[]` has TWO items, but whose only captured drill-down call
|
|
818
|
+
* threads the SECOND item's composite join key (`region: "eu"`,
|
|
819
|
+
* `accountId: 9`), never the first item's (`region: "us"`, `accountId: 7`).
|
|
820
|
+
* Proves the same non-first-item selection requirement holds when the join
|
|
821
|
+
* key is composite, not just when it's a single field.
|
|
822
|
+
*/
|
|
823
|
+
function buildMulticallSingleShotSearchDrillDownCompositeNumericJoinNonFirstItemActionSteps() {
|
|
824
|
+
return [
|
|
825
|
+
buildStep("r0", {
|
|
826
|
+
url: ACCOUNT_SEARCH_URL,
|
|
827
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
828
|
+
responseBody: {
|
|
829
|
+
accounts: [
|
|
830
|
+
{ region: "us", accountId: 7, name: "Acme" },
|
|
831
|
+
{ region: "eu", accountId: 9, name: "Globex" },
|
|
832
|
+
],
|
|
833
|
+
},
|
|
834
|
+
timestamp: "2024-09-01T00:00:00Z",
|
|
835
|
+
}),
|
|
836
|
+
buildStep("r1", {
|
|
837
|
+
url: `${ACCOUNT_DETAIL_URL}?region=eu&accountId=9`,
|
|
838
|
+
requestPostData: null,
|
|
839
|
+
responseBody: { transactions: [{ transactionId: "t2" }] },
|
|
840
|
+
timestamp: "2024-09-01T00:00:01Z",
|
|
841
|
+
}),
|
|
842
|
+
];
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* A single-shot search whose primary item join field (`accountId`) is
|
|
846
|
+
* threaded into the drill-down call ONLY via a custom request header
|
|
847
|
+
* (`X-Account-Id`) — never a query param, JSON body value, or URL path
|
|
848
|
+
* segment. `collectRequestStringValues` deliberately never scans
|
|
849
|
+
* `requestHeaders` (recon-generate.ts:4787-4815), so this join is invisible
|
|
850
|
+
* to `findThreadedJoinFields` and {@link detectDrillDownFoldPlan} must return
|
|
851
|
+
* `null` here regardless of how many other threading channels it learns to
|
|
852
|
+
* scan — only a flow-declared `foldReturn` naming `accountId` can resolve
|
|
853
|
+
* this fold.
|
|
854
|
+
*/
|
|
855
|
+
function buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps() {
|
|
856
|
+
return [
|
|
857
|
+
buildStep("r0", {
|
|
858
|
+
url: ACCOUNT_SEARCH_URL,
|
|
859
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
860
|
+
responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
|
|
861
|
+
timestamp: "2024-08-01T00:00:00Z",
|
|
862
|
+
}),
|
|
863
|
+
buildStep("r1", {
|
|
864
|
+
url: ACCOUNT_DETAIL_URL,
|
|
865
|
+
requestPostData: null,
|
|
866
|
+
requestHeaders: { "Content-Type": "application/json", "X-Account-Id": "42" },
|
|
867
|
+
responseBody: { transactions: [{ transactionId: "t1" }] },
|
|
868
|
+
timestamp: "2024-08-01T00:00:01Z",
|
|
869
|
+
}),
|
|
870
|
+
];
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* A sibling of {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}
|
|
874
|
+
* whose primary endpoint is hit TWICE (`r0`, `r1`), each occurrence's single
|
|
875
|
+
* `accounts[]` item independently satisfying the drill-down's `X-Account-Id`
|
|
876
|
+
* join on its own — mirroring
|
|
877
|
+
* {@link buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps}'s
|
|
878
|
+
* requeried-primary-overlap shape, but with the join threaded ONLY through a
|
|
879
|
+
* request header rather than a URL query param, so `detectDrillDownFoldPlan`'s
|
|
880
|
+
* structural heuristic can never see it (same blind spot documented on
|
|
881
|
+
* {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}).
|
|
882
|
+
* Only a flow-declared `foldReturn` naming `accountId` can resolve this fold,
|
|
883
|
+
* isolating `buildFoldPlanFromSpec`'s own freshest-wins primary lookahead:
|
|
884
|
+
* the resolved plan must anchor on `r1` (the LATER occurrence) and its
|
|
885
|
+
* differing `name` value ("Acme Corp"), not `r0`'s stale "Acme".
|
|
886
|
+
*/
|
|
887
|
+
function buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinRequeriedPrimaryOverlapActionSteps() {
|
|
888
|
+
return [
|
|
889
|
+
buildStep("r0", {
|
|
890
|
+
url: ACCOUNT_SEARCH_URL,
|
|
891
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
892
|
+
responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
|
|
893
|
+
timestamp: "2024-08-01T00:00:00Z",
|
|
894
|
+
}),
|
|
895
|
+
buildStep("r1", {
|
|
896
|
+
url: ACCOUNT_SEARCH_URL,
|
|
897
|
+
requestPostData: JSON.stringify({ page: 2 }),
|
|
898
|
+
responseBody: { accounts: [{ accountId: 42, name: "Acme Corp" }] },
|
|
899
|
+
timestamp: "2024-08-01T00:00:01Z",
|
|
900
|
+
}),
|
|
901
|
+
buildStep("r2", {
|
|
902
|
+
url: ACCOUNT_DETAIL_URL,
|
|
903
|
+
requestPostData: null,
|
|
904
|
+
requestHeaders: { "Content-Type": "application/json", "X-Account-Id": "42" },
|
|
905
|
+
responseBody: { transactions: [{ transactionId: "t1" }] },
|
|
906
|
+
timestamp: "2024-08-01T00:00:02Z",
|
|
907
|
+
}),
|
|
908
|
+
];
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Sibling of {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}
|
|
912
|
+
* whose primary `accounts[]` array holds TWO items rather than one, so a
|
|
913
|
+
* runtime fold loop built from this capture must re-key the drill-down's
|
|
914
|
+
* request header per iteration rather than replaying the single captured
|
|
915
|
+
* header value.
|
|
916
|
+
*
|
|
917
|
+
* Threaded on `API-Token` rather than the sibling fixture's `X-Account-Id`:
|
|
918
|
+
* `emitMultiStepExecuteHttp`'s per-call header emission only ever renders a
|
|
919
|
+
* captured header back into the generated request when it recognizes the
|
|
920
|
+
* header name (`API-Token`/`Authorization`, or a base-URL-/tenant-derived
|
|
921
|
+
* header passed in separately — recon-generate.ts:4273-4279) — an arbitrary
|
|
922
|
+
* custom header like `X-Account-Id` is captured but never re-emitted, so a
|
|
923
|
+
* fold loop built from it would have nothing to re-key at runtime. `API-Token`
|
|
924
|
+
* is still a request header the join value reaches through NO OTHER channel
|
|
925
|
+
* (not the URL, which is identical on every call, and not the body, which is
|
|
926
|
+
* empty), so it still exercises the same header-threaded-join runtime path
|
|
927
|
+
* {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}
|
|
928
|
+
* demonstrates is invisible to the structural heuristic.
|
|
929
|
+
*
|
|
930
|
+
* The captured drill call was made for `accountId: 43` (the SECOND item),
|
|
931
|
+
* pinning `primaryMatchedItemIndex` away from the index-0 default.
|
|
932
|
+
*/
|
|
933
|
+
function buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinMultiItemActionSteps() {
|
|
934
|
+
return [
|
|
935
|
+
buildStep("r0", {
|
|
936
|
+
url: ACCOUNT_SEARCH_URL,
|
|
937
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
938
|
+
responseBody: {
|
|
939
|
+
accounts: [
|
|
940
|
+
{ accountId: 42, name: "Acme" },
|
|
941
|
+
{ accountId: 43, name: "Globex" },
|
|
942
|
+
],
|
|
943
|
+
},
|
|
944
|
+
timestamp: "2024-08-01T00:00:00Z",
|
|
945
|
+
}),
|
|
946
|
+
buildStep("r1", {
|
|
947
|
+
url: ACCOUNT_DETAIL_URL,
|
|
948
|
+
requestPostData: null,
|
|
949
|
+
requestHeaders: { "Content-Type": "application/json", "API-Token": "43" },
|
|
950
|
+
responseBody: { transactions: [{ transactionId: "t2" }] },
|
|
951
|
+
timestamp: "2024-08-01T00:00:01Z",
|
|
952
|
+
}),
|
|
953
|
+
];
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* A single-shot search whose primary results array is NOT drilled into at
|
|
957
|
+
* item 0 — only a single later drill call exists, and it threads the second
|
|
958
|
+
* item's (`itemId: "i-a"`) join value, never the first's (`itemId: "i-b"`).
|
|
959
|
+
* With no coincidental extra call re-drilling item 0 (unlike
|
|
960
|
+
* {@link buildMulticallDependentDrillDownActionSteps}, whose items are also
|
|
961
|
+
* out of order but which happens to pass a items[0]-only match because a
|
|
962
|
+
* third step re-drills item 0), this pins {@link detectDrillDownFoldPlan}'s
|
|
963
|
+
* item search to the actually-drilled item rather than an index-0 default —
|
|
964
|
+
* the search must find the match at `primaryMatchedItemIndex` 1.
|
|
965
|
+
* Named "OutOfOrder" (rather than "NonFirstItem") to avoid colliding with
|
|
966
|
+
* {@link buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps}.
|
|
967
|
+
*/
|
|
968
|
+
function buildMulticallSingleShotSearchDrillDownOutOfOrderItemActionSteps() {
|
|
969
|
+
return [
|
|
970
|
+
buildStep("r0", {
|
|
971
|
+
url: CATALOG_SEARCH_URL,
|
|
972
|
+
requestPostData: '{"page":1}',
|
|
973
|
+
responseBody: {
|
|
974
|
+
results: [{ itemId: "i-b" }, { itemId: "i-a" }],
|
|
975
|
+
},
|
|
976
|
+
timestamp: "2024-09-01T00:00:00Z",
|
|
977
|
+
}),
|
|
978
|
+
buildStep("r1", {
|
|
979
|
+
url: CATALOG_PRICING_URL,
|
|
980
|
+
requestPostData: '{"itemId":"i-a"}',
|
|
981
|
+
responseBody: { prices: [{ itemId: "i-a", amount: 19.99 }] },
|
|
982
|
+
timestamp: "2024-09-01T00:00:01Z",
|
|
983
|
+
}),
|
|
984
|
+
];
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* A single-shot search sibling of
|
|
988
|
+
* {@link buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps}
|
|
989
|
+
* whose axis is on the DRILL side rather than the primary side: the primary
|
|
990
|
+
* `results[]` has a single item (`sku-a`), but the drill-down's own
|
|
991
|
+
* `prices[]` array comes back holding TWO entries — a decoy for an unrelated
|
|
992
|
+
* sku (`sku-z`, listed first) alongside the real match (`sku-a`, listed
|
|
993
|
+
* second). A fold that merges `foldMatches[0]`/`drillItems?.[0]` would
|
|
994
|
+
* silently splice the decoy's `amount` onto the primary item; only matching
|
|
995
|
+
* the drill array's own entries against the threaded join field (`sku`)
|
|
996
|
+
* picks the correct one, at a non-zero index within that array.
|
|
997
|
+
*/
|
|
998
|
+
function buildMulticallSingleShotSearchDrillDownMultiMatchActionSteps() {
|
|
999
|
+
return [
|
|
1000
|
+
buildStep("r0", {
|
|
1001
|
+
url: CATALOG_SEARCH_URL,
|
|
1002
|
+
requestPostData: '{"page":1}',
|
|
1003
|
+
responseBody: {
|
|
1004
|
+
results: [{ sku: "sku-a" }],
|
|
1005
|
+
},
|
|
1006
|
+
timestamp: "2024-09-01T00:00:00Z",
|
|
1007
|
+
}),
|
|
1008
|
+
buildStep("r1", {
|
|
1009
|
+
url: CATALOG_PRICING_URL,
|
|
1010
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
1011
|
+
responseBody: {
|
|
1012
|
+
prices: [
|
|
1013
|
+
{ sku: "sku-z", amount: 5.0 },
|
|
1014
|
+
{ sku: "sku-a", amount: 19.99 },
|
|
1015
|
+
],
|
|
1016
|
+
},
|
|
1017
|
+
timestamp: "2024-09-01T00:00:01Z",
|
|
1018
|
+
}),
|
|
1019
|
+
];
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* A multi-match sibling of
|
|
1023
|
+
* {@link buildMulticallSingleShotSearchDrillDownMultiMatchActionSteps} whose
|
|
1024
|
+
* join field's type DIFFERS across the two sides: the primary item's
|
|
1025
|
+
* `accountId` is a NUMBER, while every item in the drill-down response's
|
|
1026
|
+
* array carries `accountId` as a STRING (as request-threaded values always
|
|
1027
|
+
* are). A decoy entry is listed first with a string that never equals the
|
|
1028
|
+
* primary's numeric value even after coercion; only the second entry's
|
|
1029
|
+
* string stringifies to the primary's value. A fold that matches via strict
|
|
1030
|
+
* equality (`m["accountId"] === item.accountId`, i.e. `"42" === 42`) fails
|
|
1031
|
+
* for every candidate here, proving the fold must compare join values by
|
|
1032
|
+
* their string representation, not by strict identity.
|
|
1033
|
+
*/
|
|
1034
|
+
function buildMulticallSingleShotSearchDrillDownTypeMismatchJoinActionSteps() {
|
|
1035
|
+
return [
|
|
1036
|
+
buildStep("r0", {
|
|
1037
|
+
url: ACCOUNT_SEARCH_URL,
|
|
1038
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
1039
|
+
responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
|
|
1040
|
+
timestamp: "2024-10-01T00:00:00Z",
|
|
1041
|
+
}),
|
|
1042
|
+
buildStep("r1", {
|
|
1043
|
+
url: `${ACCOUNT_DETAIL_URL}?accountId=42`,
|
|
1044
|
+
requestPostData: null,
|
|
1045
|
+
responseBody: {
|
|
1046
|
+
transactions: [
|
|
1047
|
+
{ accountId: "99", transactionId: "decoy" },
|
|
1048
|
+
{ accountId: "42", transactionId: "t-real" },
|
|
1049
|
+
],
|
|
1050
|
+
},
|
|
1051
|
+
timestamp: "2024-10-01T00:00:01Z",
|
|
1052
|
+
}),
|
|
1053
|
+
];
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Same shape as {@link buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps},
|
|
1057
|
+
* but the CHAIN's second step (the price-history call `r2`, not the drill
|
|
1058
|
+
* step `r1` itself) is a multipart upload rather than a JSON POST.
|
|
1059
|
+
* `emitMultiStepExecuteHttp`'s fold loop re-issues EVERY chain step's
|
|
1060
|
+
* request per item by re-keying its rendered JSON body template — a
|
|
1061
|
+
* multipart step anywhere in `chain`, not just at `drillStepIndex`, has no
|
|
1062
|
+
* such template to re-key, so `resolveFoldPlan` must disqualify the whole
|
|
1063
|
+
* plan here exactly as it already does when the drill step itself is
|
|
1064
|
+
* multipart.
|
|
1065
|
+
*/
|
|
1066
|
+
function buildMulticallSingleShotSearchDrillDownChainedDependentMultipartChainStepActionSteps() {
|
|
1067
|
+
const steps = buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps();
|
|
1068
|
+
return steps.map((step, index) => (index === 2 ? { ...step, isMultipart: true } : step));
|
|
1069
|
+
}
|
|
1070
|
+
const CATALOG_PRICE_HISTORY_URL = "https://api.example.com/catalog/price-history";
|
|
1071
|
+
/**
|
|
1072
|
+
* A single-shot search → per-item drill-down flow whose drill step's OWN
|
|
1073
|
+
* response is foldable on its own terms — it carries `prices[]`, the same
|
|
1074
|
+
* per-item results shape {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps}
|
|
1075
|
+
* would fold directly — but is ALSO depended on by a THIRD step: the drill
|
|
1076
|
+
* response's `priceToken` threads into a price-history call whose response
|
|
1077
|
+
* carries the real per-item array this flow means to fold. `computeFoldChain`
|
|
1078
|
+
* must extend the plan's chain to `[drillStepIndex, historyStepIndex]`
|
|
1079
|
+
* instead of stopping at the drill step's own array, so
|
|
1080
|
+
* `emitMultiStepExecuteHttp`'s fold loop renders BOTH calls per item and
|
|
1081
|
+
* shape inference ({@link foldResponseBodyForShapeInference}) folds the
|
|
1082
|
+
* chain's TERMINAL (history) response, not the drill step's own `prices[]`.
|
|
1083
|
+
*/
|
|
1084
|
+
function buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps() {
|
|
1085
|
+
return [
|
|
1086
|
+
buildStep("r0", {
|
|
1087
|
+
url: CATALOG_SEARCH_URL,
|
|
1088
|
+
requestPostData: '{"page":1}',
|
|
1089
|
+
responseBody: {
|
|
1090
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
1091
|
+
},
|
|
1092
|
+
timestamp: "2024-11-15T00:00:00Z",
|
|
1093
|
+
}),
|
|
1094
|
+
buildStep("r1", {
|
|
1095
|
+
url: CATALOG_PRICING_URL,
|
|
1096
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
1097
|
+
responseBody: {
|
|
1098
|
+
priceToken: "tok-a1",
|
|
1099
|
+
prices: [{ sku: "sku-a", amount: 19.99 }],
|
|
1100
|
+
},
|
|
1101
|
+
timestamp: "2024-11-15T00:00:01Z",
|
|
1102
|
+
}),
|
|
1103
|
+
buildStep("r2", {
|
|
1104
|
+
url: CATALOG_PRICE_HISTORY_URL,
|
|
1105
|
+
requestPostData: '{"priceToken":"tok-a1"}',
|
|
1106
|
+
responseBody: {
|
|
1107
|
+
history: [{ sku: "sku-a", amount: 18.5, asOf: "2024-11-01" }],
|
|
1108
|
+
},
|
|
1109
|
+
timestamp: "2024-11-15T00:00:02Z",
|
|
1110
|
+
}),
|
|
1111
|
+
];
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* A response-header sibling of {@link buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps}:
|
|
1115
|
+
* the drill step's (`r1`) response mints its join token (`tok-a1`) ONLY in a
|
|
1116
|
+
* custom response HEADER (`X-Price-Token`) — its body is empty, unlike the
|
|
1117
|
+
* sibling's `priceToken` body field. A third step (`r2`) threads that header
|
|
1118
|
+
* value into its own request body and carries the real per-item `history[]`
|
|
1119
|
+
* array. `computeFoldChain`'s `dependsOnChain` check must walk `r1`'s
|
|
1120
|
+
* response headers (not just its body) to see the shared value, extending
|
|
1121
|
+
* the chain to `[drillStepIndex, historyStepIndex]` rather than stopping at
|
|
1122
|
+
* `r1`.
|
|
1123
|
+
*/
|
|
1124
|
+
function buildMulticallSingleShotSearchDrillDownHeaderMintedChainedResponseValueActionSteps() {
|
|
1125
|
+
return [
|
|
1126
|
+
buildStep("r0", {
|
|
1127
|
+
url: CATALOG_SEARCH_URL,
|
|
1128
|
+
requestPostData: '{"page":1}',
|
|
1129
|
+
responseBody: {
|
|
1130
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
1131
|
+
},
|
|
1132
|
+
timestamp: "2024-11-16T00:00:00Z",
|
|
1133
|
+
}),
|
|
1134
|
+
buildStep("r1", {
|
|
1135
|
+
url: CATALOG_PRICING_URL,
|
|
1136
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
1137
|
+
responseBody: {},
|
|
1138
|
+
responseHeaders: { "content-type": "application/json", "X-Price-Token": "tok-a1" },
|
|
1139
|
+
timestamp: "2024-11-16T00:00:01Z",
|
|
1140
|
+
}),
|
|
1141
|
+
buildStep("r2", {
|
|
1142
|
+
url: CATALOG_PRICE_HISTORY_URL,
|
|
1143
|
+
requestPostData: '{"priceToken":"tok-a1"}',
|
|
1144
|
+
responseBody: {
|
|
1145
|
+
history: [{ sku: "sku-a", amount: 18.5, asOf: "2024-11-01" }],
|
|
1146
|
+
},
|
|
1147
|
+
timestamp: "2024-11-16T00:00:02Z",
|
|
1148
|
+
}),
|
|
1149
|
+
];
|
|
1150
|
+
}
|
|
1151
|
+
const CATALOG_VERIFICATION_STATUS_URL = "https://api.example.com/catalog/verification-status";
|
|
1152
|
+
/**
|
|
1153
|
+
* A boolean-threading sibling of {@link buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps}:
|
|
1154
|
+
* the drill step's (`r1`) response mints ONLY a boolean value
|
|
1155
|
+
* (`{ verified: true }`) and carries no array of its own — unlike the
|
|
1156
|
+
* `priceToken`/`prices[]` sibling, this drill step is NOT foldable in
|
|
1157
|
+
* isolation. A third step (`r2`) threads that boolean (rendered as the
|
|
1158
|
+
* string `"true"`) into its request and carries the real per-item
|
|
1159
|
+
* `history[]` array in its response. `computeFoldChain` must recognize the
|
|
1160
|
+
* boolean leaf as a genuine chain-dependency source and extend the chain to
|
|
1161
|
+
* `[drillStepIndex, historyStepIndex]` rather than stopping at `r1`.
|
|
1162
|
+
*/
|
|
1163
|
+
function buildMulticallSingleShotSearchDrillDownBooleanChainedResponseValueActionSteps() {
|
|
1164
|
+
return [
|
|
1165
|
+
buildStep("r0", {
|
|
1166
|
+
url: CATALOG_SEARCH_URL,
|
|
1167
|
+
requestPostData: '{"page":1}',
|
|
1168
|
+
responseBody: {
|
|
1169
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
1170
|
+
},
|
|
1171
|
+
timestamp: "2024-12-01T00:00:00Z",
|
|
1172
|
+
}),
|
|
1173
|
+
buildStep("r1", {
|
|
1174
|
+
url: CATALOG_PRICING_URL,
|
|
1175
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
1176
|
+
responseBody: {
|
|
1177
|
+
verified: true,
|
|
1178
|
+
},
|
|
1179
|
+
timestamp: "2024-12-01T00:00:01Z",
|
|
1180
|
+
}),
|
|
1181
|
+
buildStep("r2", {
|
|
1182
|
+
url: CATALOG_VERIFICATION_STATUS_URL,
|
|
1183
|
+
requestPostData: '{"verified":"true"}',
|
|
1184
|
+
responseBody: {
|
|
1185
|
+
history: [{ sku: "sku-a", amount: 18.5, asOf: "2024-11-01" }],
|
|
1186
|
+
},
|
|
1187
|
+
timestamp: "2024-12-01T00:00:02Z",
|
|
1188
|
+
}),
|
|
1189
|
+
];
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* A nested-join-key sibling of {@link buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps}:
|
|
1193
|
+
* each primary `results[]` item carries its join key under a nested
|
|
1194
|
+
* `identifiers` object (`{ identifiers: { sku } }`, matching
|
|
1195
|
+
* {@link buildMulticallSingleShotSearchDrillDownNestedJoinFieldMultiItemActionSteps})
|
|
1196
|
+
* rather than as a top-level field, AND (as in the chained-dependent fixture)
|
|
1197
|
+
* the drill step's own response is foldable on its own terms but is ALSO
|
|
1198
|
+
* depended on by a further step whose response carries the real per-item
|
|
1199
|
+
* data. The primary item that threads the nested join is the SECOND item
|
|
1200
|
+
* (`sku-b`), not the first, so `primaryMatchedItemIndex` must resolve by
|
|
1201
|
+
* walking into each item's nested fields rather than assuming index 0, while
|
|
1202
|
+
* `computeFoldChain` must still extend the chain past the drill step to its
|
|
1203
|
+
* dependent follow-up rather than stopping at the drill step's own array.
|
|
1204
|
+
*/
|
|
1205
|
+
function buildMulticallSingleShotSearchDrillDownNestedJoinFieldChainedDependentActionSteps() {
|
|
1206
|
+
return [
|
|
1207
|
+
buildStep("r0", {
|
|
1208
|
+
url: CATALOG_SEARCH_URL,
|
|
1209
|
+
requestPostData: '{"page":1}',
|
|
1210
|
+
responseBody: {
|
|
1211
|
+
results: [{ identifiers: { sku: "sku-a" } }, { identifiers: { sku: "sku-b" } }],
|
|
1212
|
+
},
|
|
1213
|
+
timestamp: "2024-11-20T00:00:00Z",
|
|
1214
|
+
}),
|
|
1215
|
+
buildStep("r1", {
|
|
1216
|
+
url: CATALOG_PRICING_URL,
|
|
1217
|
+
requestPostData: '{"sku":"sku-b"}',
|
|
1218
|
+
responseBody: {
|
|
1219
|
+
priceToken: "tok-b1",
|
|
1220
|
+
prices: [{ sku: "sku-b", amount: 24.99 }],
|
|
1221
|
+
},
|
|
1222
|
+
timestamp: "2024-11-20T00:00:01Z",
|
|
1223
|
+
}),
|
|
1224
|
+
buildStep("r2", {
|
|
1225
|
+
url: CATALOG_PRICE_HISTORY_URL,
|
|
1226
|
+
requestPostData: '{"priceToken":"tok-b1"}',
|
|
1227
|
+
responseBody: {
|
|
1228
|
+
history: [{ sku: "sku-b", amount: 22.5, asOf: "2024-11-01" }],
|
|
1229
|
+
},
|
|
1230
|
+
timestamp: "2024-11-20T00:00:02Z",
|
|
1231
|
+
}),
|
|
1232
|
+
];
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* A search endpoint re-queried with two distinct bodies (`page 1`/`page 2`,
|
|
1236
|
+
* satisfying {@link findRequeriedActions}'s re-query signature) whose
|
|
1237
|
+
* responses BOTH independently contain a `results[]` item sharing the same
|
|
1238
|
+
* `sku` — but at a different `price` (10 vs 12) — followed by a pricing
|
|
1239
|
+
* drill-down call threading that `sku` via its URL query param. Every prior
|
|
1240
|
+
* action is a fold-primary candidate for {@link detectDrillDownFoldPlan}, so
|
|
1241
|
+
* both step 0 and step 1 satisfy the drill's join; the primary-candidate
|
|
1242
|
+
* scan currently commits to the FIRST match (step 0, `price: 10`) and
|
|
1243
|
+
* globally consumes the drill step, so step 1 — the fresher, `price: 12`
|
|
1244
|
+
* re-queried occurrence — is never considered. `amount` in the drill
|
|
1245
|
+
* response is the object-array field distinct from `price` that a fold
|
|
1246
|
+
* assertion reads to observe which occurrence was actually folded onto.
|
|
1247
|
+
*/
|
|
1248
|
+
function buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps() {
|
|
1249
|
+
return [
|
|
1250
|
+
buildStep("r0", {
|
|
1251
|
+
url: CATALOG_SEARCH_URL,
|
|
1252
|
+
requestPostData: '{"page":1}',
|
|
1253
|
+
responseBody: {
|
|
1254
|
+
results: [{ sku: "sku-a", price: 10 }],
|
|
1255
|
+
},
|
|
1256
|
+
timestamp: "2024-12-01T00:00:00Z",
|
|
1257
|
+
}),
|
|
1258
|
+
buildStep("r1", {
|
|
1259
|
+
url: CATALOG_SEARCH_URL,
|
|
1260
|
+
requestPostData: '{"page":2}',
|
|
1261
|
+
responseBody: {
|
|
1262
|
+
results: [{ sku: "sku-a", price: 12 }],
|
|
1263
|
+
},
|
|
1264
|
+
timestamp: "2024-12-01T00:00:01Z",
|
|
1265
|
+
}),
|
|
1266
|
+
buildStep("r2", {
|
|
1267
|
+
url: `${CATALOG_PRICING_URL}?sku=sku-a`,
|
|
1268
|
+
requestPostData: null,
|
|
1269
|
+
responseBody: {
|
|
1270
|
+
prices: [{ sku: "sku-a", amount: 19.99 }],
|
|
1271
|
+
},
|
|
1272
|
+
timestamp: "2024-12-01T00:00:02Z",
|
|
1273
|
+
}),
|
|
1274
|
+
];
|
|
1275
|
+
}
|
|
1276
|
+
const CATALOG_VENDOR_DETAIL_URL = "https://api.example.com/catalog/vendors/detail/";
|
|
1277
|
+
/**
|
|
1278
|
+
* A single primary search response carrying TWO genuinely independent
|
|
1279
|
+
* object-array fields — `products[]` and `vendors[]` — each drilled by its
|
|
1280
|
+
* own, unrelated later step (`sku` into a pricing lookup, `vendorId` into a
|
|
1281
|
+
* vendor-detail lookup). `scanPrimaryCandidate`'s old candidatePool lock
|
|
1282
|
+
* (recon-generate.ts:5411-5413) resolved only whichever array its FIRST
|
|
1283
|
+
* qualifying drill-down threaded from (`products[]`, via r1) and then
|
|
1284
|
+
* restricted every later candidate scan to that SAME array, so r2's
|
|
1285
|
+
* `vendorId` thread into `vendors[]` was silently discarded — this fixture
|
|
1286
|
+
* proves BOTH arrays fold independently, one FoldPlan per distinct
|
|
1287
|
+
* `primaryArrayPath`.
|
|
1288
|
+
*/
|
|
1289
|
+
function buildMulticallSingleShotSearchTwoIndependentArraysActionSteps() {
|
|
1290
|
+
return [
|
|
1291
|
+
buildStep("r0", {
|
|
1292
|
+
url: CATALOG_SEARCH_URL,
|
|
1293
|
+
requestPostData: '{"page":1}',
|
|
1294
|
+
responseBody: {
|
|
1295
|
+
products: [{ sku: "sku-a" }],
|
|
1296
|
+
vendors: [{ vendorId: "v1" }],
|
|
1297
|
+
},
|
|
1298
|
+
timestamp: "2025-01-01T00:00:00Z",
|
|
1299
|
+
}),
|
|
1300
|
+
buildStep("r1", {
|
|
1301
|
+
url: `${CATALOG_PRICING_URL}?sku=sku-a`,
|
|
1302
|
+
requestPostData: null,
|
|
1303
|
+
responseBody: {
|
|
1304
|
+
prices: [{ sku: "sku-a", amount: 9.99 }],
|
|
1305
|
+
},
|
|
1306
|
+
timestamp: "2025-01-01T00:00:01Z",
|
|
1307
|
+
}),
|
|
1308
|
+
buildStep("r2", {
|
|
1309
|
+
url: `${CATALOG_VENDOR_DETAIL_URL}?vendorId=v1`,
|
|
1310
|
+
requestPostData: null,
|
|
1311
|
+
responseBody: {
|
|
1312
|
+
contracts: [{ vendorId: "v1", contractId: "c1" }],
|
|
1313
|
+
},
|
|
1314
|
+
timestamp: "2025-01-01T00:00:02Z",
|
|
1315
|
+
}),
|
|
1316
|
+
];
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* A single primary search response carrying TWO independent object-array
|
|
1320
|
+
* fields — `products[]`, whose `sku` join threads through the pricing call's
|
|
1321
|
+
* own JSON body and so is structurally detectable, and `vendors[]`, whose
|
|
1322
|
+
* `vendorId` join threads ONLY through the vendor-detail call's
|
|
1323
|
+
* `X-Vendor-Id` request header, invisible to `collectRequestStringValues`
|
|
1324
|
+
* and resolvable only via a flow-declared `foldReturn` spec naming `vendors`
|
|
1325
|
+
* as its `resultsPath`. `mergeSpecPlanOntoSamePrimary` used to key its
|
|
1326
|
+
* consumed-index check off `primaryStepIndex` alone, so the spec's plan —
|
|
1327
|
+
* anchored on the SAME step 0 the structural heuristic had already resolved,
|
|
1328
|
+
* but naming a DIFFERENT array — was wrongly treated as already consumed and
|
|
1329
|
+
* silently dropped instead of appended.
|
|
1330
|
+
*/
|
|
1331
|
+
function buildMulticallStructuralPlusSpecOnlySameStepActionSteps() {
|
|
1332
|
+
return [
|
|
1333
|
+
buildStep("r0", {
|
|
1334
|
+
url: CATALOG_SEARCH_URL,
|
|
1335
|
+
requestPostData: '{"page":1}',
|
|
1336
|
+
responseBody: {
|
|
1337
|
+
products: [{ sku: "sku-a" }],
|
|
1338
|
+
vendors: [{ vendorId: "v1" }],
|
|
1339
|
+
},
|
|
1340
|
+
timestamp: "2025-03-01T00:00:00Z",
|
|
1341
|
+
}),
|
|
1342
|
+
buildStep("r1", {
|
|
1343
|
+
url: `${CATALOG_PRICING_URL}?sku=sku-a`,
|
|
1344
|
+
requestPostData: null,
|
|
1345
|
+
responseBody: {
|
|
1346
|
+
prices: [{ sku: "sku-a", amount: 9.99 }],
|
|
1347
|
+
},
|
|
1348
|
+
timestamp: "2025-03-01T00:00:01Z",
|
|
1349
|
+
}),
|
|
1350
|
+
buildStep("r2", {
|
|
1351
|
+
url: CATALOG_VENDOR_DETAIL_URL,
|
|
1352
|
+
requestPostData: '{"lookup":true}',
|
|
1353
|
+
responseBody: {
|
|
1354
|
+
contracts: [{ vendorId: "v1", contractId: "c1" }],
|
|
1355
|
+
},
|
|
1356
|
+
timestamp: "2025-03-01T00:00:02Z",
|
|
1357
|
+
requestHeaders: { "Content-Type": "application/json", "X-Vendor-Id": "v1" },
|
|
1358
|
+
}),
|
|
1359
|
+
];
|
|
1360
|
+
}
|
|
1361
|
+
const ORDER_HISTORY_URL = "https://api.example.com/orders/history/";
|
|
1362
|
+
/**
|
|
1363
|
+
* A 3-step chain where the drill step (`r1`) is a bare threading step — its
|
|
1364
|
+
* response carries the join value (`statusToken`) but no object-array field
|
|
1365
|
+
* at all, so it can't itself be the decoy. The decoy instead sits on the
|
|
1366
|
+
* chain's terminal step (`r2`): a `warnings[]` array positioned BEFORE the
|
|
1367
|
+
* real per-item `entries[]` array in key order, where `entries[]`'s items
|
|
1368
|
+
* thread `r2`'s own `statusToken` request value and `warnings[]`'s items do
|
|
1369
|
+
* not. {@link computeFoldChain}'s `selectDisambiguatedCandidate` call on
|
|
1370
|
+
* this (non-immediate) chain hop must resolve `chainArrayPath` to `entries`
|
|
1371
|
+
* on the threaded-join-fields match, not `warnings` for being found first.
|
|
1372
|
+
*/
|
|
1373
|
+
function buildMulticallSingleShotSearchDrillDownChainedDecoyOnChainTerminalActionSteps() {
|
|
1374
|
+
return [
|
|
1375
|
+
buildStep("r0", {
|
|
1376
|
+
url: CATALOG_SEARCH_URL,
|
|
1377
|
+
requestPostData: '{"page":1}',
|
|
1378
|
+
responseBody: {
|
|
1379
|
+
results: [{ orderId: "order-7" }],
|
|
1380
|
+
},
|
|
1381
|
+
timestamp: "2024-09-01T00:00:00Z",
|
|
1382
|
+
}),
|
|
1383
|
+
buildStep("r1", {
|
|
1384
|
+
url: "https://api.example.com/orders/order-7/status",
|
|
1385
|
+
requestPostData: null,
|
|
1386
|
+
responseBody: {
|
|
1387
|
+
statusToken: "tok-99",
|
|
1388
|
+
},
|
|
1389
|
+
timestamp: "2024-09-01T00:00:01Z",
|
|
1390
|
+
}),
|
|
1391
|
+
buildStep("r2", {
|
|
1392
|
+
url: ORDER_HISTORY_URL,
|
|
1393
|
+
requestPostData: '{"statusToken":"tok-99"}',
|
|
1394
|
+
responseBody: {
|
|
1395
|
+
warnings: [{ code: "stale-cache" }],
|
|
1396
|
+
entries: [{ statusToken: "tok-99", ts: "2024-09-01T00:00:02Z", event: "shipped" }],
|
|
1397
|
+
},
|
|
1398
|
+
timestamp: "2024-09-01T00:00:02Z",
|
|
1399
|
+
}),
|
|
1400
|
+
];
|
|
1401
|
+
}
|
|
1402
|
+
const CATALOG_ORDER_STATUS_URL = "https://api.example.com/catalog/order-status";
|
|
1403
|
+
/**
|
|
1404
|
+
* A single-shot search → per-item drill-down flow where the drill step's
|
|
1405
|
+
* OWN response (`r1`) is entirely OPAQUE — a bare array holding a single
|
|
1406
|
+
* token, satisfying neither `isObjectArrayItem` nor
|
|
1407
|
+
* `findAllObjectArrayFields`, so `selectDisambiguatedCandidate` finds no
|
|
1408
|
+
* candidate there at all — and is depended on by a THIRD step (`r2`) whose
|
|
1409
|
+
* response carries the real per-item array this flow means to fold. Unlike
|
|
1410
|
+
* {@link buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps},
|
|
1411
|
+
* whose `r1` is ALSO independently foldable, this fixture isolates the case
|
|
1412
|
+
* where `computeFoldChain` has nothing to fall back on at the immediate hop
|
|
1413
|
+
* and must extend the chain past it to `r2` to find any candidate at all.
|
|
1414
|
+
*/
|
|
1415
|
+
function buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps() {
|
|
1416
|
+
return [
|
|
1417
|
+
buildStep("r0", {
|
|
1418
|
+
url: CATALOG_SEARCH_URL,
|
|
1419
|
+
requestPostData: '{"page":1}',
|
|
1420
|
+
responseBody: {
|
|
1421
|
+
results: [{ orderId: "order-a" }, { orderId: "order-b" }],
|
|
1422
|
+
},
|
|
1423
|
+
timestamp: "2024-10-01T00:00:00Z",
|
|
1424
|
+
}),
|
|
1425
|
+
buildStep("r1", {
|
|
1426
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1427
|
+
requestPostData: '{"orderId":"order-a"}',
|
|
1428
|
+
responseBody: ["status-token-order-a"],
|
|
1429
|
+
timestamp: "2024-10-01T00:00:01Z",
|
|
1430
|
+
}),
|
|
1431
|
+
buildStep("r2", {
|
|
1432
|
+
url: ORDER_HISTORY_URL,
|
|
1433
|
+
requestPostData: '{"statusToken":"status-token-order-a"}',
|
|
1434
|
+
responseBody: {
|
|
1435
|
+
entries: [
|
|
1436
|
+
{ statusToken: "status-token-order-a", ts: "2024-10-01T00:00:02Z", event: "shipped" },
|
|
1437
|
+
],
|
|
1438
|
+
},
|
|
1439
|
+
timestamp: "2024-10-01T00:00:02Z",
|
|
1440
|
+
}),
|
|
1441
|
+
];
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* A 4-step sibling of
|
|
1445
|
+
* {@link buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps}
|
|
1446
|
+
* that inserts one more chain hop between the opaque token step and the real
|
|
1447
|
+
* per-item terminal: a flat confirmation object (`r2`) that echoes back the
|
|
1448
|
+
* token it was called with (excluded from its richness by
|
|
1449
|
+
* `directPrimitiveChildCountExcludingEchoed`) alongside a boolean status flag
|
|
1450
|
+
* AND a second, non-echoed token that threads onward into the real terminal
|
|
1451
|
+
* step (`r3`). Both `r2` and `r3` land on the SAME richness — two non-echoed
|
|
1452
|
+
* primitive fields apiece (`held`/`receiptToken` on `r2`, `event`/`ts` on
|
|
1453
|
+
* `r3`) — so `computeFoldChain`'s strict `candidateRichness > chainTerminalRichness`
|
|
1454
|
+
* comparison never lets `r3` displace `r2` as the chain terminal: a tie is
|
|
1455
|
+
* exactly as "not richer" as a loss. This isolates the case where a
|
|
1456
|
+
* side-effect confirmation hop that merely happens to match the real
|
|
1457
|
+
* terminal's field count silently masks it, as opposed to
|
|
1458
|
+
* {@link buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps}'s
|
|
1459
|
+
* opaque hop, which offers no candidate at all.
|
|
1460
|
+
*/
|
|
1461
|
+
function buildMulticallSingleShotSearchDrillDownRichnessTiedConfirmationHopChainedDependentActionSteps() {
|
|
1462
|
+
return [
|
|
1463
|
+
buildStep("r0", {
|
|
1464
|
+
url: CATALOG_SEARCH_URL,
|
|
1465
|
+
requestPostData: '{"page":1}',
|
|
1466
|
+
responseBody: {
|
|
1467
|
+
results: [{ orderId: "order-a" }, { orderId: "order-b" }],
|
|
1468
|
+
},
|
|
1469
|
+
timestamp: "2024-10-02T00:00:00Z",
|
|
1470
|
+
}),
|
|
1471
|
+
buildStep("r1", {
|
|
1472
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1473
|
+
requestPostData: '{"orderId":"order-a"}',
|
|
1474
|
+
responseBody: ["status-token-order-a"],
|
|
1475
|
+
timestamp: "2024-10-02T00:00:01Z",
|
|
1476
|
+
}),
|
|
1477
|
+
buildStep("r2", {
|
|
1478
|
+
url: ORDER_HISTORY_URL,
|
|
1479
|
+
requestPostData: '{"statusToken":"status-token-order-a"}',
|
|
1480
|
+
responseBody: {
|
|
1481
|
+
statusToken: "status-token-order-a",
|
|
1482
|
+
held: true,
|
|
1483
|
+
receiptToken: "receipt-token-order-a",
|
|
1484
|
+
},
|
|
1485
|
+
timestamp: "2024-10-02T00:00:02Z",
|
|
1486
|
+
}),
|
|
1487
|
+
buildStep("r3", {
|
|
1488
|
+
url: `${ORDER_HISTORY_URL}receipt/`,
|
|
1489
|
+
requestPostData: '{"receiptToken":"receipt-token-order-a"}',
|
|
1490
|
+
responseBody: {
|
|
1491
|
+
entries: [
|
|
1492
|
+
{
|
|
1493
|
+
receiptToken: "receipt-token-order-a",
|
|
1494
|
+
event: "shipped",
|
|
1495
|
+
ts: "2024-10-02T00:00:03Z",
|
|
1496
|
+
},
|
|
1497
|
+
],
|
|
1498
|
+
},
|
|
1499
|
+
timestamp: "2024-10-02T00:00:03Z",
|
|
1500
|
+
}),
|
|
1501
|
+
];
|
|
1502
|
+
}
|
|
1503
|
+
const ORDER_HISTORY_BULK_URL = "https://api.example.com/orders/history/bulk";
|
|
1504
|
+
/**
|
|
1505
|
+
* An array-wrapped sibling of
|
|
1506
|
+
* {@link buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps}:
|
|
1507
|
+
* the opaque intermediate hop (`r1`) is identical, but the chain's TERMINAL
|
|
1508
|
+
* hop (`r2`) threads `r1`'s produced `statusToken` wrapped inside a
|
|
1509
|
+
* single-element request-body ARRAY (`{"tokens":["status-token-order-a"]}`,
|
|
1510
|
+
* a bulk/batch-lookup request shape) instead of as a flat top-level scalar
|
|
1511
|
+
* field. `computeFoldChain`'s structural `dependsOnChain` detection (which
|
|
1512
|
+
* walks every request-body leaf regardless of nesting) resolves the chain
|
|
1513
|
+
* correctly either way, so this isolates a DIFFERENT failure: the
|
|
1514
|
+
* request-body render must still be able to thread `statusToken` into that
|
|
1515
|
+
* array slot instead of freezing the whole array as an opaque caller-payload
|
|
1516
|
+
* blob (`applyStructuredValuePayloadSubstitutions`'s "swallow whole
|
|
1517
|
+
* caller-supplied array/object" mechanism, which runs BEFORE state
|
|
1518
|
+
* threading and has no concept of "this array actually wraps a threaded
|
|
1519
|
+
* dependent-drill-down join value").
|
|
1520
|
+
*/
|
|
1521
|
+
function buildMulticallSingleShotSearchDrillDownArrayWrappedChainedDependentActionSteps() {
|
|
1522
|
+
return [
|
|
1523
|
+
buildStep("r0", {
|
|
1524
|
+
url: CATALOG_SEARCH_URL,
|
|
1525
|
+
requestPostData: '{"page":1}',
|
|
1526
|
+
responseBody: {
|
|
1527
|
+
results: [{ orderId: "order-a" }, { orderId: "order-b" }],
|
|
1528
|
+
},
|
|
1529
|
+
timestamp: "2024-10-03T00:00:00Z",
|
|
1530
|
+
}),
|
|
1531
|
+
buildStep("r1", {
|
|
1532
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1533
|
+
requestPostData: '{"orderId":"order-a"}',
|
|
1534
|
+
responseBody: ["status-token-order-a"],
|
|
1535
|
+
timestamp: "2024-10-03T00:00:01Z",
|
|
1536
|
+
}),
|
|
1537
|
+
buildStep("r2", {
|
|
1538
|
+
url: ORDER_HISTORY_BULK_URL,
|
|
1539
|
+
requestPostData: '{"tokens":["status-token-order-a"]}',
|
|
1540
|
+
responseBody: {
|
|
1541
|
+
entries: [
|
|
1542
|
+
{ statusToken: "status-token-order-a", ts: "2024-10-03T00:00:02Z", event: "shipped" },
|
|
1543
|
+
],
|
|
1544
|
+
},
|
|
1545
|
+
timestamp: "2024-10-03T00:00:02Z",
|
|
1546
|
+
}),
|
|
1547
|
+
];
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Same array-wrapped-chained shape as
|
|
1551
|
+
* {@link buildMulticallSingleShotSearchDrillDownArrayWrappedChainedDependentActionSteps},
|
|
1552
|
+
* but the chain-produced value threaded from `r1` into `r2` is a bare JSON
|
|
1553
|
+
* NUMBER (`12345678`) rather than a string. Unlike the primary-item join
|
|
1554
|
+
* field covered by
|
|
1555
|
+
* {@link buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps},
|
|
1556
|
+
* a chain-produced value must first be indexed as a threadable STATE value by
|
|
1557
|
+
* `indexStateValues` before any array-wrap rendering question can even arise
|
|
1558
|
+
* — and `indexStateValues` walks response bodies via `walkStringLeaves`,
|
|
1559
|
+
* which yields string leaves only. A bare-number response leaf is therefore
|
|
1560
|
+
* never indexed and never appears in `compileActionSteps`' `produces[]` at
|
|
1561
|
+
* all, so `r2`'s templated body has no accessor to substitute and renders
|
|
1562
|
+
* `{"tokens":[undefined]}` — invalid JSON, thrown as a fetch failure. This is
|
|
1563
|
+
* a distinct, more fundamental gap than the array-wrap fix
|
|
1564
|
+
* (`applyStructuredValuePayloadSubstitutions`, bugfix-001) addresses: it sits
|
|
1565
|
+
* upstream, in state INDEXING, not in payload-substitution SPARING, and
|
|
1566
|
+
* reproduces identically whether or not the terminal body is array-wrapped.
|
|
1567
|
+
*/
|
|
1568
|
+
function buildMulticallSingleShotSearchDrillDownArrayWrappedNumericChainedJoinFieldActionSteps() {
|
|
1569
|
+
return [
|
|
1570
|
+
buildStep("r0", {
|
|
1571
|
+
url: CATALOG_SEARCH_URL,
|
|
1572
|
+
requestPostData: '{"page":1}',
|
|
1573
|
+
responseBody: {
|
|
1574
|
+
results: [{ orderId: "order-a" }, { orderId: "order-b" }],
|
|
1575
|
+
},
|
|
1576
|
+
timestamp: "2024-10-03T00:00:00Z",
|
|
1577
|
+
}),
|
|
1578
|
+
buildStep("r1", {
|
|
1579
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1580
|
+
requestPostData: '{"orderId":"order-a"}',
|
|
1581
|
+
responseBody: [12345678],
|
|
1582
|
+
timestamp: "2024-10-03T00:00:01Z",
|
|
1583
|
+
}),
|
|
1584
|
+
buildStep("r2", {
|
|
1585
|
+
url: ORDER_HISTORY_BULK_URL,
|
|
1586
|
+
requestPostData: '{"tokens":[12345678]}',
|
|
1587
|
+
responseBody: {
|
|
1588
|
+
entries: [{ statusToken: 12345678, ts: "2024-10-03T00:00:02Z", event: "shipped" }],
|
|
1589
|
+
},
|
|
1590
|
+
timestamp: "2024-10-03T00:00:02Z",
|
|
1591
|
+
}),
|
|
1592
|
+
];
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* A sibling of
|
|
1596
|
+
* {@link buildMulticallSingleShotSearchDrillDownArrayWrappedNumericChainedJoinFieldActionSteps}
|
|
1597
|
+
* isolating a DIFFERENT gap in the numeric chain-produced join field family:
|
|
1598
|
+
* `r1`'s produced token is a bare (non-array-wrapped) JSON number, and — the
|
|
1599
|
+
* shape that matters here — it is SHORT (`42`/`43`, two digits) rather than
|
|
1600
|
+
* an 8-digit token. `indexStateValues` gates every candidate state value on
|
|
1601
|
+
* `MIN_STATE_VALUE_LENGTH` (8 chars), a threshold sized to keep an unrelated
|
|
1602
|
+
* short STRING (an enum code, a page number) from colliding with arbitrary
|
|
1603
|
+
* substrings elsewhere; applied uniformly to a bare NUMBER's stringified
|
|
1604
|
+
* length, it also silently excludes a short numeric id/token — exactly the
|
|
1605
|
+
* common shape for an order/account/status id — from ever being indexed as
|
|
1606
|
+
* producible state. Without that index entry, `compileActionSteps` never
|
|
1607
|
+
* emits a `produces[]` accessor for `r1`'s response, so `r2`'s templated
|
|
1608
|
+
* body has nothing to substitute and falls back to treating `{"token":...}`
|
|
1609
|
+
* as an opaque caller-supplied payload field (`payload.token`) instead of
|
|
1610
|
+
* the per-item threaded value — every iteration would send whatever (or
|
|
1611
|
+
* nothing) the caller passed, not each primary item's own token.
|
|
1612
|
+
*/
|
|
1613
|
+
function buildMulticallSingleShotSearchDrillDownShortNumericChainedJoinFieldActionSteps() {
|
|
1614
|
+
return [
|
|
1615
|
+
buildStep("r0", {
|
|
1616
|
+
url: CATALOG_SEARCH_URL,
|
|
1617
|
+
requestPostData: '{"page":1}',
|
|
1618
|
+
responseBody: {
|
|
1619
|
+
results: [{ orderId: "order-a" }, { orderId: "order-b" }],
|
|
1620
|
+
},
|
|
1621
|
+
timestamp: "2024-10-04T00:00:00Z",
|
|
1622
|
+
}),
|
|
1623
|
+
buildStep("r1", {
|
|
1624
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1625
|
+
requestPostData: '{"orderId":"order-a"}',
|
|
1626
|
+
responseBody: [42],
|
|
1627
|
+
timestamp: "2024-10-04T00:00:01Z",
|
|
1628
|
+
}),
|
|
1629
|
+
buildStep("r2", {
|
|
1630
|
+
url: ORDER_HISTORY_URL,
|
|
1631
|
+
requestPostData: '{"token":42}',
|
|
1632
|
+
responseBody: {
|
|
1633
|
+
entries: [{ token: 42, ts: "2024-10-04T00:00:02Z", event: "shipped" }],
|
|
1634
|
+
},
|
|
1635
|
+
timestamp: "2024-10-04T00:00:02Z",
|
|
1636
|
+
}),
|
|
1637
|
+
];
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Cookie-origin sibling of
|
|
1641
|
+
* {@link buildMulticallSingleShotSearchDrillDownShortNumericChainedJoinFieldActionSteps}:
|
|
1642
|
+
* here `r1` mints the short chain-produced token (`tok1`, under
|
|
1643
|
+
* `MIN_STATE_VALUE_LENGTH`) via `Set-Cookie` rather than the response body,
|
|
1644
|
+
* AND echoes the same value in its response body (the "body-level echo" that
|
|
1645
|
+
* lets {@link collectDependentDrillDownChainValues} confirm the cookie value
|
|
1646
|
+
* is chain-produced, exactly as a body-sourced token would be). `r2` threads
|
|
1647
|
+
* `tok1` back via its request body. Exercises `indexStateValues`' Set-Cookie
|
|
1648
|
+
* branch's `chainForceIncludeValues`/`forceIncludeValues` exemption, mirroring
|
|
1649
|
+
* the body-value floor exemption.
|
|
1650
|
+
*/
|
|
1651
|
+
function buildMulticallSingleShotSearchDrillDownShortCookieChainedJoinFieldActionSteps() {
|
|
1652
|
+
return [
|
|
1653
|
+
buildStep("r0", {
|
|
1654
|
+
url: CATALOG_SEARCH_URL,
|
|
1655
|
+
requestPostData: '{"page":1}',
|
|
1656
|
+
responseBody: {
|
|
1657
|
+
results: [{ orderId: "order-a" }, { orderId: "order-b" }],
|
|
1658
|
+
},
|
|
1659
|
+
timestamp: "2024-10-05T00:00:00Z",
|
|
1660
|
+
}),
|
|
1661
|
+
buildStep("r1", {
|
|
1662
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1663
|
+
requestPostData: '{"orderId":"order-a"}',
|
|
1664
|
+
responseHeaders: { "set-cookie": "sess=tok1; Path=/; HttpOnly" },
|
|
1665
|
+
responseBody: { echoedToken: "tok1" },
|
|
1666
|
+
timestamp: "2024-10-05T00:00:01Z",
|
|
1667
|
+
}),
|
|
1668
|
+
buildStep("r2", {
|
|
1669
|
+
url: ORDER_HISTORY_URL,
|
|
1670
|
+
requestPostData: '{"token":"tok1"}',
|
|
1671
|
+
responseBody: {
|
|
1672
|
+
entries: [{ token: "tok1", ts: "2024-10-05T00:00:02Z", event: "shipped" }],
|
|
1673
|
+
},
|
|
1674
|
+
timestamp: "2024-10-05T00:00:02Z",
|
|
1675
|
+
}),
|
|
1676
|
+
];
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* A different sibling of the same array-wrapped-join-field failure family:
|
|
1680
|
+
* here it's the IMMEDIATE drill step (`r1`), not a later chain hop, whose
|
|
1681
|
+
* request body wraps the value it threads — but that value is the PRIMARY
|
|
1682
|
+
* ITEM's own join field (`orderId`), not a prior step's produced response
|
|
1683
|
+
* value. `applyStructuredValuePayloadSubstitutions`'s "spare a candidate
|
|
1684
|
+
* already threaded" exception only recognized prior-step state values, so it
|
|
1685
|
+
* still froze `r1`'s `{"orderIds":["order-a"]}` into an opaque
|
|
1686
|
+
* `${JSON.stringify(payload.orderIds)}` blob, destroying the literal
|
|
1687
|
+
* `"order-a"` text the fold-loop's own per-item `parameterize` pass (which
|
|
1688
|
+
* runs even later, once rendering enters the loop) needs to find and swap
|
|
1689
|
+
* for `${item.orderId}` — every iteration replayed the SAME captured order
|
|
1690
|
+
* instead of each primary item's own.
|
|
1691
|
+
*/
|
|
1692
|
+
function buildMulticallSingleShotSearchDrillDownArrayWrappedImmediateJoinFieldActionSteps() {
|
|
1693
|
+
return [
|
|
1694
|
+
buildStep("r0", {
|
|
1695
|
+
url: CATALOG_SEARCH_URL,
|
|
1696
|
+
requestPostData: '{"page":1}',
|
|
1697
|
+
responseBody: {
|
|
1698
|
+
results: [{ orderId: "order-a" }, { orderId: "order-b" }],
|
|
1699
|
+
},
|
|
1700
|
+
timestamp: "2024-10-03T00:00:00Z",
|
|
1701
|
+
}),
|
|
1702
|
+
buildStep("r1", {
|
|
1703
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1704
|
+
requestPostData: '{"orderIds":["order-a"]}',
|
|
1705
|
+
responseBody: ["status-token-order-a"],
|
|
1706
|
+
timestamp: "2024-10-03T00:00:01Z",
|
|
1707
|
+
}),
|
|
1708
|
+
buildStep("r2", {
|
|
1709
|
+
url: ORDER_HISTORY_BULK_URL,
|
|
1710
|
+
requestPostData: '{"tokens":["status-token-order-a"]}',
|
|
1711
|
+
responseBody: {
|
|
1712
|
+
entries: [
|
|
1713
|
+
{ statusToken: "status-token-order-a", ts: "2024-10-03T00:00:02Z", event: "shipped" },
|
|
1714
|
+
],
|
|
1715
|
+
},
|
|
1716
|
+
timestamp: "2024-10-03T00:00:02Z",
|
|
1717
|
+
}),
|
|
1718
|
+
];
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Same array-wrapped-immediate-join-field shape as
|
|
1722
|
+
* {@link buildMulticallSingleShotSearchDrillDownArrayWrappedImmediateJoinFieldActionSteps},
|
|
1723
|
+
* but the primary item's join field is a bare JSON NUMBER (`orderId: 101`)
|
|
1724
|
+
* rather than a string. `walkStringLeaves` (used by the spare check in
|
|
1725
|
+
* `applyStructuredValuePayloadSubstitutions`) silently skips number leaves,
|
|
1726
|
+
* so a numeric join value wrapped in `{"orderIds":[101]}` was frozen into an
|
|
1727
|
+
* opaque `${JSON.stringify(payload.orderIds)}` blob instead of being spared
|
|
1728
|
+
* for the fold-loop's later per-item `parameterize` pass.
|
|
1729
|
+
*/
|
|
1730
|
+
function buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps() {
|
|
1731
|
+
return [
|
|
1732
|
+
buildStep("r0", {
|
|
1733
|
+
url: CATALOG_SEARCH_URL,
|
|
1734
|
+
requestPostData: '{"page":1}',
|
|
1735
|
+
responseBody: {
|
|
1736
|
+
results: [{ orderId: 101 }, { orderId: 102 }],
|
|
1737
|
+
},
|
|
1738
|
+
timestamp: "2024-10-03T00:00:00Z",
|
|
1739
|
+
}),
|
|
1740
|
+
buildStep("r1", {
|
|
1741
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1742
|
+
requestPostData: '{"orderIds":[101]}',
|
|
1743
|
+
responseBody: ["status-token-101"],
|
|
1744
|
+
timestamp: "2024-10-03T00:00:01Z",
|
|
1745
|
+
}),
|
|
1746
|
+
buildStep("r2", {
|
|
1747
|
+
url: ORDER_HISTORY_BULK_URL,
|
|
1748
|
+
requestPostData: '{"tokens":["status-token-101"]}',
|
|
1749
|
+
responseBody: {
|
|
1750
|
+
entries: [
|
|
1751
|
+
{ statusToken: "status-token-101", ts: "2024-10-03T00:00:02Z", event: "shipped" },
|
|
1752
|
+
],
|
|
1753
|
+
},
|
|
1754
|
+
timestamp: "2024-10-03T00:00:02Z",
|
|
1755
|
+
}),
|
|
1756
|
+
];
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Same array-wrapped-immediate-join-field shape as
|
|
1760
|
+
* {@link buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps},
|
|
1761
|
+
* but the primary item's join field is a bare JSON BOOLEAN (`flag: true`)
|
|
1762
|
+
* rather than a number or string. `walkStringLeaves` (used by the spare
|
|
1763
|
+
* check in `applyStructuredValuePayloadSubstitutions`) silently skips
|
|
1764
|
+
* boolean leaves the same way it skips number leaves, so a boolean join
|
|
1765
|
+
* value wrapped in `{"flags":[true]}` was frozen into an opaque
|
|
1766
|
+
* `${JSON.stringify(payload.flags)}` blob instead of being spared for the
|
|
1767
|
+
* fold-loop's later per-item `parameterize` pass.
|
|
1768
|
+
*/
|
|
1769
|
+
function buildMulticallSingleShotSearchDrillDownArrayWrappedBooleanImmediateJoinFieldActionSteps() {
|
|
1770
|
+
return [
|
|
1771
|
+
buildStep("r0", {
|
|
1772
|
+
url: CATALOG_SEARCH_URL,
|
|
1773
|
+
requestPostData: '{"page":1}',
|
|
1774
|
+
responseBody: {
|
|
1775
|
+
results: [{ flag: true }, { flag: false }],
|
|
1776
|
+
},
|
|
1777
|
+
timestamp: "2024-10-03T00:00:00Z",
|
|
1778
|
+
}),
|
|
1779
|
+
buildStep("r1", {
|
|
1780
|
+
url: CATALOG_ORDER_STATUS_URL,
|
|
1781
|
+
requestPostData: '{"flags":[true]}',
|
|
1782
|
+
responseBody: ["status-token-true"],
|
|
1783
|
+
timestamp: "2024-10-03T00:00:01Z",
|
|
1784
|
+
}),
|
|
1785
|
+
buildStep("r2", {
|
|
1786
|
+
url: ORDER_HISTORY_BULK_URL,
|
|
1787
|
+
requestPostData: '{"tokens":["status-token-true"]}',
|
|
1788
|
+
responseBody: {
|
|
1789
|
+
entries: [
|
|
1790
|
+
{ statusToken: "status-token-true", ts: "2024-10-03T00:00:02Z", event: "shipped" },
|
|
1791
|
+
],
|
|
1792
|
+
},
|
|
1793
|
+
timestamp: "2024-10-03T00:00:02Z",
|
|
1794
|
+
}),
|
|
1795
|
+
];
|
|
1796
|
+
}
|
|
1797
|
+
const ACCOUNT_STATUS_URL = "https://api.example.com/accounts/status";
|
|
1798
|
+
const ACCOUNT_TRANSACTIONS_URL = "https://api.example.com/accounts/transactions";
|
|
1799
|
+
/**
|
|
1800
|
+
* A dependent (chained) drill-down whose join key is threaded ONLY through a
|
|
1801
|
+
* request HEADER (`API-Token`) on the chain's ENTRY hop (`r1`) — invisible to
|
|
1802
|
+
* `detectDrillDownFoldPlan`'s structural heuristic exactly like
|
|
1803
|
+
* {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}
|
|
1804
|
+
* — so only a flow-declared `foldReturn` can resolve this fold at all. Unlike
|
|
1805
|
+
* every existing header-threaded-join fixture, though, a site author writing
|
|
1806
|
+
* that `foldReturn` naturally points `endpointPattern` at `r2`
|
|
1807
|
+
* (`/accounts/transactions`) — the call whose OWN response actually carries
|
|
1808
|
+
* the per-item data (`transactions[]`) they want folded — not at `r1`
|
|
1809
|
+
* (`/accounts/status`), the opaque intermediate hop that merely carries the
|
|
1810
|
+
* `accountId`→`API-Token` header thread onward as a `statusToken`. `r2`'s own
|
|
1811
|
+
* request only ever carries that threaded `statusToken`, never `accountId`
|
|
1812
|
+
* in any form (body, URL, or header) — the join key is resolvable only by
|
|
1813
|
+
* walking back to `r1`, exactly the way `computeFoldChain` already walks
|
|
1814
|
+
* FORWARD from a resolved entry hop to a richer terminal. `resultsPath`
|
|
1815
|
+
* anchors on `accounts`, `joinFields` on `accountId`.
|
|
1816
|
+
*/
|
|
1817
|
+
function buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinChainedDependentActionSteps() {
|
|
1818
|
+
return [
|
|
1819
|
+
buildStep("r0", {
|
|
1820
|
+
url: ACCOUNT_SEARCH_URL,
|
|
1821
|
+
requestPostData: JSON.stringify({ page: 1 }),
|
|
1822
|
+
responseBody: {
|
|
1823
|
+
accounts: [
|
|
1824
|
+
{ accountId: 42, name: "Acme" },
|
|
1825
|
+
{ accountId: 43, name: "Globex" },
|
|
1826
|
+
],
|
|
1827
|
+
},
|
|
1828
|
+
timestamp: "2024-08-01T00:00:00Z",
|
|
1829
|
+
}),
|
|
1830
|
+
buildStep("r1", {
|
|
1831
|
+
url: ACCOUNT_STATUS_URL,
|
|
1832
|
+
requestPostData: null,
|
|
1833
|
+
requestHeaders: { "Content-Type": "application/json", "API-Token": "42" },
|
|
1834
|
+
responseBody: { statusToken: "status-token-42" },
|
|
1835
|
+
timestamp: "2024-08-01T00:00:01Z",
|
|
1836
|
+
}),
|
|
1837
|
+
buildStep("r2", {
|
|
1838
|
+
url: ACCOUNT_TRANSACTIONS_URL,
|
|
1839
|
+
requestPostData: JSON.stringify({ statusToken: "status-token-42" }),
|
|
1840
|
+
responseBody: {
|
|
1841
|
+
transactions: [{ statusToken: "status-token-42", transactionId: "t-42", amount: 19.99 }],
|
|
1842
|
+
},
|
|
1843
|
+
timestamp: "2024-08-01T00:00:02Z",
|
|
1844
|
+
}),
|
|
1845
|
+
];
|
|
1846
|
+
}
|
|
1847
|
+
const ORDER_STATUS_LOOKUP_URL = "https://api.example.com/orders/status-lookup";
|
|
1848
|
+
const ORDER_EVENTS_URL = "https://api.example.com/orders/events";
|
|
1849
|
+
/**
|
|
1850
|
+
* A dependent (chained) drill-down whose ENTRY hop (`r1`) is a `GET`
|
|
1851
|
+
* request — not the `POST` every other chained-dependent fixture in this
|
|
1852
|
+
* file uses — and whose response produces the chain's join value
|
|
1853
|
+
* (`statusToken`) as a non-UUID string. `indexStateValues` indexes a `GET`
|
|
1854
|
+
* capture's response leaves ONLY when they are UUID-shaped
|
|
1855
|
+
* (`isGet && !UUID_REGEX.test(value)` skips everything else, a filter aimed
|
|
1856
|
+
* at excluding noisy short non-UUID strings a GET-only telemetry/schema
|
|
1857
|
+
* fetch surfaces); that filter runs unconditionally, with no exemption for a
|
|
1858
|
+
* value `collectDependentDrillDownChainValues` has already confirmed is
|
|
1859
|
+
* threaded from this exact hop into a later chain hop's own request — unlike
|
|
1860
|
+
* the `MIN_STATE_VALUE_LENGTH` floor a few lines above it, which DOES carry
|
|
1861
|
+
* that exemption. So a chain-produced token minted by a GET response is
|
|
1862
|
+
* never indexed as producible state at all, `compileActionSteps` never
|
|
1863
|
+
* emits a `produces[]` accessor for it, and `r2`'s templated body has
|
|
1864
|
+
* nothing to substitute — every iteration renders the literal string
|
|
1865
|
+
* `"undefined"` instead of threading each primary item's own token.
|
|
1866
|
+
*/
|
|
1867
|
+
function buildMulticallSingleShotSearchDrillDownGetEntryHopChainedDependentActionSteps() {
|
|
1868
|
+
return [
|
|
1869
|
+
buildStep("r0", {
|
|
1870
|
+
url: CATALOG_SEARCH_URL,
|
|
1871
|
+
requestPostData: '{"page":1}',
|
|
1872
|
+
responseBody: {
|
|
1873
|
+
results: [{ orderId: "order-a" }, { orderId: "order-b" }],
|
|
1874
|
+
},
|
|
1875
|
+
timestamp: "2024-10-05T00:00:00Z",
|
|
1876
|
+
}),
|
|
1877
|
+
buildStep("r1", {
|
|
1878
|
+
method: "GET",
|
|
1879
|
+
url: `${ORDER_STATUS_LOOKUP_URL}?orderId=order-a`,
|
|
1880
|
+
requestPostData: null,
|
|
1881
|
+
responseBody: { statusToken: "status-token-order-a" },
|
|
1882
|
+
timestamp: "2024-10-05T00:00:01Z",
|
|
1883
|
+
}),
|
|
1884
|
+
buildStep("r2", {
|
|
1885
|
+
url: ORDER_EVENTS_URL,
|
|
1886
|
+
requestPostData: '{"token":"status-token-order-a"}',
|
|
1887
|
+
responseBody: {
|
|
1888
|
+
entries: [{ token: "status-token-order-a", ts: "2024-10-05T00:00:02Z", event: "shipped" }],
|
|
1889
|
+
},
|
|
1890
|
+
timestamp: "2024-10-05T00:00:02Z",
|
|
1891
|
+
}),
|
|
1892
|
+
];
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* A response-header sibling of {@link buildMulticallSingleShotSearchDrillDownGetEntryHopChainedDependentActionSteps}:
|
|
1896
|
+
* the chain's drill hop (`r1`) mints its join token (`tok-a1`) ONLY in a
|
|
1897
|
+
* custom response HEADER (`X-Price-Token`) — its body is empty ({}), unlike
|
|
1898
|
+
* {@link buildMulticallSingleShotSearchDrillDownHeaderMintedChainedResponseValueActionSteps}'s
|
|
1899
|
+
* body-echo-free sibling, which threads the header value forward via the
|
|
1900
|
+
* request BODY. Here the terminal hop (`r2`) instead threads that value back
|
|
1901
|
+
* as a REQUEST HEADER of its own (also named `X-Price-Token`) — the only
|
|
1902
|
+
* shape `createHttpClient`'s `bind` option can actually thread a
|
|
1903
|
+
* header-origin value into, since the emitted response variable never
|
|
1904
|
+
* exposes response headers to the rest of the generated code. Exercises
|
|
1905
|
+
* `compileActionSteps`' non-cookie response-header produce block: without it,
|
|
1906
|
+
* `tok-a1` is never captured into a `produces[]`/`bind` entry at all, and
|
|
1907
|
+
* `r2`'s per-item call goes out with no `X-Price-Token` header, so the
|
|
1908
|
+
* terminal `history[]` fold silently collapses to whichever primary item the
|
|
1909
|
+
* stub happens to answer first instead of each item's own token.
|
|
1910
|
+
*/
|
|
1911
|
+
function buildMulticallSingleShotSearchDrillDownResponseHeaderThreadedJoinChainedDependentActionSteps() {
|
|
1912
|
+
return [
|
|
1913
|
+
buildStep("r0", {
|
|
1914
|
+
url: CATALOG_SEARCH_URL,
|
|
1915
|
+
requestPostData: '{"page":1}',
|
|
1916
|
+
responseBody: {
|
|
1917
|
+
results: [{ sku: "sku-a" }, { sku: "sku-b" }],
|
|
1918
|
+
},
|
|
1919
|
+
timestamp: "2024-12-10T00:00:00Z",
|
|
1920
|
+
}),
|
|
1921
|
+
buildStep("r1", {
|
|
1922
|
+
url: CATALOG_PRICING_URL,
|
|
1923
|
+
requestPostData: '{"sku":"sku-a"}',
|
|
1924
|
+
responseBody: {},
|
|
1925
|
+
responseHeaders: { "content-type": "application/json", "X-Price-Token": "tok-a1" },
|
|
1926
|
+
timestamp: "2024-12-10T00:00:01Z",
|
|
1927
|
+
}),
|
|
1928
|
+
buildStep("r2", {
|
|
1929
|
+
url: CATALOG_PRICE_HISTORY_URL,
|
|
1930
|
+
requestPostData: "{}",
|
|
1931
|
+
requestHeaders: { "Content-Type": "application/json", "X-Price-Token": "tok-a1" },
|
|
1932
|
+
responseBody: {
|
|
1933
|
+
history: [{ sku: "sku-a", amount: 18.5, asOf: "2024-11-01" }],
|
|
1934
|
+
},
|
|
1935
|
+
timestamp: "2024-12-10T00:00:02Z",
|
|
1936
|
+
}),
|
|
1937
|
+
];
|
|
1938
|
+
}
|
|
149
1939
|
//# sourceMappingURL=recon-generate-multicall-fixture.js.map
|