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

@wisemen/node-fetch

v0.0.3

Published

A fetch client with a typed interceptor pipeline. Attach hooks that run before a request is sent, after a response is received, or when an error occurs — compatible with the interceptor API used by [hey-api](https://heyapi.dev) generated clients.

Readme

@wisemen/node-fetch

A fetch client with a typed interceptor pipeline. Attach hooks that run before a request is sent, after a response is received, or when an error occurs — compatible with the interceptor API used by hey-api generated clients.

Installation

pnpm add @wisemen/node-fetch

Quick start

import { createClient } from '@wisemen/node-fetch'

const client = createClient({
  baseUrl: 'https://api.example.com',
  headers: { 'X-App': 'my-app' },
})

client.interceptors.request.use(async (req) => {
  req.headers.set('Authorization', `Bearer ${await getToken()}`)
  return req
})

const res = await client.get('/users')
const data = await res.json()

createClient(config?)

Returns a FetchClient — a thin wrapper around the native fetch API with an interceptor pipeline applied to every call.

interface ClientConfig {
  baseUrl?: string      // prepended to every relative URL
  fetch?: typeof fetch  // custom fetch implementation (useful for testing)
  headers?: HeadersInit // default headers merged into every request
}

HTTP methods

All methods mirror the native fetch signature and return a native Response. Non-2xx responses are not thrown — they pass through to the response interceptors like any other response.

client.fetch(input, init?)   // same signature as globalThis.fetch
client.get(url, init?)
client.post(url, init?)
client.put(url, init?)
client.patch(url, init?)
client.delete(url, init?)
client.head(url, init?)
client.options(url, init?)

Interceptors

Each interceptor channel is available on client.interceptors:

| Channel | When it runs | |---|---| | request | Before the request is sent — use it to add auth headers, inject trace IDs, etc. | | response | After a response is received — use it to refresh tokens, log responses, etc. | | error | When fetch throws (network failure, abort) — use it to log or transform the error. |

Request interceptor

Receives the outgoing Request, must return the (possibly modified) request.

client.interceptors.request.use(async (req) => {
  req.headers.set('Authorization', `Bearer ${await getToken()}`)
  return req
})

Response interceptor

Receives the Response and the original Request. Must return the (possibly modified) response.

client.interceptors.response.use(async (res, req) => {
  if (res.status !== 401) return res

  // Refresh the token and retry once
  await refreshToken()
  const retry = new Request(req.url, { method: req.method, headers: req.headers })
  return client.fetch(retry)
})

Error interceptor

Receives the thrown error (and the Request). Must return the (possibly modified) error. The client always rethrows after running error interceptors.

client.interceptors.error.use((err, _res, req) => {
  logger.error('Network failure', { url: req.url, err })
  return err
})

Managing interceptors

// Add — returns a numeric id
const id = client.interceptors.request.use(myFn)

// Remove by id or by function reference
client.interceptors.request.eject(id)
client.interceptors.request.eject(myFn)

// Replace
client.interceptors.request.update(id, newFn)

// Check if still registered
client.interceptors.request.exists(id)
client.interceptors.request.exists(myFn)

// Remove all
client.interceptors.request.clear()

Recipes

OAuth Bearer token

const client = createClient({ baseUrl: 'https://api.example.com' })

client.interceptors.request.use(async (req) => {
  const token = await tokenStore.get()
  req.headers.set('Authorization', `Bearer ${token}`)
  return req
})

Token refresh on 401

let refreshing = false

client.interceptors.response.use(async (res, req) => {
  if (res.status !== 401 || refreshing) return res

  refreshing = true
  await tokenStore.refresh()
  refreshing = false

  const retry = new Request(req.url, { method: req.method, headers: req.headers })
  return client.fetch(retry)
})

Request tracing

client.interceptors.request.use((req) => {
  req.headers.set('X-Request-Id', crypto.randomUUID())
  req.headers.set('X-Trace-Id', traceContext.current())
  return req
})

Error logging

client.interceptors.error.use((err, _res, req) => {
  logger.error({ url: req.url, err })
  return err
})

Using interceptors standalone

If you have your own fetch wrapper and only need the interceptor pipeline:

import { createInterceptors } from '@wisemen/node-fetch'

const interceptors = createInterceptors<Request, Response, unknown, Request>()

async function send(request: Request): Promise<Response> {
  for (const fn of interceptors.request.fns) {
    if (fn) request = await fn(request, request)
  }

  let response: Response
  try {
    response = await fetch(request)
  }
  catch (error) {
    let finalError: unknown = error
    for (const fn of interceptors.error.fns) {
      if (fn) finalError = await fn(error, undefined as any, request, request)
    }
    throw finalError
  }

  for (const fn of interceptors.response.fns) {
    if (fn) response = await fn(response, request, request)
  }

  return response
}