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

jervis-connect

v1.0.0-alpha.2

Published

A lightweight, zero-dependency, plugin-based HTTP client for all JavaScript runtimes. Axios-like API with modern fetch.

Readme

JervisConnect

A lightweight, zero-dependency, plugin-based HTTP client for all JavaScript runtimes. Axios-like API with modern fetch under the hood.

npm version bundle size license

Works everywhere: Browser, Node.js 18+, Deno, Bun, Cloudflare Workers, Edge, React Native, Electron, Service Workers.

Frameworks: Vue / Nuxt, React / Next.js, Angular, Svelte / SvelteKit, Remix, Gatsby, Expo.


Why JervisConnect?

| | jervis-connect | axios | ky | got | | --------------------------- | :------------: | :---: | :-: | :-: | | Zero dependencies | ✅ | ❌ | ✅ | ❌ | | Fetch-based | ✅ | ❌ | ✅ | ❌ | | Plugin system | ✅ | ❌ | ❌ | ❌ | | Auth (JWT/OAuth2/API key) | ✅ | ❌ | ❌ | ❌ | | Circuit breaker | ✅ | ❌ | ❌ | ❌ | | OWASP security built-in | ✅ | ❌ | ❌ | ❌ | | SSE / streaming | ✅ | ❌ | ❌ | ✅ | | HMAC signing | ✅ | ❌ | ❌ | ❌ | | GraphQL helpers | ✅ | ❌ | ❌ | ❌ | | Response caching (RFC 7234) | ✅ | ❌ | ❌ | ❌ | | Cookie jar | ✅ | ❌ | ❌ | ✅ | | Request inspector | ✅ | ❌ | ❌ | ❌ | | Tree-shakeable | ✅ | ❌ | ✅ | ❌ |


Features

Core

  • Universal — built on fetch, works in every modern runtime
  • Plugin system — api.use(plugin) for modular features
  • Request & response interceptors (Axios-compatible)
  • Multiple configured instances
  • Timeout with AbortController
  • Auto-retry with exponential backoff + jitter
  • Cancel tokens + AbortSignal
  • File upload with progress tracking
  • Upload & download progress callbacks
  • Request/response data transformers

Plugin Ecosystem (v2)

  • Auth — JWT auto-attach/refresh, API Key, OAuth2 client credentials
  • Circuit breaker — auto-stop after repeated failures, auto-recover
  • Concurrency control — limit parallel in-flight requests
  • Priority queue — high/normal/low request prioritization
  • Request batching — combine requests within a time window
  • Offline mode — queue mutations, auto-replay on reconnect
  • Runtime validation — validate responses against schemas
  • DevTools — real-time request monitoring panel

Security (OWASP)

  • SSRF protection (IPv4 + IPv6)
  • Header injection prevention
  • Prototype pollution defense
  • HMAC request signing (SHA-256/384/512)
  • Nonce / replay attack prevention
  • Webhook signature verification
  • Security header validation & grading
  • Request tamper detection

Advanced

  • Response caching (RFC 7234, LRU, ETag/conditional)
  • Response streaming + SSE parser
  • In-memory cookie jar (domain/path/secure matching)
  • GraphQL error detection & categorization
  • Lifecycle hooks (beforeRequest, afterResponse, onError)
  • Fluent request builder pattern
  • FormData builder (chainable)
  • Request inspector / debugger (DevTools-like)
  • HTTP compression helpers
  • TTFB tracking & rate limit detection
  • Mock adapter for testing
  • Structured logger middleware

Installation

npm install jervis-connect
yarn add jervis-connect
pnpm add jervis-connect

Quick Start

import { createInstance } from "jervis-connect";

const api = createInstance({
  baseURL: "https://api.example.com",
  timeout: 10000,
});

// GET
const { data } = await api.get("/users");

// POST
await api.post("/users", { name: "Ahmed", email: "[email protected]" });

Query Parameters

Pass params as an object — scalars and arrays are supported out of the box:

// Scalar params → ?page=1&limit=10
await api.get("/users", { params: { page: 1, limit: 10 } });

// Array params (default: indices format)
// → ?scopes[0]=withChildren&scopes[1]=isParent
await api.get("/users", {
  params: { scopes: ["withChildren", "isParent"] },
});

Array Serialization Formats

