Skip to content

Pre-stable validation: smaller findings (response edge cases, type/export drift, deprecated typings) #1482

Description

@pi0x

Umbrella for the remaining smaller findings from the pre-stable validation pass (all verified against main @ 75ac47d). Larger items are tracked separately: #1477 (response preparation), #1478 (SSE disconnect), #1479 (non-Error throws), #1481 (!val.ok header drop). The double-readBody Node hang is being addressed upstream in srvx.

Response handling

  • HEAD body stripping misses the in-place merge pathsrc/response.ts:129-131: only the fast path (:120) and the immutable-headers catch path (:134) strip HEAD bodies. When prepared headers merge in place into a mutable 2xx Response, the body is returned intact — app.fetch/web-mode/service-worker consumers see a HEAD response with a body (Node strips it at the socket), contradicting the "keep self-consistent" intent at :119.
  • kHandled ignores event.res statesrc/response.ts:62-63: returns a bare FastResponse(null) before preparedRes is read or kEventRes cleared. A handler that sets event.res.status = 201 + headers then returns kHandled produces a 200 with no headers, and onResponse hooks observe a synthetic 200 that doesn't match what was actually sent. we can't alter node.js responses in reality
  • Throwing onResponse hook fails the request rawsrc/response.ts:30: Promise.resolve(onResponse(...)).then(() => response) has no rejection handling, so a hook that throws turns the request into an unhandled rejection and onError is never invoked. If intentional (avoiding hook recursion), it's undocumented.

Exports / types drift

  • kNotFound / kHandled not exported from src/index.ts they're part of the documented handler return contract (AGENTS.md), but users can only reach them via Symbol.for("h3.notFound"). Either export them or. fix the agents.md docs
  • Declared H3Core type is missing "~dispatch" and the static "~h3" brandsrc/types/h3.ts:102 gained "~composed" in perf(middleware): precompose middleware chains #1475 but not "~dispatch" (src/h3.ts:50), and the exported H3 is cast to the declared type, so TS consumers (e.g. nitro) can't see or invalidate the dispatch cache the way use()/mount() do. The static "~h3" brand (src/h3.ts:42, used by src/handler.ts for instance detection) is also absent from the declared class.
  • event.context.clientAddress can be silently ignoredsrc/types/context.ts:27 declares it, but the only reader is getRequestIP at src/utils/request.ts:542, which reads event.req.context?.clientAddress — a different object from event.context when a context is passed explicitly or req.context is unset (H3Event ctor falls back to a fresh object, src/event.ts:83).
  • StaticAssetMeta.path is typed but never read by serveStaticsrc/utils/static.ts:13.

Deprecated compat (src/_deprecated.ts)

  • appendResponseHeaders param typed string but iterated as a record:245 does Object.entries(headers) over a headers: string param; should be Record<string, string> (v1 signature). The appendHeaders alias at :252 inherits the wrong type.
  • sendRedirect requires code:133: v1 defaulted to 302 (and the delegated redirect() has status = 302), so code should be optional.

Docs-level

  • bodyLimit/assertBodySize buffers before next()src/utils/body.ts:224-234 drains a req.clone() (up to the limit) before the handler runs, so streaming handlers behind bodyLimit() can't start until the whole body has arrived and is buffered. Enforcement itself is correct (no chunked-encoding bypass; content-length + transfer-encoding rejected with 400) — but the latency/memory behavior deserves a doc note, or eventually a streaming enforcement mode.
  • AGENTS.md says the node entry "adds serve()" — every platform entry exports serve; only toNodeHandler is node-specific.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions