Found while auditing the Node adapter (dist/adapters/node.mjs, Node 24.18) for v1 stable.
NodeRequest#text() and #json() correctly guard against double reads, but arrayBuffer(), bytes(), blob() and formData() resolve successfully with an empty body when the body was already consumed, instead of rejecting like native fetch.
Repro
import { serve } from "srvx/node";
serve({
async fetch(req) {
await req.text();
const buf = await req.arrayBuffer(); // native: rejects "Body is unusable"
return new Response(`resolved, byteLength=${buf.byteLength}, bodyUsed=${req.bodyUsed}`);
},
});
$ curl -X POST --data hi http://localhost:3000
resolved, byteLength=0, bodyUsed=true
Native comparison (undici):
const req = new Request("http://localhost/", { method: "POST", body: "hi" });
await req.text();
await req.arrayBuffer(); // TypeError: Body is unusable: Body has already been read
Root cause
Only text()/json() have explicit #bodyUsed guards. The other body methods are delegated through lazyInherit to the lazily-created native request, and the _request getter builds that request with body: null once #bodyUsed is set:
https://github.com/h3js/srvx/blob/main/src/adapters/_node/request.ts#L291
So the native request is a fresh empty-bodied request and every consumer on it resolves with an empty result. This silently masks double-read bugs in user code that would throw on every other runtime/adapter.
Related (same accounting, lower impact)
Consuming the body stream directly (req.body.getReader() / iterating req.body) never flips bodyUsed to true — natively the flag turns true as soon as the stream is disturbed.
Found while auditing the Node adapter (
dist/adapters/node.mjs, Node 24.18) for v1 stable.NodeRequest#text()and#json()correctly guard against double reads, butarrayBuffer(),bytes(),blob()andformData()resolve successfully with an empty body when the body was already consumed, instead of rejecting like native fetch.Repro
Native comparison (undici):
Root cause
Only
text()/json()have explicit#bodyUsedguards. The other body methods are delegated throughlazyInheritto the lazily-created native request, and the_requestgetter builds that request withbody: nullonce#bodyUsedis set:https://github.com/h3js/srvx/blob/main/src/adapters/_node/request.ts#L291
So the native request is a fresh empty-bodied request and every consumer on it resolves with an empty result. This silently masks double-read bugs in user code that would throw on every other runtime/adapter.
Related (same accounting, lower impact)
Consuming the body stream directly (
req.body.getReader()/ iteratingreq.body) never flipsbodyUsedtotrue— natively the flag turns true as soon as the stream is disturbed.