Control how arrays are serialized via paramsSerializer:

const params = { scopes: ["withChildren", "isParent"] };

// indices (default) → scopes[0]=withChildren&scopes[1]=isParent
await api.get("/users", { params, paramsSerializer: "indices" });

// brackets → scopes[]=withChildren&scopes[]=isParent
await api.get("/users", { params, paramsSerializer: "brackets" });

// repeat → scopes=withChildren&scopes=isParent
await api.get("/users", { params, paramsSerializer: "repeat" });

// comma → scopes=withChildren,isParent
await api.get("/users", { params, paramsSerializer: "comma" });

Set a Default Format

const api = createInstance({
  baseURL: "https://api.example.com",
  paramsSerializer: "brackets", // All requests use brackets
});

Custom Serializer Function

For full control, pass a function:

await api.get("/search", {
  params: { tags: ["vue", "react"], page: 1 },
  paramsSerializer: (params) =>
    Object.entries(params)
      .map(([k, v]) => `${k}=${Array.isArray(v) ? v.join("|") : v}`)
      .join("&"),
});
// → /search?tags=vue|react&page=1

Plugin System (v2)

Install features as tree-shakeable plugins:

import { createInstance } from "jervis-connect";
import { authPlugin } from "jervis-connect/plugins/auth";
import { cachePlugin } from "jervis-connect/plugins/cache";
import { circuitBreakerPlugin } from "jervis-connect/plugins/circuit-breaker";

const api = createInstance({ baseURL: "https://api.example.com" });

api
  .use(
    authPlugin({
      type: "jwt",
      getToken: () => localStorage.getItem("token"),
      refreshToken: async () => {
        const res = await fetch("/auth/refresh", { method: "POST" });
        const { token } = await res.json();
        localStorage.setItem("token", token);
        return token;
      },
    }),
  )
  .use(cachePlugin({ maxEntries: 200, defaultTTL: 600 }))
  .use(circuitBreakerPlugin({ failureThreshold: 5, cooldownMs: 30000 }));

Available Plugins

| Plugin | Import Path | Description | | ---------------- | -------------------------- | ------------------------------------ | | Auth | plugins/auth | JWT, API Key, OAuth2, custom auth | | Cache | plugins/cache | RFC 7234 response caching | | Circuit Breaker | plugins/circuit-breaker | Stop requests after failures | | Concurrency | plugins/concurrency | Limit parallel requests | | Priority | plugins/priority | Request priority queue | | Batch | plugins/batch | Combine requests in time window | | Offline | plugins/offline | Queue mutations, replay on reconnect | | Validate | plugins/validate | Runtime response validation | | Logger | plugins/logger | Structured request logging | | Cookies | plugins/cookies | In-memory cookie jar | | GraphQL | plugins/graphql | GraphQL helper methods | | HMAC | plugins/hmac | HMAC request signing | | Compression | plugins/compression | Accept-Encoding negotiation | | Security Headers | plugins/security-headers | Security header validation | | Mock | plugins/mock | Mock adapter for testing | | Dedup | plugins/dedup | Request deduplication | | Retry | plugins/retry | Retry as plugin (vs built-in) | | DevTools | devtools | Real-time monitoring panel |

Custom Plugins

import type { JervisPlugin, PluginHooks } from "jervis-connect";

function myPlugin(): JervisPlugin {
  return {
    name: "my-plugin",
    install(instance): PluginHooks {
      return {
        beforeRequest(config) {
          config.headers = { ...config.headers, "X-Custom": "value" };
          return config;
        },
      };
    },
  };
}

api.use(myPlugin());

Modular Imports

// Full library (backward compat)
import { createInstance } from "jervis-connect";

// Minimal core — only client + interceptors + cancel + errors
import { createInstance } from "jervis-connect/core";

// Individual plugins
import { authPlugin } from "jervis-connect/plugins/auth";
import { cachePlugin } from "jervis-connect/plugins/cache";

Interceptors

// Request — add auth token
api.interceptors.request.use((config) => {
  const token = localStorage.getItem("token");
  if (token) config.headers!["Authorization"] = `Bearer ${token}`;
  return config;
});

// Response — handle errors globally
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Redirect to login
    }
    return Promise.reject(error);
  },
);

File Upload

const formData = new FormData();
formData.append("avatar", file);

