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

vicks

v1.0.0

Published

A lightweight, zero-dependency wrapper for the fetch API with interceptors, retry, timeout, and more.

Downloads

15

Readme

Vicks - Fetch API with Superpowers

Vicks is a lightweight, zero-dependency wrapper built on the fetch API for browsers, Node.js, and Deno. It is fully compatible with the native fetch API while providing interceptors, retry, timeout, error handling, and more. Written in TypeScript with full type safety.

Features

  • Zero Dependencies — no third-party libraries, tiny bundle size
  • Interceptors — request, response, and error interceptors with chaining
  • Timeout — built-in request timeout via AbortSignal
  • Retry — automatic retry with configurable delay, status codes, and method safety
  • Error HandlingthrowOnError and validateStatus for automatic HTTP error throwing
  • Default Configurations — set headers, credentials, timeout, and more per client
  • Multiple Clients — isolated instances with independent config and interceptors
  • TypeScript — full type definitions, HeadersInit support, typed responses
  • Fetch Compatible — all fetch options (signal, credentials, mode, cache, etc.) pass through

Installation

npm install vicks
yarn add vicks
pnpm install vicks

Quick Start

import { vicks } from "vicks";

// Simple GET
const response = await vicks.get("https://api.example.com/posts");
const posts = await response.json();

// With a configured client
const client = vicks.create({
  baseUrl: "https://api.example.com",
  headers: { "Authorization": "Bearer token" },
  throwOnError: true,
  timeout: 10000,
});

const users = await client.get("/users");

Creating a Client

Create a client with default configurations. Each client has its own interceptors and defaults with no interference between instances.

import { vicks } from "vicks";

const client = vicks.create({
  baseUrl: "https://jsonplaceholder.typicode.com",
  headers: { "X-App-Name": "my-app" },
  credentials: "include",
  timeout: 5000,
});

const response = await client.get("/posts");
const posts = await response.json();

Default Client

Vicks exports a pre-configured default client:

import { vicks } from "vicks";

const response = await vicks.get("https://jsonplaceholder.typicode.com/posts");

You can modify the default client's configuration at runtime:

vicks.defaults.baseUrl = "https://jsonplaceholder.typicode.com";
vicks.defaults.headers = { "Authorization": "Bearer token" };

const response = await vicks.get("/posts");

Making Requests

All methods return a native fetch Response object. You can pass any option supported by the fetch API alongside Vicks-specific options.

GET

const response = await client.get("/posts", {
  params: { userId: 1 },
  signal: abortController.signal,
});

POST

The body is the second argument, config is the third:

const response = await client.post("/posts", { title: "foo", body: "bar", userId: 1 }, {
  params: { draft: true },
});

PUT

const response = await client.put("/posts/1", { title: "updated" });

PATCH

const response = await client.patch("/posts/1", { title: "patched" });

DELETE

const response = await client.delete("/posts/1");

Automatic Content-Type

Vicks automatically sets Content-Type based on the body type:

| Body Type | Content-Type | |---|---| | Plain object | application/json (body is JSON.stringify'd) | | string | text/plain | | URLSearchParams | application/x-www-form-urlencoded;charset=UTF-8 | | Blob | application/octet-stream | | FormData | Set automatically by the browser |

You can override by passing headers:

await client.post("/data", myBody, {
  headers: { "Content-Type": "text/xml" },
});

Timeout

Set a timeout in milliseconds. The request will abort if it takes longer:

const client = vicks.create({
  baseUrl: "https://api.example.com",
  timeout: 5000, // 5 seconds
});

// Or per-request:
await client.get("/slow-endpoint", { timeout: 15000 });

Timeout composes correctly with a user-provided signal — both can abort the request.

Retry

Automatic retry for transient failures:

const client = vicks.create({
  baseUrl: "https://api.example.com",
  retry: {
    retries: 3,
    retryDelay: 1000, // ms between retries
    retryOn: [503, 502], // retry on these status codes
  },
});

Safety constraints

  • Idempotent methods only by default: Only GET, PUT, and DELETE are retried. POST and PATCH are excluded unless explicitly added via retryMethods.
  • Non-replayable bodies are never retried: ReadableStream bodies skip retries regardless of config.
  • User aborts are never retried: Only timeout-related abort errors trigger retries.
// Explicitly allow POST retries (use with caution)
await client.post("/idempotent-endpoint", data, {
  retry: { retries: 2, retryOn: [503], retryMethods: ["GET", "POST"] },
});

Exponential backoff

const client = vicks.create({
  retry: {
    retries: 3,
    retryDelay: (attempt) => Math.pow(2, attempt) * 1000, // 1s, 2s, 4s
  },
});

Error Handling

throwOnError

By default, Vicks behaves like fetch — non-2xx responses don't throw. Enable throwOnError to throw an HttpError on error status codes:

import { vicks, HttpError } from "vicks";

const client = vicks.create({
  baseUrl: "https://api.example.com",
  throwOnError: true,
});

try {
  await client.get("/not-found");
} catch (error) {
  if (error instanceof HttpError) {
    console.log(error.status);   // 404
    console.log(error.response); // the Response object
  }
}

validateStatus

For custom status validation:

const client = vicks.create({
  validateStatus: (status) => status < 500, // only throw on 5xx
});

Both throwOnError and validateStatus run after response interceptors, so interceptors can still inspect and act on error responses (e.g., redirect on 401).

Interceptors

Vicks provides request, response, and error interceptors.

Request Interceptors

Modify the request config before it's sent. Interceptors receive the full config object. The return value is deep-merged with the current config. To remove a header, mutate the config directly.

client.interceptors.request.use((config) => {
  // Add a header (via merge)
  return { ...config, headers: { ...config.headers, "Authorization": getToken() } };
});

// Remove a header (via mutation)
client.interceptors.request.use((config) => {
  const headers = config.headers as Record<string, string>;
  delete headers["x-unwanted"];
  return config;
});

Request interceptors fail fast — if one throws, the request is aborted and the error flows to error interceptors.

Response Interceptors

Transform or inspect the response:

client.interceptors.response.use((response) => {
  if (response.status === 401) {
    window.location.href = "/login";
  }
  return response; // Must return a Response object
});

Error Interceptors

Catch errors from any stage of the pipeline (network failures, timeouts, interceptor errors, HttpError from throwOnError):

client.interceptors.error.use((error) => {
  if (error instanceof HttpError && error.status === 401) {
    // Recover by returning a new Response
    return refreshTokenAndRetry();
  }
  // Re-throw by returning the error
  return error;
});

If an error interceptor returns a Response, it's treated as a recovery — the response goes through the normal response interceptor and status validation pipeline before being returned to the caller.

Removing Interceptors

Each use() returns a removal function:

const remove = client.interceptors.request.use(attachToken);

// Later:
remove();

Clearing All Interceptors

client.interceptors.clear();          // Clear all
client.interceptors.request.clear();  // Clear request only
client.interceptors.response.clear(); // Clear response only
client.interceptors.error.clear();    // Clear error only

Request Pipeline

The full request lifecycle:

request interceptors → fetch() → response interceptors → validateStatus/throwOnError → return
                                                                    ↓ (on error)
                                                            error interceptors → throw or recover

Configuration Options

Options can be set as client defaults or per-request overrides.

| Option | Type | Description | |---|---|---| | baseUrl | string | Base URL prepended to relative endpoints. Handles trailing slashes. | | headers | HeadersInit | Default headers. Accepts Record, Headers, or [key, value][] tuples. Merged case-insensitively. | | params | Record / URLSearchParams / string | Query parameters. Multi-value keys are preserved. | | timeout | number | Request timeout in milliseconds. | | retry | RetryConfig | Retry configuration (see Retry). | | throwOnError | boolean | Throw HttpError on non-2xx responses. Default: false. | | validateStatus | (status: number) => boolean | Custom status validation function. | | credentials | RequestCredentials | Fetch credentials option (omit, same-origin, include). | | mode | RequestMode | Fetch mode (cors, no-cors, same-origin). | | cache | RequestCache | Fetch cache option. | | signal | AbortSignal | Abort signal (per-request only, not in defaults). | | All fetch options | — | Any RequestInit property is passed through to fetch(). |

Migration from v0.x

Breaking changes in v1.0

  • baseUrl replaces baseURL: baseURL still works but logs a deprecation warning. It will be removed in v2.
  • Headers are case-insensitive: Headers are normalized via the Headers API. Content-Type and content-type are the same header. Header keys in fetch calls will be lowercase.
  • Request interceptors fail fast: A throwing interceptor now aborts the request instead of silently continuing. This prevents sending misconfigured requests (e.g., missing auth).
  • POST/PUT/PATCH method signatures: post(endpoint, body, config) — body is the second argument, config is third. This hasn't changed, but previous README examples were incorrect.

License

MIT