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

@cassets/http-client

v0.1.1

Published

Fetch-based TypeScript HTTP client with interceptors, retries, React hooks, and browser-native chunked file uploads.

Downloads

57

Readme

@cassets/http-client

A fetch-based TypeScript HTTP client with request/response interceptors, retries, timeouts, React helpers, and browser-native chunked uploads.

npm license

@cassets/http-client is intentionally small: it wraps the platform fetch API instead of replacing it, while adding reusable behavior needed by application and upload layers.

Installation

Core only:

npm install @cassets/http-client

React hooks/provider:

npm install @cassets/http-client react

React is optional and is declared as a peer dependency.

Runtime support

The core HTTP client requires a runtime with fetch, Headers, AbortController, and standard Fetch API body types. Chunked uploads additionally require browser APIs including File, Blob, Worker, XMLHttpRequest, and localStorage when persistence metadata is enabled.

Quick start

import { HttpClient } from '@cassets/http-client';

const api = new HttpClient({
  baseURL: 'https://api.example.com/',
  defaultHeaders: {
    Accept: 'application/json',
  },
  timeout: 10_000,
});

const response = await api.get<{ id: string; name: string }>('/users/42');
console.log(response.status, response.data);

Request bodies are passed to fetch as provided. For JSON, use JSON.stringify() and set Content-Type: application/json.

await api.post(
  '/users',
  JSON.stringify({ name: 'Vivek' }),
  { headers: { 'Content-Type': 'application/json' } },
);

Response shape

interface HttpResponse<T> {
  data: T;
  status: number;
  statusText: string;
  headers: Headers;
  config: HttpRequest;
}