await api.post("/upload", formData, {
  isFile: true,
  onUploadProgress: (p) => console.log(`${Math.round(p.progress * 100)}%`),
});

Or use the FormData builder:

import { formData } from "jervis-connect";

const config = formData()
  .field("name", "Ahmed")
  .file("avatar", blob, "avatar.jpg")
  .json("metadata", { role: "admin" })
  .toConfig("/upload");

await api.request(config);

Auto Retry

const api = createInstance({
  baseURL: "https://api.example.com",
  retry: 3, // Retry up to 3 times
  retryDelay: 1000, // Exponential backoff with jitter
  retryOn: [408, 429, 500, 502, 503, 504],
});

Cancellation

const controller = new AbortController();
api.get("/users", { signal: controller.signal });
controller.abort();

HMAC Request Signing

import { createHmacSigningInterceptor } from "jervis-connect";

api.interceptors.request.use(
  createHmacSigningInterceptor({
    secret: "my-secret-key",
    algorithm: "SHA-256",
  }),
);

Response Caching

import { createCacheInterceptors } from "jervis-connect";

const cache = createCacheInterceptors({ maxEntries: 200, defaultTTL: 600 });
api.interceptors.request.use(cache.requestInterceptor);
api.interceptors.response.use(cache.responseInterceptor);

GraphQL

import {
  buildGraphQLRequest,
  createGraphQLErrorInterceptor,
} from "jervis-connect";

api.interceptors.response.use(createGraphQLErrorInterceptor());

const config = buildGraphQLRequest("/graphql", {
  query: `query { users { name email } }`,
});
const { data } = await api.request(config);

SSE / Streaming

import { parseSSEStream } from "jervis-connect";

const response = await fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  body: JSON.stringify({ model: "gpt-4", messages: [...], stream: true }),
});

for await (const event of parseSSEStream(response.body!)) {
  if (event.data === "[DONE]") break;
  const parsed = JSON.parse(event.data);
  process.stdout.write(parsed.choices[0]?.delta?.content || "");
}

Cookie Jar

import { createCookieJar } from "jervis-connect";

const jar = createCookieJar();
api.interceptors.request.use(jar.requestInterceptor);
api.interceptors.response.use(jar.responseInterceptor);

Request Inspector

import { createInspector } from "jervis-connect";

const inspector = createInspector();
api.interceptors.request.use(inspector.requestInterceptor);
api.interceptors.response.use(
  inspector.responseInterceptor,
  inspector.errorInterceptor,
);

// Later
console.log(inspector.getSummary());
// { total: 42, successful: 40, failed: 2, averageResponseTime: 156, successRate: 0.952 }

Lifecycle Hooks

import { createHooksInterceptors } from "jervis-connect";

const hooks = createHooksInterceptors({
  beforeRequest: [
    (config) => {
      console.log(`→ ${config.method} ${config.url}`);
      return config;
    },
  ],
  afterResponse: [
    (res) => {
      console.log(`← ${res.status}`);
      return res;
    },
  ],
  onError: [
    (err) => {
      console.error(err);
      throw err;
    },
  ],
});

api.interceptors.request.use(hooks.requestInterceptor);
api.interceptors.response.use(
  hooks.responseInterceptor,
  hooks.errorInterceptor,
);

Webhook Verification

import { verifyWebhookSignature } from "jervis-connect";

const isValid = await verifyWebhookSignature({
  payload: req.body,
  signature: req.headers["x-hub-signature-256"],
  secret: process.env.WEBHOOK_SECRET!,
});

Security Header Validation

import { validateSecurityHeaders } from "jervis-connect";

const result = validateSecurityHeaders(responseHeaders);
console.log(result.grade); // "A+", "A", "B", "C", "D", "F"
console.log(result.score); // 0–100

Mock Adapter (Testing)

import { createInstance, createMockAdapter } from "jervis-connect";

const client = createInstance({ baseURL: "https://api.example.com" });
const mock = createMockAdapter(client);

mock.onGet("/users").replyWith(200, [{ id: 1, name: "Ahmed" }]);

const { data } = await client.get("/users");
// data === [{ id: 1, name: "Ahmed" }]

Fluent Builder

const { data } = await api
  .builder()
  .post("/users", { name: "Ahmed" })
  .header("X-Custom", "value")
  .timeout(5000)
  .retry(3, 1000)
  .execute();

