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

@gavoryn/clearfetch

v1.0.6

Published

A dependency-free, fetch-native HTTP client for modern JavaScript and TypeScript runtimes.

Readme

clearfetch

A dependency-free, fetch-native HTTP client for modern JavaScript and TypeScript runtimes.

npm install @gavoryn/clearfetch

Why clearfetch?

Use clearfetch when you want a thin layer over native fetch, not a separate transport abstraction.

Choose it when you want:

  • reusable client defaults for baseURL, headers, timeout, retries, and hooks
  • JSON request/response convenience without runtime dependencies
  • predictable typed errors instead of repeating the same fetch boilerplate
  • a small surface area that is easy to audit

Not a fit if...

clearfetch is intentionally narrow. It is probably not the right client if you need:

  • upload or download progress APIs
  • interceptor-style response rewriting or a middleware ecosystem
  • legacy CommonJS or old-runtime support
  • automatic caching, cookie jars, XSRF helpers, or transport adapters
  • a broader, older, more feature-rich abstraction like axios

Hooks are intentionally not axios-style interceptors.

Usage

One-off request

import { request } from '@gavoryn/clearfetch'

const user = await request<{ id: string; name: string }>(
  'https://api.example.com/users/123',
)

Reusable client

import { createClient } from '@gavoryn/clearfetch'

const api = createClient({
  baseURL: 'https://api.example.com',
  headers: {
    Accept: 'application/json',
  },
  timeout: 5_000,
})

const user = await api.get<{ id: string; name: string }>('/users/123')

Query parameters

import { createClient } from '@gavoryn/clearfetch'

const api = createClient({
  baseURL: 'https://api.example.com',
})

const users = await api.get('/users', {
  query: {
    active: true,
    tag: ['admin', 'editor'],
  },
})

const ordered = await api.get('/users', {
  query: new URLSearchParams('tag=admin&page=1&tag=editor'),
})

Use an object for ordinary query parameters. Use native URLSearchParams when duplicate-key ordering matters.

JSON request bodies

import { createClient } from '@gavoryn/clearfetch'

const api = createClient({
  baseURL: 'https://api.example.com',
})

const created = await api.post<{ id: string }>('/users', {
  json: {
    name: 'Ada Lovelace',
    role: 'admin',
  },
})

If json is provided, clearfetch:

  • serializes the value with JSON.stringify()
  • sets Content-Type: application/json if it is not already present
  • rejects the request with ConfigError if body is also provided

Use body directly only when you want to send a raw payload such as FormData, URLSearchParams, or pre-serialized text.

Raw body payloads

import { createClient } from '@gavoryn/clearfetch'

const api = createClient({
  baseURL: 'https://api.example.com',
})

const form = new FormData()
form.set('avatar', fileInput.files[0])

await api.post('/profile/avatar', {
  body: form,
})

Extended client defaults

import { createClient } from '@gavoryn/clearfetch'

const api = createClient({
  baseURL: 'https://api.example.com',
})

const authed = api.extend({
  headers: {
    Authorization: 'Bearer token',
  },
})

const profile = await authed.get('/me')

Conservative retries

import { createClient } from '@gavoryn/clearfetch'

const api = createClient({
  baseURL: 'https://api.example.com',
  retry: {
    attempts: 3,
    backoffMs: 200,
    maxBackoffMs: 1_000,
    retryOnMethods: ['GET', 'HEAD'],
    retryOnStatuses: [429, 503],
  },
})

const response = await api.get('/status')

Retries are disabled by default. When enabled, they are intentionally conservative and do not allow streaming request bodies. They are a convenience for bounded retry cases, not a general resilience framework.

Abort a request

import {
  AbortRequestError,
  createClient,
} from '@gavoryn/clearfetch'

const controller = new AbortController()
const api = createClient({
  baseURL: 'https://api.example.com',
})

const promise = api.get('/reports/current', {
  signal: controller.signal,
})

controller.abort(new Error('user cancelled'))

