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

@poluru-labs/fetchwise

v0.1.0

Published

Type-safe Fetch HTTP client with retries, interceptors, validation, and multi-host baseURL. Published as @poluru-labs/fetchwise; not affiliated with the npm package fetchwise.

Readme

@poluru-labs/fetchwise

A small, type-safe HTTP client for TypeScript and JavaScript. Built on the Fetch API.

This package is not affiliated with the existing npm package fetchwise. Install this one as @poluru-labs/fetchwise.

Works in any JS environment — browsers, Node, Deno, Bun, React Native, and frameworks like React, Vue, Angular, Lit, Next.js, and Nuxt.

Automatic retries · Response validation · Interceptors · Generated API types · Multiple API hosts

import { createClient } from "@poluru-labs/fetchwise";

const api = createClient({
  baseURL: {
    default: "https://api.shop.com",
    auth: "https://auth.shop.com",
  },
  retry: 3,
});

const users = await api.get("/users");
const session = await api.get("/session", { baseURL: "auth" });

Install

npm install @poluru-labs/fetchwise
pnpm add @poluru-labs/fetchwise
yarn add @poluru-labs/fetchwise
bun add @poluru-labs/fetchwise

The unscoped name fetchwise is already taken on npm, so this package is published as @poluru-labs/fetchwise.

Works everywhere

| Environment | How to import | | --- | --- | | ESM / TypeScript | import { createClient } from "@poluru-labs/fetchwise" | | CommonJS | const { createClient } = require("@poluru-labs/fetchwise") | | Default import | import fetchwise from "@poluru-labs/fetchwise" | | Browser <script> | fetchwise.createClient(...) via unpkg / jsDelivr | | React, Vue, Angular, Lit | Same ESM import — no adapter | | Next.js, Nuxt, SvelteKit, Remix | Same import on server and client | | Deno / Bun / Workers | Same ESM import |

The core library has no Node-only APIs. It uses globalThis.fetch, so it runs anywhere Fetch exists.

<script src="https://unpkg.com/@poluru-labs/fetchwise"></script>
<script>
  const api = fetchwise.createClient({ baseURL: "https://api.shop.com" });
  api.get("/users").then(console.log);
</script>

Examples by stack: examples/ — TypeScript, JavaScript, React, Vue, Angular, and Lit.

Quick start

import { createClient } from "@poluru-labs/fetchwise";

type User = { id: number; name: string; email: string };

const api = createClient({
  baseURL: "https://api.shop.com",
  headers: { Accept: "application/json" },
  credentials: "include",
  timeout: 8_000,
  retry: { attempts: 3, delay: 300 },
});

const users = await api.get<User[]>("/users");
const user = await api.get<User>("/users/:id", { params: { id: 1 } });
const created = await api.post<User>("/users", {
  name: "Poluru Ada",
  email: "[email protected]",
});

Plain JavaScript is the same API. See examples/javascript.

Features

Automatic retries

Failed network calls, timeouts, and selected HTTP statuses are retried with backoff.

const api = createClient({
  baseURL: "https://api.example.com",
  retry: {
    attempts: 3,
    delay: 300,
    backoff: "exponential",
    retryOn: [408, 429, 500, 502, 503, 504],
  },
});

Pass retry: 3 for defaults, or retry: false to turn retries off.

Multiple API hosts

baseURL can be one host or a map of names. Pick a host per request, pass a full URL, or extend() a client for one API.

const api = createClient({
  baseURL: {
    default: "https://api.shop.com",
    auth: "https://auth.shop.com",
    payments: "https://payments.shop.com",
  },
});

await api.get("/users");
await api.get("/session", { baseURL: "auth" });
await api.get("/charges", { baseURL: "payments" });
await api.get("/health", { baseURL: "https://status.shop.com" });
await api.get("https://edge.shop.com/ping");

const payments = api.extend({ baseURL: { default: "https://payments.shop.com" } });
await payments.post("/charges", { amount: 2500 });