Framework Examples

Vue 3 — useJervisFetch Composable

JervisConnect ships a first-class Vue 3 adapter via jervis-connect/vue:

import { createInstance } from "jervis-connect";
import { createVueAdapter } from "jervis-connect/vue";

const api = createInstance({ baseURL: "/api" });
const { useJervisFetch } = createVueAdapter(api);

export { useJervisFetch };

Use the composable in any <script setup> component:

<script setup lang="ts">
import { useJervisFetch } from "@/lib/api";

const { data, loading, error, refresh } = useJervisFetch<User[]>("/users");
</script>

<template>
  <p v-if="loading">Loading…</p>
  <p v-else-if="error">{{ error.message }}</p>
  <ul v-else>
    <li v-for="u in data" :key="u.id">{{ u.name }}</li>
  </ul>
  <button @click="refresh">Reload</button>
</template>

Key features:

  • Reactivedata, error, loading, status are Vue ref()s
  • Auto-execute on mount (or lazy: true for manual)
  • Watch reactive sources for auto re-fetch
  • SSRonServerPrefetch support
  • Suspenseawait useJervisFetch(…) for <Suspense> boundaries
  • SWRswr: true keeps stale data on revalidation error
  • Transformtransform: (data) => … before setting data.value
  • Abort — auto-aborts on scope dispose, manual abort() method

Vue / Nuxt (manual)

import { createInstance } from "jervis-connect";

const api = createInstance({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 15000,
});

api.interceptors.request.use((config) => {
  const token = useCookie("token").value;
  if (token) config.headers!["Authorization"] = `Bearer ${token}`;
  return config;
});

export default api;

React / Next.js

import { createInstance } from "jervis-connect";

const api = createInstance({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  timeout: 10000,
});

api.interceptors.request.use((config) => {
  const token = localStorage.getItem("token");
  if (token) config.headers!["Authorization"] = `Bearer ${token}`;
  return config;
});

export default api;

Node.js / Express

import { createInstance } from "jervis-connect";

const api = createInstance({
  baseURL: "https://external-api.com",
  timeout: 5000,
  retry: 3,
});

app.get("/proxy/users", async (req, res) => {
  const response = await api.get("/users");
  res.json(response.data);
});

UMD / Script Tag

<script src="https://unpkg.com/jervis-connect/dist/umd/index.min.js"></script>
<script>
  const client = JervisConnect.createInstance({
    baseURL: "https://api.example.com",
  });
  client.get("/users").then((res) => console.log(res.data));
</script>

Build Outputs

| Output | Path | Description | | -------------- | ------------------------- | ------------------------------------------------------------- | | ESM (minified) | dist/index.mjs | ES Modules — tree-shakeable | | CJS (minified) | dist/index.js | CommonJS — Node.js require() | | DTS | dist/index.d.ts | TypeScript declarations | | Core | dist/core.mjs | Minimal core entry point | | Plugins | dist/plugins/*.mjs | Individual plugin modules | | DevTools | dist/devtools/index.mjs | DevTools plugin | | Vue Adapter | dist/vue/index.mjs | Vue 3 useJervisFetch composable | | UMD | dist/umd/index.min.js | Browser <script> tag | | Secure | dist/secure/ | Extra terser pass | | Obfuscated | dist/obfuscated/ | Control-flow flattening, string encoding, dead code injection |


Security

JervisConnect ships with OWASP Top 10 security protections enabled by default:

  • SSRF Prevention — blocks requests to private networks (IPv4 + IPv6, loopback, link-local)
  • Header Injection — validates header names/values against injection patterns
  • Prototype Pollution — deep sanitization of JSON responses
  • Response Size Limits — configurable max response size
  • Sensitive Header Redaction — auto-redacts Authorization, Cookie, API keys in error objects
const api = createInstance({
  baseURL: "https://api.example.com",
  security: {
    validateURLs: true,
    ssrfProtection: true,
    sanitizeHeaders: true,
    prototypePollutionProtection: true,
    maxResponseSize: 10 * 1024 * 1024, // 10MB
    redactSensitiveHeaders: true,
  },
});

Documentation

Full documentation available at the JervisConnect Docs:


License

MIT © Kerolos Zakaria