try {
  await promise
} catch (error) {
  if (error instanceof AbortRequestError) {
    console.error('Request cancelled', error.cause)
  }
}

Hooks

const api = createClient({
  hooks: {
    beforeRequest: [
      async (context) => {
        context.headers.set('x-client', 'clearfetch')
      },
    ],
    afterResponse: [
      async (context) => {
        console.log(context.response.status)
      },
    ],
    onError: [
      async (context) => {
        console.error(context.error)
      },
    ],
  },
})

beforeRequest hook failures, request-normalization failures, retry rebuild failures, and request-construction failures propagate as-is and are observable through onError before being re-thrown. afterResponse hooks receive a cloned Response, so reading the body there does not consume the response used for normal parsing or HttpError creation.

Hook scope is intentionally narrow:

  • beforeRequest may mutate headers and may replace the URL with a final absolute URL
  • afterResponse and onError are observational only apart from throwing
  • context.options is read-only hook metadata, not a supported mutation surface

Cloned afterResponse inspection is intended for ordinary API payloads, not large streaming or heavy binary workflows.

Safe diagnostic header logging

clearfetch has no built-in logging or telemetry. Applications that log request diagnostics can use redactHeaders() to copy headers and replace common sensitive values before writing application-owned diagnostics. By default, it redacts exact case-insensitive matches for authorization, cookie, set-cookie, proxy-authorization, x-api-key, and api-key.

import { createClient, redactHeaders } from '@gavoryn/clearfetch'

const api = createClient({
  hooks: {
    beforeRequest: [
      (context) => {
        const safeHeaders = redactHeaders(context.headers)
        console.log(Object.fromEntries(safeHeaders))
      },
    ],
  },
})

Error handling

import {
  HttpError,
  ParseError,
  TimeoutError,
  createClient,
  isHttpClientError,
} from '@gavoryn/clearfetch'

const api = createClient({
  baseURL: 'https://api.example.com',
})

try {
  await api.get('/users/123')
} catch (error) {
  if (!isHttpClientError(error)) {
    throw error
  } else if (error instanceof HttpError) {
    console.error(error.status, error.bodyText)
  } else if (error instanceof ParseError) {
    console.error(error.bodyText)
  } else if (error instanceof TimeoutError) {
    console.error(error.timeout)
  }
}

Text and raw responses

import { createClient } from '@gavoryn/clearfetch'

const api = createClient({
  baseURL: 'https://api.example.com',
})

const health = await api.get('/health', {
  responseType: 'text',
})

const rawResponse = await api.get('/download', {
  responseType: 'raw',
})

Runtime validation

TypeScript generics describe the expected response shape, but they do not validate response data at runtime.

import { z } from 'zod'
import { createClient } from '@gavoryn/clearfetch'

const User = z.object({
  id: z.string(),
  name: z.string(),
})

const api = createClient({
  baseURL: 'https://api.example.com',
})

const data: unknown = await api.get<unknown>('/users/123')
const user = User.parse(data)

If you need end-to-end runtime safety, validate parsed data with a schema library such as Zod or Valibot after the request resolves.

Behavior notes

  • Non-2xx responses throw HttpError.
  • HttpError.bodyText capture is bounded and may be truncated for very large payloads.
  • ParseError.bodyText capture is also bounded and may be truncated for very large invalid JSON payloads.
  • HttpError.response remains available for status, headers, and metadata, but its body may already be consumed or canceled by diagnostic bodyText capture.
  • JSON mode returns undefined for empty response bodies.
  • In JSON mode, successful empty bodies resolve as T | undefined.
  • No default timeout is applied. Requests run until completion or external abort unless timeout is configured.
  • Invalid request configuration, including invalid hook lists, fails fast with ConfigError.
  • Hook, request-normalization, retry rebuild, and request-construction failures are not wrapped as NetworkError.
  • afterResponse receives a cloned Response for safe inspection.
  • Relative request inputs require baseURL.
  • beforeRequest may override the URL only with a final absolute URL.
  • beforeRequest may mutate headers, but hook option metadata is read-only.
  • Retry support is opt-in and conservative by default.
  • Retry support does not allow streaming request bodies.
  • The json helper serializes request bodies and sets Content-Type: application/json when absent.
  • body and json cannot be used together.
  • TypeScript rejects common invalid option combinations such as body plus json, and request bodies on GET/HEAD request shapes. Runtime validation still protects JavaScript callers.
  • The package performs no telemetry or hidden network activity beyond the caller's request.

