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

@alphinex/api

v1.1.0

Published

Transport-agnostic HTTP client contracts, interceptor pipeline, TanStack Query integration.

Readme

@alphinex/api

Transport-agnostic HTTP client contracts: a fetch-based ApiClient with an ordered request/response interceptor pipeline, a normalized ApiError, a pre-configured TanStack Query QueryClient factory, and a query-key convention. This package deliberately knows nothing about any specific backend — auth headers, CSRF, and error-shape mapping are supplied by a concrete adapter such as @alphinex/api-laravel via the interceptor options below.

createApiClient(options)

Builds an ApiClient with get/post/put/patch/delete, all returning Promise<T>. Request bodies are JSON-serialized and Content-Type/Accept: application/json are set by default. Non-2xx responses are normalized into an ApiError; a 204 resolves to undefined.

import { createApiClient } from "@alphinex/api";

const client = createApiClient({
  baseUrl: "https://api.example.com",
  requestInterceptors: [
    (config) => ({ ...config, headers: { ...config.headers, "X-App-Version": "1.0.0" } }),
  ],
  responseInterceptors: [
    (response) => {
      if (response.status === 401) {
        // e.g. redirect to login
      }
      return response;
    },
  ],
});

const user = await client.get<{ id: string; name: string }>("/user");
await client.post("/invoices", { amount: 100 });

requestInterceptors run in order over a RequestConfig (url/method/headers/body/signal) before the request is sent; responseInterceptors run in order over the raw Response before error normalization, so they can inspect status codes, throw their own ApiError to short-circuit the generic normalization, or trigger a token refresh and return a replacement Response. Pass fetchFn to override the global fetch (useful for mocking in tests, or a non-global-fetch runtime).

ApiError

The normalized error every request rejects with (extends @alphinex/core's AppError). Carries status (HTTP status code), code (a machine-readable string, e.g. http_404), and an optional fieldErrors (Record<string, string[]>) that a backend adapter populates for validation failures:

import { ApiError } from "@alphinex/api";

try {
  await client.post("/invoices", payload);
} catch (error) {
  if (error instanceof ApiError && error.status === 422) {
    console.log(error.fieldErrors); // { amount: ["The amount field is required."] }
  } else if (error instanceof ApiError && error.status === 401) {
    // session expired
  } else {
    throw error;
  }
}

createQueryClient(options?)

A TanStack Query QueryClient with platform defaults: retries skip 4xx errors except 401/408/429 (transient or refresh-recoverable), and refetchOnWindowFocus is off to avoid surprising refetches in dashboard-heavy apps. Tune defaultStaleTimeMs (default 30_000) and maxRetries (default 3):

import { createQueryClient } from "@alphinex/api";
import { QueryClientProvider } from "@tanstack/react-query";

const queryClient = createQueryClient({ defaultStaleTimeMs: 60_000, maxRetries: 2 });

function Root() {
  return (
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  );
}

queryKeys

A query-key factory convention that prevents key collisions/typos across a large app. queryKeys.resource(name) returns all(), lists(), list(params?), details(), and detail(id), each producing a stable, ordered key array:

import { queryKeys } from "@alphinex/api";
import { useQuery } from "@tanstack/react-query";

useQuery({
  queryKey: queryKeys.resource("invoices").detail(invoiceId),
  queryFn: () => client.get(`/invoices/${invoiceId}`),
});

useQuery({
  queryKey: queryKeys.resource("invoices").list({ status: "overdue" }),
  queryFn: () => client.get("/invoices", { headers: { "X-Filter": "overdue" } }),
});

See documentation/ARCHITECTURE.md for the full package contract, dependency rules, and roadmap placement.

uploadFile — uploads with progress

fetch still cannot report upload progress in any shipping browser. uploadFile is XMLHttpRequest-based for that single reason, and normalises everything else — errors, aborts, JSON parsing — into the same shapes the rest of this package produces.

import { uploadFile } from "@alphinex/api";

const saved = await uploadFile<{ id: string }>({
  url: "https://api.example.com/files",
  file,
  onProgress: ({ percent }) => setProgress(percent),
  signal: controller.signal,
});

Failures reject with ApiError, including a fieldErrors map for a Laravel-style 422 — so an upload failure is handled exactly like any other request failure:

try {
  await uploadFile({ url, file });
} catch (error) {
  if (error instanceof ApiError && error.status === 422) {
    setErrors(error.fieldErrors);
  }
}

percent is 0 whenever the total length isn't computable, rather than a guess.

createUploader binds a base URL and shared headers, the way createApiClient binds fetch:

const upload = createUploader({
  baseUrl: "https://api.example.com",
  headers: { Authorization: `Bearer ${token}` },
  withCredentials: true,
});

await upload("files", { file, onProgress });

Content-Type is never set from headers — the browser must append the multipart boundary itself, and overriding it produces a body the server cannot parse.