mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
fix(javascript): keep the request endpoint on a uniquely consumed response (#3822)
* fix(javascript): keep the request endpoint on a uniquely consumed response collectResponseConsumers always produced response-consumer operations with endpoint null, so a json()/text() consumer that resolved to exactly one request lost the request's literal endpoint in the semantic graph. When the consumer resolves to one request through that request's own result binding, the same condition that already makes it complete, it now carries that request's endpoint. Ambiguous consumers stay null. Fixes #3328 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(javascript): drop the endpoint when another write can replace the response Copy the request's endpoint only when the request is the binding's sole write; any other assignment, update, loop or destructuring target can replace the response before the consumer reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
|
||||
|
||||
const responses = (source: string) =>
|
||||
analyzeJavaScriptSemantics(source)
|
||||
.requestOperations.filter(({ kind }) => kind === "response-consumer")
|
||||
.map(({ method, endpoint, resolution }) => ({
|
||||
method,
|
||||
endpoint,
|
||||
resolution,
|
||||
}));
|
||||
|
||||
it("keeps the endpoint of a uniquely consumed request response", () => {
|
||||
expect(
|
||||
responses(`
|
||||
async function load() {
|
||||
const a = await fetch("https://example.test/a");
|
||||
const b = await fetch("https://example.test/b");
|
||||
return [await a.json(), await b.text()];
|
||||
}
|
||||
`),
|
||||
).toEqual([
|
||||
{
|
||||
method: "json",
|
||||
endpoint: "https://example.test/a",
|
||||
resolution: "complete",
|
||||
},
|
||||
{
|
||||
method: "text",
|
||||
endpoint: "https://example.test/b",
|
||||
resolution: "complete",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("leaves an ambiguous response consumer without an endpoint", () => {
|
||||
expect(
|
||||
responses(`
|
||||
async function load(flag) {
|
||||
let response = await fetch("https://example.test/a");
|
||||
if (flag) response = await fetch("https://example.test/b");
|
||||
return response.json();
|
||||
}
|
||||
`),
|
||||
).toEqual([{ method: "json", endpoint: null, resolution: "partial" }]);
|
||||
});
|
||||
|
||||
it("leaves a response replaced by a non-request value without an endpoint", () => {
|
||||
expect(
|
||||
responses(`
|
||||
async function load(cachedResponse) {
|
||||
let response = await fetch("https://example.test/a");
|
||||
response = cachedResponse;
|
||||
return response.json();
|
||||
}
|
||||
`),
|
||||
).toEqual([expect.objectContaining({ method: "json", endpoint: null })]);
|
||||
});
|
||||
@@ -270,6 +270,18 @@ const collectResponseConsumers = (
|
||||
? []
|
||||
: (requestsByBinding.get(binding.bindingId) ?? []);
|
||||
if (linked.length === 0) return;
|
||||
// A consumer of exactly one request through its own result binding
|
||||
// reads that request's response, so it keeps the request's endpoint.
|
||||
const uniqueRequest =
|
||||
linked.length === 1 &&
|
||||
!linked[0]?.operation.projectedResultBindings.some(
|
||||
({ bindingId }) => bindingId === binding?.bindingId,
|
||||
)
|
||||
? linked[0]
|
||||
: undefined;
|
||||
// Any other write (assignment, update, loop or destructuring target) can
|
||||
// replace the response before this read, so its endpoint stays unknown.
|
||||
const soleWrite = binding?.initializers.length === 1;
|
||||
output.push({
|
||||
requestId: `response:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
|
||||
kind: "response-consumer",
|
||||
@@ -287,15 +299,9 @@ const collectResponseConsumers = (
|
||||
linkedRequestIds: linked
|
||||
.map(({ operation }) => operation.requestId)
|
||||
.sort(compareUnicodeCodePoints),
|
||||
endpoint: null,
|
||||
endpoint: soleWrite ? (uniqueRequest?.operation.endpoint ?? null) : null,
|
||||
fields: [],
|
||||
resolution:
|
||||
linked.length === 1 &&
|
||||
!linked[0]?.operation.projectedResultBindings.some(
|
||||
({ bindingId }) => bindingId === binding?.bindingId,
|
||||
)
|
||||
? "complete"
|
||||
: "partial",
|
||||
resolution: uniqueRequest === undefined ? "partial" : "complete",
|
||||
});
|
||||
});
|
||||
return output;
|
||||
|
||||
Reference in New Issue
Block a user