Summary
Perry implements Response.json(data), but the current static factory ignores the optional init argument and uses a non-Node default statusText.
Node applies init.status, init.statusText, and init.headers, validates invalid status/statusText values, preserves a caller-provided content-type, and defaults statusText to the empty string. Perry's origin/main lowering passes only the JSON value to the runtime helper, and the helper always creates a 200 OK response with only content-type: application/json.
Expected Node behavior
Local Node v25.9.0 probe:
const base = Response.json({ a: 1 });
console.log(base.status, JSON.stringify(base.statusText), base.headers.get("content-type"), await base.text());
const r = Response.json(
{ ok: true },
{
status: 201,
statusText: "Created-ish",
headers: { "x-test": "yes", "content-type": "application/custom" },
},
);
console.log(r.status, JSON.stringify(r.statusText), r.ok);
console.log(r.headers.get("content-type"), r.headers.get("x-test"));
console.log(await r.text());
for (const init of [{ status: 99 }, { status: 600 }, { statusText: "bad\nline" }]) {
try {
Response.json({ a: 1 }, init);
} catch (e) {
console.log(JSON.stringify(init), e.name, e.message.split("\n")[0]);
}
}
Output:
200 "" application/json {"a":1}
201 "Created-ish" true
application/custom yes
{"ok":true}
{"status":99} RangeError init["status"] must be in the range of 200 to 599, inclusive.
{"status":600} RangeError init["status"] must be in the range of 200 to 599, inclusive.
{"statusText":"bad\nline"} TypeError Invalid statusText
Current Perry evidence
From origin/main:
docs/runtime-parity.md marks Response.json(data, init?) covered, while docs/runtime-parity-gaps.md still lists it as a Web-global gap.
crates/perry-hir/src/lower/expr_call/module_static.rs lowers Response.json into a static Fetch factory call but does not distinguish the init semantics.
crates/perry-codegen/src/lower_call/options/fetch.rs handles static_json by lowering only args[0] and calling js_response_static_json(value) with a single argument.
crates/perry-ext-fetch/src/lib.rs::js_response_static_json() always stores status: 200, status_text: "OK", and a fresh content-type: application/json header map.
test-files/test_gap_fetch_response.ts checks only body JSON and content-type for the one-argument form, so it misses init handling and the default statusText mismatch.
Suggested test surface
Add parity fixtures that verify:
Response.json(data).statusText === "" and default content-type is application/json.
Response.json(data, { status, statusText, headers }) applies status, status text, and custom headers.
- caller-provided
content-type is preserved rather than overwritten.
- invalid status values outside
200..599 throw RangeError.
- invalid
statusText characters throw TypeError.
Duplicate search notes
Before filing I searched issues and PRs for Response.json init status headers, Response.json content-type statusText, and Response.json init. I found no existing issue or PR covering these Response.json() init semantics. This is separate from #2608's globalThis.Response binding issue, #2634's missing Response.error(), and #2637's Response.redirect() validation/statusText behavior.
Summary
Perry implements
Response.json(data), but the current static factory ignores the optionalinitargument and uses a non-Node defaultstatusText.Node applies
init.status,init.statusText, andinit.headers, validates invalid status/statusText values, preserves a caller-providedcontent-type, and defaultsstatusTextto the empty string. Perry'sorigin/mainlowering passes only the JSON value to the runtime helper, and the helper always creates a200 OKresponse with onlycontent-type: application/json.Expected Node behavior
Local Node v25.9.0 probe:
Output:
Current Perry evidence
From
origin/main:docs/runtime-parity.mdmarksResponse.json(data, init?)covered, whiledocs/runtime-parity-gaps.mdstill lists it as a Web-global gap.crates/perry-hir/src/lower/expr_call/module_static.rslowersResponse.jsoninto a static Fetch factory call but does not distinguish theinitsemantics.crates/perry-codegen/src/lower_call/options/fetch.rshandlesstatic_jsonby lowering onlyargs[0]and callingjs_response_static_json(value)with a single argument.crates/perry-ext-fetch/src/lib.rs::js_response_static_json()always storesstatus: 200,status_text: "OK", and a freshcontent-type: application/jsonheader map.test-files/test_gap_fetch_response.tschecks only body JSON and content-type for the one-argument form, so it missesinithandling and the defaultstatusTextmismatch.Suggested test surface
Add parity fixtures that verify:
Response.json(data).statusText === ""and defaultcontent-typeisapplication/json.Response.json(data, { status, statusText, headers })applies status, status text, and custom headers.content-typeis preserved rather than overwritten.200..599throwRangeError.statusTextcharacters throwTypeError.Duplicate search notes
Before filing I searched issues and PRs for
Response.json init status headers,Response.json content-type statusText, andResponse.json init. I found no existing issue or PR covering theseResponse.json()init semantics. This is separate from #2608'sglobalThis.Responsebinding issue, #2634's missingResponse.error(), and #2637'sResponse.redirect()validation/statusText behavior.