JSON responses are parsed as JSON, text/* as text, and other content types as Blob.

Like fetch, non-2xx HTTP status codes are returned as responses; they do not automatically throw. Network failures and aborts reject the promise.

Convenience methods

api.get('/users');
api.post('/users', body);
api.put('/users/42', body);
api.patch('/users/42', body);
api.delete('/users/42');

Each method accepts an optional request config.

const controller = new AbortController();

api.get('/reports', {
  signal: controller.signal,
  timeout: 30_000,
  headers: { 'X-Request-ID': crypto.randomUUID() },
});

controller.abort();

Request interceptors

api.use(async (request) => {
  return {
    ...request,
    headers: {
      ...request.headers,
      'X-Client-Version': 'web-1.0',
    },
  };
});

Interceptors run in registration order.

Response interceptors

Register a response interceptor using the second true argument:

api.use(async (response) => {
  if (response.status === 401) {
    // application-specific handling
  }
  return response;
}, true);

Authentication interceptor

import { HttpClient, authInterceptor } from '@cassets/http-client';

const api = new HttpClient({ baseURL: 'https://api.example.com/' });

api.use(authInterceptor(async () => {
  return sessionStorage.getItem('access_token') ?? '';
}));

Custom header/scheme:

api.use(authInterceptor(getApiKey, 'X-API-Key', ''));

Retry configuration

Global retry policy:

const api = new HttpClient({
  retries: 3,
  retryDelay: (attempt) => Math.min(500 * 2 ** attempt, 5_000),
  onRetry: (error, attempt) => {
    console.warn('retry', attempt, error.message);
  },
});

Per-request via interceptor:

import { retryInterceptor } from '@cassets/http-client';

api.use(retryInterceptor(2, (attempt) => attempt * 750));

Current retry behavior applies to rejected fetch/network attempts. HTTP responses such as 429 or 503 are still returned normally; implement status-based retry policy in your application if required.

Logging interceptors

import {
  loggingInterceptor,
  responseLoggingInterceptor,
  setLogLevel,
} from '@cassets/http-client';

setLogLevel('debug');
api.use(loggingInterceptor());
api.use(responseLoggingInterceptor(), true);

Authorization, cookie, and x-api-key request/response headers are redacted by the built-in logger. Application payloads are not automatically redacted, so do not enable debug logging for sensitive bodies in production.

Levels:

setLogLevel('debug');
setLogLevel('info');
setLogLevel('warn');
setLogLevel('error');
setLogLevel('none');

React: useHttp

import { useHttp } from '@cassets/http-client/react';

export function Users() {
  const api = useHttp({ baseURL: 'https://api.example.com/' });

  async function load() {
    const response = await api.get('/users');
    console.log(response.data);
  }

  return <button onClick={load}>Load users</button>;
}

useHttp creates one client instance for the mounted component. Configuration is used at initial creation.

Chunked browser uploads

The upload layer splits a File into chunks, requests signed upload URLs from your callback, and uploads chunks from a Web Worker with configurable parallelism.

import { UploadManager } from '@cassets/http-client/upload';

const manager = UploadManager.getInstance();

const result = await manager.upload({
  file,
  chunkSize: 10 * 1024 * 1024,
  parallelParts: 3,
  getUploadUrls: async (file, chunkSize, totalParts) => {
    const response = await fetch('/api/uploads/sign', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        fileName: file.name,
        fileSize: file.size,
        contentType: file.type,
        chunkSize,
        totalParts,
      }),
    });

    const data = await response.json();
    return data.urls;
  },
  onProgress: ({ percent, speed, eta }) => {
    console.log(`${percent.toFixed(1)}%`, speed, eta);
  },
});

The signer must return:

Array<{
  partNumber: number;
  url: string;
  uploadId?: string;
}>

Using @cassets/cloud

The cloud package provides signer-callback adapters with the exact getUploadUrls contract:

npm install @cassets/http-client @cassets/cloud
import { UploadManager } from '@cassets/http-client/upload';
import { createS3UploadUrlFetcher } from '@cassets/cloud/s3';

const getUploadUrls = createS3UploadUrlFetcher({
  signEndpoint: '/api/uploads/s3/sign',
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

await UploadManager.getInstance().upload({
  file,
  getUploadUrls,
});

The same pattern works for Azure Blob Storage and Google Cloud Storage.

Upload progress

interface UploadProgress {
  file: File;
  uploadedBytes: number;
  totalBytes: number;
  percent: number;
  speed?: number;
  eta?: number;
  completedParts: number;
  totalParts: number;
  state: 'idle' | 'uploading' | 'paused' | 'completed' | 'error' | 'cancelled';
}

Progress includes in-flight XHR bytes, so percentage can update inside a chunk rather than only when a chunk completes.

Pause, resume, cancel

UploadManager exposes task controls by task ID:

manager.pause(taskId);
manager.resume(taskId);
manager.cancel(taskId);

Important semantics in 0.1.x:

  • Pause is soft: it prevents new chunks from starting; chunks already in flight may finish.
  • Resume: starts pending chunks again.
  • Cancel: terminates the worker and rejects the upload promise.
  • A failed chunk currently fails the complete upload; automatic per-chunk retries are not yet implemented.

If your UI needs the task ID before completion, construct UploadTask directly or obtain active tasks from the manager.

React upload provider

import { UploadProvider, useUpload } from '@cassets/http-client/react';

function App() {
  return (
    <UploadProvider>
      <Uploader />
    </UploadProvider>
  );
}

function Uploader() {
  const { upload, pause, resume, cancel, tasks } = useUpload();

  async function start(file: File) {
    await upload({
      file,
      getUploadUrls,
    });
  }

  return <div>Active uploads: {tasks.length}</div>;
}

The provider refreshes its active-task snapshot periodically.

Persistence metadata

Set persistKey to store upload progress metadata in localStorage:

await manager.upload({
  file,
  getUploadUrls,
  persistKey: `upload:${file.name}:${file.size}`,
});

In 0.1.x, this is checkpoint metadata only. It does not guarantee cross-page/restart multipart resume; after reload the upload is reconciled by re-requesting URLs and may re-upload chunks. Do not market it as durable resumability yet.

File hashing

import { computeFileHash } from '@cassets/http-client/upload';

const sha256 = await computeFileHash(file);

This currently reads the complete file into memory before hashing. For multi-gigabyte files, use a streaming/incremental hashing strategy in the application instead.

Upload security checklist

  • Generate signed URLs on a trusted backend; never expose cloud secret keys in the browser.
  • Keep signed URLs short-lived and scope them to one object/part.
  • Validate file name, size, MIME type, tenant, user, and authorization server-side.
  • Configure storage CORS only for required origins and methods.
  • Treat client-side hashes as integrity hints unless the server/cloud validates them.
  • Complete multipart uploads server-side where the provider requires a finalize/commit call; this package currently uploads parts but does not own provider-specific completion APIs.

Important provider note

For S3 multipart upload, Azure block blobs, and some GCS flows, uploading signed parts is only one stage. Your backend may need to create the multipart session and later complete/commit it. @cassets/http-client deliberately leaves those provider-specific lifecycle calls to your backend/application.

Package exports

import { HttpClient } from '@cassets/http-client';
import { useHttp, UploadProvider, useUpload } from '@cassets/http-client/react';
import { UploadManager, UploadTask, computeFileHash } from '@cassets/http-client/upload';

Both ESM and CommonJS builds plus TypeScript declarations are published.

Project links

  • ClusterAssets GitHub: https://github.com/clusterassets
  • ClusterAssets LinkedIn: https://www.linkedin.com/company/clusterassets
  • Creator GitHub: https://github.com/diskhacker
  • Creator LinkedIn: https://www.linkedin.com/in/kp-vivek-rao-bhosale/

Contributing and security

See CONTRIBUTING.md and SECURITY.md.

License

MIT © Vivek Rao Bhosale / ClusterAssets.