You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On the Node adapter, ServerRequest lazily materializes a native (undici) Request, exposed as req._request. The native constructor copies the headers into its own Headers instance, so after materialization two header stores exist: the srvx-side NodeRequestHeaders wrapper (#headers) and the native request's private copy.
Whichever one get headers() returns, the other can go stale:
Current behavior (main, after the revert): once _request materializes, get headers() switches to #request.headers. A reference taken earlier becomes detached — req.headers !== h, and mutations through h are silently invisible:
consth=req.headers;voidreq._request;// any native method (clone/blob/bytes/formData) triggers this tooh.set("x-foo","1");// lost
Approach tried in fix(node): Node adapter correctness fixes (statusText, empty body, HEAD streaming, send errors, sync bridge) #243 (reverted in 1754b02): keep #headers canonical forever. This fixes identity/liveness of early references, but freezes req._request.headers at the materialization-time snapshot — mutations made after materialization show up in req.headers but not in _request.headers, and therefore not in clone() or formData() (which sniffs content-type from the native request).
Either way one view diverges; the question is which contract to commit to for v1, since _request is supported API.
Options
Keep current behavior and document that header references must be re-read after _request materialization (mutate-after-materialize stays broken for early refs).
srvx-side canonical (the reverted approach) and document that _request.headers / clone() are a snapshot as of materialization.
Write-through: keep #headers canonical for reads/identity, but mirror set/append/delete into #request.headers once materialized. Keeps both views consistent at the cost of a dual-write on mutation paths (and needs care around undici's guard semantics).
Context
On the Node adapter,
ServerRequestlazily materializes a native (undici)Request, exposed asreq._request. The native constructor copies the headers into its ownHeadersinstance, so after materialization two header stores exist: the srvx-sideNodeRequestHeaderswrapper (#headers) and the native request's private copy.Whichever one
get headers()returns, the other can go stale:main, after the revert): once_requestmaterializes,get headers()switches to#request.headers. A reference taken earlier becomes detached —req.headers !== h, and mutations throughhare silently invisible:#headerscanonical forever. This fixes identity/liveness of early references, but freezesreq._request.headersat the materialization-time snapshot — mutations made after materialization show up inreq.headersbut not in_request.headers, and therefore not inclone()orformData()(which sniffscontent-typefrom the native request).Either way one view diverges; the question is which contract to commit to for v1, since
_requestis supported API.Options
_requestmaterialization (mutate-after-materialize stays broken for early refs)._request.headers/clone()are a snapshot as of materialization.#headerscanonical for reads/identity, but mirrorset/append/deleteinto#request.headersonce materialized. Keeps both views consistent at the cost of a dual-write on mutation paths (and needs care around undici's guard semantics).References
test("headers reference stays live across _request materialization")intest/node-adapters.test.ts(see 1754b02^).