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
RFC 10008 defines the HTTP QUERY method: a request that carries a body (like POST) but is safe, idempotent, and cacheable (like GET). It closes the long-standing "GET with a body" gap — clients send query criteria in the request body with a Content-Type, and the server treats the request as a read. The RFC also introduces the Accept-Query response header (a Structured Fields List) that advertises which query media types a resource accepts.
Now that this is a Proposed Standard, h3 should treat QUERY as a first-class method alongside the others.
Proposal
1. First-class QUERY method (core)
Add "QUERY" to the HTTPMethod union (src/types/h3.ts).
Add "QUERY" to the method loop in src/h3.ts so app.query(route, handler, opts) and app.on("QUERY", …) work.
defineRoute picks up the new method automatically via the HTTPMethod type.
rou3 matches on the method string, so routing needs no change — confirm with tests.
2. Body handling on a read method
Verify readBody / readValidatedBody don't short-circuit on non-POST/PUT/PATCH methods (they key off the request, so likely fine).
Ensure body-size limits (assertBodySize / bodyLimit) apply to QUERY, since the body is attacker-controlled.
3. Accept-Query — header utilities only (no core change)
h3 core does not introspect the route table to emit Allow/OPTIONS today (that logic doesn't exist in the request path — Allow is only ever built manually by utilities like assertMethod and serveStatic). So auto-deriving Accept-Query from registered routes would mean adding net-new core surface (route-table introspection + an OPTIONS/405 responder). We want to avoid that.
Instead, ship a symmetric header pair in src/utils/, mirroring getCookie/setCookie:
setAcceptQuery(event, mediaTypes) — serializes to the Accept-Query response header (Structured Fields List, e.g. application/sql;charset="UTF-8", application/jsonpath).
getAcceptQuery(event) — parses the header back into string[]. Reads the response header set wrote (the header is the single source of truth).
No route introspection, no pipeline hook. "Auto-derived" ergonomics remain possible in userland by calling setAcceptQuery from an OPTIONS handler; core stays untouched.
4. Content-Type validation — opt-in helper
The RFC mandates 400 (missing Content-Type), 415 (unsupported media type), and 422 (well-formed but unprocessable). Provide an opt-in helper (e.g. requireContentType(event, acceptedTypes)) that throws the appropriate HTTPError. Not enforced in core — keeps the core minimal.
5. Audits (parity with GET/POST)
CORS: QUERY is not CORS-safelisted, so browsers preflight it. h3's CORS is already method-agnostic (methods: "*"), so no logic change expected — confirm the preflight path and update docs/examples.
Caching: handleCacheHeaders should accept QUERY like GET for If-None-Match / If-Modified-Since / 304. Audit for GET/HEAD-only guards.
Proxy: confirm proxy / proxyRequest forward QUERYwith its body (some proxy paths strip bodies for GET/HEAD — don't lump QUERY in).
Runtimes: verify each adapter + srvx pass a non-standard method token through (add a describeMatrix test hitting a QUERY route in both web and node modes).
Decisions
✅ Add app.query() as a first-class method.
✅ Content-Type validation is opt-in via a helper (not enforced in core).
✅ Accept-Query is handled by a setAcceptQuery / getAcceptQuery utility pair (no core route-table introspection). get reads back what set wrote.
Suggested sequencing (small PRs)
Core method support — type + route generation + matrix tests.
Note
This proposal was drafted with AI assistance
Support the HTTP
QUERYmethod (RFC 10008)RFC 10008 defines the HTTP
QUERYmethod: a request that carries a body (likePOST) but is safe, idempotent, and cacheable (likeGET). It closes the long-standing "GET with a body" gap — clients send query criteria in the request body with aContent-Type, and the server treats the request as a read. The RFC also introduces theAccept-Queryresponse header (a Structured Fields List) that advertises which query media types a resource accepts.Now that this is a Proposed Standard, h3 should treat
QUERYas a first-class method alongside the others.Proposal
1. First-class
QUERYmethod (core)"QUERY"to theHTTPMethodunion (src/types/h3.ts)."QUERY"to the method loop insrc/h3.tssoapp.query(route, handler, opts)andapp.on("QUERY", …)work.defineRoutepicks up the new method automatically via theHTTPMethodtype.2. Body handling on a read method
readBody/readValidatedBodydon't short-circuit on non-POST/PUT/PATCHmethods (they key off the request, so likely fine).assertBodySize/bodyLimit) apply toQUERY, since the body is attacker-controlled.3.
Accept-Query— header utilities only (no core change)h3 core does not introspect the route table to emit
Allow/OPTIONStoday (that logic doesn't exist in the request path —Allowis only ever built manually by utilities likeassertMethodandserveStatic). So auto-derivingAccept-Queryfrom registered routes would mean adding net-new core surface (route-table introspection + an OPTIONS/405 responder). We want to avoid that.Instead, ship a symmetric header pair in
src/utils/, mirroringgetCookie/setCookie:setAcceptQuery(event, mediaTypes)— serializes to theAccept-Queryresponse header (Structured Fields List, e.g.application/sql;charset="UTF-8", application/jsonpath).getAcceptQuery(event)— parses the header back intostring[]. Reads the response headersetwrote (the header is the single source of truth).No route introspection, no pipeline hook. "Auto-derived" ergonomics remain possible in userland by calling
setAcceptQueryfrom an OPTIONS handler; core stays untouched.4. Content-Type validation — opt-in helper
400(missingContent-Type),415(unsupported media type), and422(well-formed but unprocessable). Provide an opt-in helper (e.g.requireContentType(event, acceptedTypes)) that throws the appropriateHTTPError. Not enforced in core — keeps the core minimal.5. Audits (parity with GET/POST)
QUERYis not CORS-safelisted, so browsers preflight it. h3's CORS is already method-agnostic (methods: "*"), so no logic change expected — confirm the preflight path and update docs/examples.handleCacheHeadersshould acceptQUERYlikeGETforIf-None-Match/If-Modified-Since/304. Audit for GET/HEAD-only guards.proxy/proxyRequestforwardQUERYwith its body (some proxy paths strip bodies for GET/HEAD — don't lumpQUERYin).srvxpass a non-standard method token through (add adescribeMatrixtest hitting aQUERYroute in bothwebandnodemodes).Decisions
app.query()as a first-class method.Accept-Queryis handled by asetAcceptQuery/getAcceptQueryutility pair (no core route-table introspection).getreads back whatsetwrote.Suggested sequencing (small PRs)
setAcceptQuery/getAcceptQueryutilities.handleCacheHeaders,proxy,bodyLimitparity + regression tests.Progress checklist
PR 1 — Core method support (#1445 ✅ approved)
"QUERY"added toHTTPMethodunion (src/types/h3.ts)app.query()+app.on("QUERY", …)via method loop (src/h3.ts)defineRoutepicks upQUERYviaHTTPMethodreadBody/readValidatedBodywork onQUERY(not method-gated) — testedassertBodySize/ body-limit parity — testedproxy/proxyRequestforwardQUERYwith body (QUERYadded toPayloadMethods) — testedweb+nodematrix (router/body/proxy tests)PR 2 —
Accept-Queryheader utilitiessetAcceptQuery(event, mediaTypes)— serialize to Structured Fields ListgetAcceptQuery(event)— parse header back tostring[]PR 3 — Opt-in Content-Type validation helper
requireContentType(event, acceptedTypes)→400missing /415unsupported /422unprocessablePR 4 — Cross-cutting audits + docs
QUERY(non-safelisted); document that explicitmethodsallowlists must include"QUERY"handleCacheHeadersacceptsQUERYlikeGET(If-None-Match/If-Modified-Since/304) — audit GET/HEAD-only guards + regression testapp.query()and the QUERY use case