npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@danmat/query-fetch

v0.2.0

Published

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.

Readme

@danmat/query-fetch

CI npm version minified + gzip size License: MIT

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 },
});

Why QUERY?

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.

Install

npm install @danmat/query-fetch

What it does for you

Scripted fetch(url, { method: "QUERY", body }) already works in modern runtimes — but the semantics of RFC 10008 are on you. This library covers them:

  • Enforces 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.
  • Transparent 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.
  • Safe automatic retry (opt-in) — QUERY is idempotent by definition, so retrying transient failures is safe here in a way it never is for POST. Exponential backoff + jitter, honours Retry-After.
  • Redirect-safe — the RFC's 303 See Other indirect-result pattern is handled by fetch's own redirect following; nothing surprising here.
  • Zero dependencies, fully typed, tree-shakeable, dual ESM/CJS.

Usage

JSON queries

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.

Raw bodies with an explicit content type

await query("https://api.example.com/search", {
  body: "SELECT * WHERE status = 'active'",
  contentType: "application/sql",
  accept: "application/json",
});

Automatic retry (safe, because QUERY is idempotent)

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.)

Opt out of the POST fallback

await query(url, { json, fallbackToPost: false });

Bring your own fetch

import { fetch as undiciFetch } from "undici";

await query(url, { json, fetch: undiciFetch });

API

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. | | 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.

QueryError

Thrown for construction-time problems (a body without a content type, no available fetch) and non-2xx responses in queryJson.

Caveats & status

QUERY is a Proposed Standard (June 2026). Two things to know:

  • CORS: QUERY is not a CORS-safelisted method, so a cross-origin QUERY triggers a preflight. Your server must handle OPTIONS accordingly.
  • Spec churn: browser-integration details (method normalization, caching) are still being ironed out in whatwg/fetch#1938. This library tracks runtime behavior as it ships.

The @danmat QUERY suite

▶️ See them work together: query-suite-example — a runnable demo using all four, with a 🌐 live playground.

License

MIT © Dan Matthew