Advanced behavior notes

  • Timeout windows are per attempt when retries are enabled. A configured timeout is not a total deadline across all retry attempts.
  • External abort signals surface as AbortRequestError, including when the signal was aborted with a custom reason.
  • External abort beats timeout if it happens first; timeout beats external abort if the timeout fires first.
  • Timeout aborts surface as TimeoutError.
  • External abort reasons are preserved as AbortRequestError.cause when the platform exposes them.
  • Retry backoff waits are abortable.
  • Timeout windows start after beforeRequest hooks complete.
  • Retry backoff waits do not consume per-attempt timeout windows.
  • If beforeRequest replaces context.url, that replacement is final. Previously resolved baseURL and query parameters are not reapplied to the replacement URL.
  • Hook metadata includes context.options.attempt and context.options.maxAttempts. Non-retried requests report attempt 1 and max attempts 1.
  • When query serializes to a non-empty string, hook metadata includes context.options.queryString without a leading ?. Existing search parameters from the input URL remain visible on context.url.

Important limitations by design

  • The package stays close to native fetch rather than inventing a separate transport model.
  • Hooks are intentionally narrower than axios-style interceptors.
  • Retries are conservative and explicit, not aggressive or automatic.
  • The package is ESM-only and targets modern runtimes only.
  • The public API is intentionally small; missing features are often deliberate non-goals, not incomplete work.

Supported runtimes

clearfetch currently supports:

  • Node.js 18.x and newer
  • modern browsers with native fetch, Request, Response, Headers, URL, and AbortController

The package is ESM-only and does not target legacy runtimes or polyfill-driven environments.

Security

  • The package includes no built-in telemetry.
  • The package performs no hidden network activity beyond the caller's request.
  • Vulnerability reports should follow the policy in SECURITY.md.

Release and CI

  • CI lints GitHub Actions workflows before merge.
  • CI runs lint, test, and build checks on selected supported Node.js versions.
  • CI also runs a lightweight browser-like test path using happy-dom on Node.js 20.
  • Dependency review is enforced for pull requests and supports manual base/head validation.
  • The release workflow supports a non-publishing dry-run path via manual dispatch.
  • npm publishing now uses npm trusted publishing from GitHub Actions instead of a long-lived publish token.
  • The release workflow publishes to npm with provenance and creates or verifies the matching GitHub Release record.
  • Normal releases are expected to publish from GitHub Actions, not from local machines.
  • Release and repository protection policy is documented in RELEASE.md.

Package surface

The public package surface is intentionally narrow:

  • the root export provides the supported runtime API and public types
  • internal implementation modules are not part of the supported import contract
  • the package includes no lifecycle scripts and is intended to publish only built dist/ artifacts

Development

  • npm install: install development dependencies
  • npm run build: compile the package into dist/
  • npm run check:package-metadata: validate publish metadata and zero-runtime-dependency posture
  • npm run check:pack-smoke: smoke-test the packed tarball from a clean temporary install
  • npm run lint: run TypeScript static checks
  • npm test: run the test suite
  • npm run test:browser-like: run browser-like package entrypoint coverage with happy-dom

Status

clearfetch is published as @gavoryn/clearfetch. The main branch may be ahead of the latest npm package until a matching release tag runs the Release workflow. Check npm and GitHub Releases for the currently published version. Project goals and behavior are documented in PURPOSE.md and DESIGN.md.