Response validation

Pass any schema with a parse() method. Zod, Valibot, ArkType, or a tiny custom object all work.

const UserSchema = {
  parse(data: unknown): User {
    return data as User;
  },
};

const user = await api.get("/users/1", { schema: UserSchema });

Invalid payloads throw ValidationError.

Request and response interceptors

api.interceptors.request.use((request) => {
  request.headers.set("Authorization", `Bearer ${token}`);
  return request;
});

api.interceptors.response.use((response) => {
  console.log(response.status, response.url);
  return response;
});

api.interceptors.error.use((error) => {
  if (error.status === 401) logout();
  return error;
});

Generated API types

Describe routes once. fetchwise infers params, bodies, and responses.

interface API {
  "GET /users": User[];
  "GET /users/:id": User;
  "POST /users": { body: CreateUser; response: User };
}

const api = createClient<API>({ baseURL: "https://api.shop.com" });

const users = await api.get("/users");
const user = await api.get("/users/:id", { params: { id: 1 } });
const created = await api.post("/users", { name: "Poluru Ada", email: "[email protected]" });

Or generate that interface from JSON:

npx @poluru-labs/fetchwise generate api.spec.json -o api.types.ts
{
  "name": "API",
  "models": {
    "User": "{ id: number; name: string; email: string }"
  },
  "routes": [
    { "method": "GET", "path": "/users", "response": "User[]" },
    { "method": "POST", "path": "/users", "body": "CreateUser", "response": "User" }
  ]
}

Named methods are available too:

import { defineApi } from "@poluru-labs/fetchwise";

const users = defineApi({
  client: api,
  routes: {
    getUser: { method: "GET", path: "/users/:id" },
    createUser: { method: "POST", path: "/users" },
  },
});

await users.getUser({ params: { id: 1 } });

Examples

Organized by stack in examples/:

| Folder | Pattern | | --- | --- | | typescript | Generics, retries, generated types | | javascript | ESM, CommonJS, browser script | | react | Shared client + useUsers hook | | vue | Composable + SFCs | | angular | ApiService + signals | | lit | LitElement custom elements |

npm install
npm run example examples/typescript/basic.ts

API

createClient(options)

| Option | Type | Default | Description | | --- | --- | --- | --- | | baseURL | string \| { default?: string; [name: string]: string } | | Default host, or named hosts for multiple APIs | | headers | HeadersInit | | Default headers | | credentials | RequestCredentials | | Browser CORS cookies (include, same-origin) | | timeout | number | | Request timeout in ms | | retry | number \| RetryOptions \| false | false | Retry policy | | fetch | typeof fetch | globalThis.fetch | Custom fetch implementation |

fetchwise(options) and the default export are aliases of createClient(options).

Methods

api.get(url, config?)
api.post(url, body?, config?)
api.put(url, body?, config?)
api.patch(url, body?, config?)
api.delete(url, config?)
api.request(url, config?)   // returns parsed data
api.send(url, config?)      // returns { data, status, headers, raw }
api.extend(options)         // copy the client with new defaults

Request config

{
  baseURL, // named host or full URL for this request
  headers, query, params, body,
  timeout, retry, schema, signal,
  parseAs: "json" | "text" | "blob" | "auto" | "raw",
  fetchOptions, // extra Fetch init
}

Path tokens like /users/:id are filled from params.

Errors

| Class | When | | --- | --- | | HTTPError | Response status is not 2xx | | TimeoutError | The request exceeded timeout | | ValidationError | schema.parse() failed | | FetchwiseError | Network, abort, or other failures |

import { HTTPError } from "@poluru-labs/fetchwise";

try {
  await api.get("/missing");
} catch (error) {
  if (error instanceof HTTPError || error?.name === "HTTPError") {
    console.log(error.status, error.data);
  }
}

Use error.name when multiple copies of the package are bundled.

License

MIT