
Company News
Jerod Santo Joins Socket as Head of Media
Allow myself to introduce... myself.
@danmat/query-fetch
Advanced tools
A tiny, dependency-free client for the HTTP QUERY method (RFC 10008) — the safe, idempotent request with a body. Content-Type enforcement, POST fallback, and Accept negotiation over native fetch.
A tiny, dependency-free client for the HTTP QUERY method (RFC 10008) — the request that is safe and idempotent like GET, but carries a body like POST, and caches like neither before it could.
Built on native fetch. Works in Node 18+, Deno, Bun, Cloudflare Workers, and the browser.
import { query } from "@danmat/query-fetch";
const res = await query("https://api.example.com/search", {
json: { filter: { status: "active" }, sort: "-createdAt", limit: 50 },
});
For years you had two bad options for a search endpoint:
GET with a query string — safe, idempotent, cacheable… but your filter blows past URL length limits and leaks into logs.POST with a body — room for a rich query… but it's neither safe, idempotent, nor cacheable, so proxies and clients treat it as a state change.QUERY is the missing third option: a body-carrying request that intermediaries may cache and clients may safely retry. This library handles the sharp edges the spec introduces.
npm install @danmat/query-fetch
Scripted fetch(url, { method: "QUERY", body }) already works in modern runtimes — but the semantics of RFC 10008 are on you. This library covers them:
Content-Type — the RFC requires servers to reject a QUERY whose body has no content type. We throw before the round-trip instead of letting you debug a 400.POST fallback — servers that don't understand QUERY yet respond 405/501; we automatically retry as POST and advertise the original method via X-HTTP-Method-Override so override-aware backends still route it correctly.Accept negotiation — pass a media type (or list) to negotiate the response format the RFC's Accept-Query dance is built around.POST. Exponential backoff + jitter, honours Retry-After.303 See Other indirect-result pattern is handled by fetch's own redirect following; nothing surprising here.import { queryJson } from "@danmat/query-fetch";
const { data, response } = await queryJson<{ total: number }>(
"https://api.example.com/search",
{ json: { q: "http query method" } },
);
console.log(data.total, response.headers.get("age"));
queryJson sets Accept: application/json, throws on a non-2xx status, and returns the parsed body alongside the raw Response.
await query("https://api.example.com/search", {
body: "SELECT * WHERE status = 'active'",
contentType: "application/sql",
accept: "application/json",
});
RFC 10008 defines QUERY as safe and idempotent — so unlike POST, retrying a
failed request can't cause a double-effect. Opt in with a count, or an object
for full control:
// Retry up to 3 times with exponential backoff + jitter.
await query(url, { json, retry: 3 });
// Full control.
await query(url, {
json,
retry: {
retries: 5,
minDelay: 200, // base backoff (ms)
maxDelay: 10_000,
factor: 2,
jitter: true,
respectRetryAfter: true, // honour Retry-After on 429/503
retryOn: ({ response, error }) =>
Boolean(error) || (response?.status ?? 0) >= 500,
onRetry: ({ attempt, delay }) => console.warn(`retry #${attempt} in ${delay}ms`),
},
});
By default it retries network errors and 408/425/429/500/502/503/504, and
does not retry aborts. Retry is off unless you set it. (Retries reuse a
buffered body — a string, bytes, or json; a streaming body is sent once.)
await query(url, { json, fallbackToPost: false });
The automatic fallback only triggers when the server answers 405/501. Some
origins reject QUERY before that — a legacy proxy, or a cross-origin server whose
CORS allows POST but not QUERY yet — so the QUERY throws (or its preflight fails)
with no status to react to. When you already know an origin is like that, skip the
doomed QUERY and POST with the override from the start:
await query(url, { json, transport: "post-override" });
This is a deliberate, per-call opt-in. The library never switches to it on its own, because a POST is treated as unsafe and uncacheable by intermediaries unless the server honors the override — so the choice stays yours.
fetchimport { fetch as undiciFetch } from "undici";
await query(url, { json, fetch: undiciFetch });
query(input, options?): Promise<Response>Performs a QUERY request. options extends RequestInit (so signal, credentials, redirect, etc. all work), minus method and with a richer body:
| Option | Type | Default | Description |
|---|---|---|---|
body | BodyInit | null | — | Raw query body. Pair with contentType. |
json | unknown | — | Value serialized to JSON; sets application/json. |
contentType | string | — | MIME type of body. Required when a body is present. |
accept | string | string[] | — | Sets the Accept header. |
fallbackToPost | boolean | true | Retry as POST on 405/501. |
methodOverrideHeader | string | false | "X-HTTP-Method-Override" | Header advertising the original method on fallback. |
transport | "query" | "post-override" | "query" | "post-override" sends POST + override from the start, for an origin you already know rejects QUERY. |
retry | number | RetryOptions | off | Auto-retry transient failures (safe: QUERY is idempotent). |
fetch | typeof fetch | globalThis.fetch | Custom fetch implementation. |
queryJson<T>(input, options?): Promise<{ data: T; response: Response }>query + JSON parsing + a non-2xx guard.
QueryErrorThrown for construction-time problems (a body without a content type, no available fetch) and non-2xx responses in queryJson.
QUERY is a Proposed Standard (June 2026). Two things to know:
QUERY in Access-Control-Allow-Methods. When it doesn't, fetch throws with no status, so the automatic 405/501 fallback can't see it. For an origin you know is in that state, pass transport: "post-override" to POST from the start — provided that server allows POST (and the override header) via CORS. There is no client-only way around a server that CORS-blocks both.@danmat QUERY suite@danmat/query-fetch — client for the QUERY method (you are here).@danmat/accept-query — parse/build/negotiate the Accept-Query header.@danmat/query-cache — body-aware response caching.@danmat/query-server — server-side request validation & negotiation.▶️ See them work together: query-suite-example — a runnable demo using all four, with a 🌐 live playground.
MIT © Dan Matthew
FAQs
A tiny, dependency-free client for the HTTP QUERY method (RFC 10008) — the safe, idempotent request with a body. Content-Type enforcement, POST fallback, and Accept negotiation over native fetch.
The npm package @danmat/query-fetch receives a total of 2 weekly downloads. As such, @danmat/query-fetch popularity was classified as not popular.
We found that @danmat/query-fetch demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.

Security News
Anthropic found biased reasoning and recklessness drove Claude Mythos 5 to publish malware on PyPI and compromise a security vendor.