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

Published

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

Downloads

191

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

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 { 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()

await promise

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 propagate as-is. 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.

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.
  • 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.
  • Hook 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.
  • The package performs no telemetry or hidden network activity beyond the caller's request.

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 supported Node.js versions.
  • CI also runs a lightweight browser-like test path using happy-dom on Node.js 20.
  • Dependency review is configured for pull requests and manual validation, but requires the relevant GitHub security features to be enabled on the repository.
  • 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.
  • 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

Status

clearfetch is published as @gavoryn/clearfetch. Project goals and behavior are documented in PURPOSE.md and DESIGN.md.