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 🙏

© 2025 – Pkg Stats / Ryan Hefner

stealth-io

v1.0.2

Published

stealth-io

Readme


Motivation

Why build another HTTP client when so many exist? Because sometimes you need full control without the baggage of external dependencies. Stealth-IO is built entirely from Node.js core modules (http, https, url, stream, zlib, events), making it:

  • Lightweight: Zero external runtime dependencies
  • Secure: No supply chain vulnerabilities from third-party packages
  • Predictable: Behavior determined solely by Node.js internals
  • Portable: Works anywhere Node.js runs

Features

  • Zero Dependencies: Built entirely from Node.js core modules
  • Full HTTP Method Support: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
  • Automatic JSON Handling: Serialize/deserialize JSON request and response bodies
  • Response Body Processing: Support for JSON, text, buffer, and stream responses
  • Middleware Pipeline: Koa-style onion model for request/response interception
  • Configurable Retry Logic: Exponential backoff with jitter support
  • Pattern-Based Retry: Retry on response body/header patterns (unique feature)
  • Rate Limiting: Built-in rate limiter with queue support for API integrations
  • Request Timeout: Configurable timeout with proper cleanup
  • Abort/Cancellation: Full AbortController/AbortSignal support
  • Automatic Redirects: Follow redirects with configurable limits
  • Response Decompression: Automatic gzip, deflate, and brotli decompression
  • Case-Insensitive Headers: Headers class with normalized key access
  • Query String Handling: Parse and serialize query parameters
  • Connection Pooling: Leverage Node.js built-in HTTP agents
  • Pluggable Transport: Custom transport interface for advanced use cases

Requirements

  • Node.js >= 18.0.0

Installation

npm install stealth-io

Loading and configuring the module

ES Modules (ESM)

import stealthIO from 'stealth-io';

Named Exports

import { StealthClient, createClient, Headers } from 'stealth-io';

CommonJS

Stealth-IO is an ESM-only module. For CommonJS projects, use dynamic import:

const stealthIO = (...args) => import('stealth-io').then(({default: stealthIO}) => stealthIO(...args));

Quick Start

import stealthIO from 'stealth-io';

const response = await stealthIO.get('https://api.example.com/users');
const data = await response.json();

console.log(data);

Common Usage

Plain text or HTML

import stealthIO from 'stealth-io';

const response = await stealthIO.get('https://github.com/');
const body = await response.text();

console.log(body);

JSON

import stealthIO from 'stealth-io';

const response = await stealthIO.get('https://api.github.com/users/github');
const data = await response.json();

console.log(data);

Simple Post

import stealthIO from 'stealth-io';

const response = await stealthIO.post('https://httpbin.org/post', 'a=1');
const data = await response.json();

console.log(data);

Post with JSON

import stealthIO from 'stealth-io';

const response = await stealthIO.post('https://httpbin.org/post', {
	name: 'John Doe',
	email: '[email protected]'
});
const data = await response.json();

console.log(data);

Post with form parameters

import stealthIO from 'stealth-io';

const params = new URLSearchParams();
params.append('username', 'john');
params.append('password', 'secret');

const response = await stealthIO.post('https://httpbin.org/post', params.toString(), {
	headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
const data = await response.json();

console.log(data);

Handling exceptions

import stealthIO, { TimeoutError, NetworkError, AbortError } from 'stealth-io';

try {
	const response = await stealthIO.get('https://domain.invalid/');
} catch (error) {
	if (error instanceof TimeoutError) {
		console.log('Request timed out:', error.timeout);
	} else if (error instanceof NetworkError) {
		console.log('Network error:', error.code);
	} else if (error instanceof AbortError) {
		console.log('Request was aborted');
	} else {
		console.log('Unknown error:', error);
	}
}

Handling client and server errors

import stealthIO, { HTTPError } from 'stealth-io';

try {
	const response = await stealthIO.get('https://httpbin.org/status/404');
} catch (error) {
	if (error instanceof HTTPError) {
		console.log('HTTP Error:', error.status, error.statusText);
		const body = await error.response.text();
		console.log('Response body:', body);
	}
}

Or disable automatic status validation:

import stealthIO from 'stealth-io';

const response = await stealthIO.get('https://httpbin.org/status/404', {
	validateStatus: false
});

if (!response.ok) {
	console.log('Request failed with status:', response.status);
}

Advanced Usage

Creating a Client Instance

For repeated requests to the same API, create a client instance with shared configuration:

import { StealthClient, createClient } from 'stealth-io';

const client = new StealthClient({
	baseURL: 'https://api.example.com',
	timeout: 10000,
	headers: {
		'Authorization': 'Bearer your-token',
		'X-API-Key': 'your-api-key'
	}
});

const users = await client.get('/users');
const user = await client.post('/users', { name: 'John' });

Or use the factory function:

const client = createClient({
	baseURL: 'https://api.example.com',
	timeout: 10000
});

Streams

Stream responses for large files or real-time data:

import { createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
import stealthIO from 'stealth-io';

const response = await stealthIO.get('https://example.com/large-file.zip');

if (!response.ok) throw new Error(`Unexpected response: ${response.statusText}`);

await pipeline(response.stream(), createWriteStream('./large-file.zip'));

Accessing Headers and other Metadata

import stealthIO from 'stealth-io';

const response = await stealthIO.get('https://github.com/');

console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
console.log(response.url);
console.log(response.headers.get('content-type'));
console.log(response.headers.entries());

Request cancellation with AbortSignal

import stealthIO, { AbortError } from 'stealth-io';

const controller = new AbortController();

setTimeout(() => controller.abort(), 150);

try {
	const response = await stealthIO.get('https://example.com/slow-endpoint', {
		signal: controller.signal
	});
	const data = await response.json();
} catch (error) {
	if (error instanceof AbortError) {
		console.log('Request was aborted');
	}
}

Using the built-in abort controller:

import { StealthClient } from 'stealth-io';

const client = new StealthClient();
const controller = client.createAbortController();

const promise = client.get('https://example.com/slow-endpoint', {
	signal: controller.signal
});

setTimeout(() => controller.abort(), 1000);

try {
	const response = await promise;
} catch (error) {
	if (error.code === 'ABORTED') {
		console.log('Request was cancelled');
	}
}

Middleware

Add middleware to intercept requests and responses:

import { StealthClient, createLoggingMiddleware, createAuthMiddleware } from 'stealth-io';

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

client.use(createLoggingMiddleware({
	logger: console.log
}));

client.use(createAuthMiddleware({
	type: 'bearer',
	token: 'your-token'
}));

const response = await client.get('/users');

Retry Logic

Configure automatic retries with exponential backoff:

import { StealthClient, createRetryPolicy } from 'stealth-io';

const client = new StealthClient({
	maxRetries: 3,
	retryDelay: 1000,
	retryStrategy: 'exponential_jitter'
});

const response = await client.get('/flaky-endpoint');

Using preset retry policies:

import { StealthClient, createRetryPolicy } from 'stealth-io';

const client = new StealthClient({
	...createRetryPolicy('aggressive')
});

Available policies:

  • aggressive: 5 retries, 500ms delay, exponential jitter
  • moderate: 3 retries, 1000ms delay, exponential
  • conservative: 2 retries, 2000ms delay, exponential
  • none: No retries

Rate Limiting

Built-in rate limiting with queue support for API integrations:

import { createClient } from 'stealth-io';

const client = createClient({
	rateLimit: {
		maxRequests: 50,
		perMs: 1000,
		queue: true
	}
});

const response = await client.get('https://api.example.com/data');

Alternative time-based configurations:

const client = createClient({
	rateLimit: { perSecond: 10 }
});

const client = createClient({
	rateLimit: { perMinute: 60 }
});

const client = createClient({
	rateLimit: { perHour: 1000 }
});

Full rate limit options:

const client = createClient({
	rateLimit: {
		maxRequests: 50,
		perMs: 1000,
		queue: true,
		maxQueueSize: 100,
		queueTimeout: 60000,
		algorithm: 'sliding-window',
		burstLimit: null,
		onThrottle: ({ waitTime, queueSize, queued }) => {
			console.log(`Rate limited. Wait: ${waitTime}ms`);
		},
		onDequeue: ({ waitedMs, remainingQueue }) => {
			console.log(`Request dequeued after ${waitedMs}ms`);
		}
	}
});

| Option | Type | Default | Description | |--------|------|---------|-------------| | maxRequests | number | 50 | Maximum requests per time window | | perMs | number | 1000 | Time window in milliseconds | | perSecond | number | - | Shorthand for requests per second | | perMinute | number | - | Shorthand for requests per minute | | perHour | number | - | Shorthand for requests per hour | | queue | boolean | false | Queue requests instead of rejecting | | maxQueueSize | number | 100 | Maximum queue size | | queueTimeout | number | 60000 | Queue timeout in milliseconds | | algorithm | string | 'sliding-window' | Algorithm: 'sliding-window' or 'token-bucket' | | burstLimit | number | null | Burst limit for token bucket algorithm | | onThrottle | function | null | Callback when rate limited | | onDequeue | function | null | Callback when request is dequeued |

Accessing the rate limiter:

const limiter = client.getRateLimiter();
console.log(limiter.getRemainingRequests());
console.log(limiter.getQueueSize());

client.setRateLimit({ perSecond: 20 });

client.setRateLimit(null);

Pattern-Based Retry

Retry requests based on response body or header patterns:

import { createClient } from 'stealth-io';

const client = createClient({
	retry: {
		maxRetries: 5,
		matchBody: /temporarily unavailable/i
	}
});

Full pattern retry options:

const client = createClient({
	retry: {
		maxRetries: 5,
		retryDelay: 1000,
		maxRetryDelay: 30000,
		retryStrategy: 'exponential',
		matchBody: [/temporarily unavailable/i, /rate limit/i],
		matchHeaders: { 'x-ratelimit-remaining': '0' },
		matchStatus: [429, 500, 502, 503, 504],
		matchAny: true,
		excludeBody: /permanent error/i,
		excludeHeaders: { 'x-no-retry': 'true' },
		excludeStatus: [404, 401],
		onRetry: ({ response, attempt, delay, reason }) => {
			console.log(`Retry ${attempt + 1}, reason: ${reason}`);
		},
		onPatternMatch: ({ response, attempt, result }) => {
			console.log(`Pattern matched: ${result.reason}`);
		}
	}
});

| Option | Type | Default | Description | |--------|------|---------|-------------| | maxRetries | number | 3 | Maximum retry attempts | | retryDelay | number | 1000 | Base retry delay in milliseconds | | maxRetryDelay | number | 30000 | Maximum retry delay | | retryStrategy | string | 'exponential' | Backoff strategy | | matchBody | RegExp\|string\|function\|array | - | Body patterns to trigger retry | | matchHeaders | object | - | Header patterns to trigger retry | | matchStatus | number\|array\|Set\|function | - | Status codes to trigger retry | | matchAny | boolean | false | Retry if any pattern matches (default: all must match) | | excludeBody | RegExp\|string\|function\|array | - | Body patterns to prevent retry | | excludeHeaders | object | - | Header patterns to prevent retry | | excludeStatus | number\|array\|Set | - | Status codes to prevent retry | | onRetry | function | - | Callback on retry | | onPatternMatch | function | - | Callback when pattern matches |

Pre-built patterns for common APIs:

import { createClient, createApiSpecificPatterns } from 'stealth-io';

const discordPatterns = createApiSpecificPatterns('discord');
const client = createClient({
	retry: {
		maxRetries: 5,
		...discordPatterns
	}
});

Supported APIs: discord, twitter, shopify, github, stripe

Common pattern matchers:

import { createCommonPatternMatchers } from 'stealth-io';

const patterns = createCommonPatternMatchers();

const client = createClient({
	retry: {
		maxRetries: 3,
		matchBody: [
			patterns.temporarilyUnavailable,
			patterns.rateLimited,
			patterns.maintenance,
			patterns.overloaded
		],
		matchAny: true
	}
});

Available patterns:

  • temporarilyUnavailable - Matches "temporarily unavailable"
  • rateLimited - Matches "rate limit", "too many requests", "throttle"
  • maintenance - Matches "maintenance", "down for maintenance"
  • overloaded - Matches "overloaded", "capacity", "try again later"
  • serviceUnavailable - Matches "service unavailable", "503"
  • timeout - Matches "timeout", "timed out"
  • internalError - Matches "internal error", "unexpected error"
  • badGateway - Matches "bad gateway", "502"
  • gatewayTimeout - Matches "gateway timeout", "504"
  • connectionError - Matches "connection refused", "connection reset"
  • retryLater - Matches "retry", "try again", "come back later"

API

stealthIO(url[, options])

  • url A string representing the URL for the request
  • options Options for the HTTP(S) request
  • Returns: Promise<StealthResponse>

Perform an HTTP(S) request.

Options

The default values are shown after each option key.

{
	method: 'GET',
	headers: {},
	body: null,
	timeout: 30000,
	followRedirects: true,
	maxRedirects: 5,
	validateStatus: true,
	maxRetries: 0,
	retryDelay: 1000,
	retryStrategy: 'exponential',
	responseType: 'auto',
	signal: null,
	params: {},
	auth: null,
	baseURL: null,
	rateLimit: null,
	retry: null
}

| Option | Type | Default | Description | |--------|------|---------|-------------| | method | string | 'GET' | HTTP method | | headers | object | {} | Request headers | | body | any | null | Request body | | timeout | number | 30000 | Request timeout in milliseconds | | followRedirects | boolean | true | Whether to follow redirects | | maxRedirects | number | 5 | Maximum redirects to follow | | validateStatus | boolean | true | Throw HTTPError on 4xx/5xx status | | maxRetries | number | 0 | Number of retry attempts | | retryDelay | number | 1000 | Base retry delay in milliseconds | | retryStrategy | string | 'exponential' | Retry strategy: 'fixed', 'exponential', 'exponential_jitter' | | responseType | string | 'auto' | Response type: 'auto', 'json', 'text', 'buffer', 'stream' | | signal | AbortSignal | null | AbortSignal for cancellation | | params | object | {} | URL query parameters | | auth | object | null | Authentication { username, password } | | baseURL | string | null | Base URL prepended to relative URLs | | rateLimit | object | null | Rate limiting configuration | | retry | object | null | Pattern-based retry configuration |

Class: StealthClient

The main client class for making HTTP requests.

new StealthClient([options])

  • options Default Options for all requests

Creates a new StealthClient instance with default options.

const client = new StealthClient({
	baseURL: 'https://api.example.com',
	timeout: 10000,
	headers: { 'Authorization': 'Bearer token' }
});

client.get(url[, options])

  • url Request URL
  • options Request Options
  • Returns: Promise<StealthResponse>

client.post(url[, body[, options]])

  • url Request URL
  • body Request body (automatically serialized if object)
  • options Request Options
  • Returns: Promise<StealthResponse>

client.put(url[, body[, options]])

client.patch(url[, body[, options]])

client.delete(url[, options])

client.head(url[, options])

client.use(middleware)

  • middleware Middleware object or function
  • Returns: StealthClient (for chaining)

Add middleware to the request pipeline.

client.getRateLimiter()

  • Returns: RateLimiter | null

Get the rate limiter instance.

client.setRateLimit(config)

  • config Rate limit configuration object or null to disable
  • Returns: void

Update or disable rate limiting.

Class: StealthResponse

An HTTP response object.

response.ok

  • boolean

true if status is 200-299.

response.status

  • number

HTTP status code.

response.statusText

  • string

HTTP status message.

response.headers

  • Headers

Response headers.

response.url

  • string

Final URL after redirects.

response.json()

  • Returns: Promise<any>

Parse response body as JSON.

response.text()

  • Returns: Promise<string>

Get response body as string.

response.buffer()

  • Returns: Promise<Buffer>

Get response body as Buffer.

response.stream()

  • Returns: Readable

Get response body as Node.js Readable stream.

Class: Headers

HTTP headers with case-insensitive key access.

new Headers([init])

  • init Optional object, Headers instance, or entries array
import { Headers } from 'stealth-io';

const headers = new Headers({
	'Content-Type': 'application/json',
	'X-Custom-Header': 'value'
});

headers.get('content-type');
headers.set('Authorization', 'Bearer token');
headers.has('x-custom-header');
headers.delete('X-Custom-Header');

Class: StealthIOError

Base error class for all Stealth-IO errors.

Class: TimeoutError

Thrown when a request times out.

  • error.timeout - The timeout value in milliseconds

Class: AbortError

Thrown when a request is aborted via AbortSignal.

Class: NetworkError

Thrown for network-level errors (DNS, connection refused, etc).

  • error.code - System error code (e.g., 'ECONNREFUSED')

Class: HTTPError

Thrown for HTTP error status codes when validateStatus is enabled.

  • error.status - HTTP status code
  • error.statusText - HTTP status message
  • error.response - The StealthResponse object

Class: RetryError

Thrown when all retry attempts are exhausted.

  • error.attempts - Number of attempts made
  • error.lastError - The last error encountered

Class: RateLimitError

Thrown when rate limit is exceeded and queue is disabled.

  • error.retryAfter - Suggested wait time in milliseconds
  • error.queueSize - Current queue size
import { createClient, RateLimitError } from 'stealth-io';

const client = createClient({
	rateLimit: { maxRequests: 2, perMs: 1000, queue: false }
});

try {
	await client.get('/endpoint');
} catch (error) {
	if (error instanceof RateLimitError) {
		console.log(`Rate limited. Retry after ${error.retryAfter}ms`);
	}
}

Middleware

Built-in Middleware

import {
	createLoggingMiddleware,
	createAuthMiddleware,
	createCacheMiddleware,
	createTimingMiddleware,
	createHeadersMiddleware,
	createErrorMiddleware
} from 'stealth-io';

Logging Middleware

client.use(createLoggingMiddleware({
	logger: console.log,
	logRequest: true,
	logResponse: true
}));

Auth Middleware

client.use(createAuthMiddleware({
	type: 'bearer',
	token: 'your-token'
}));

client.use(createAuthMiddleware({
	type: 'basic',
	username: 'user',
	password: 'pass'
}));

Cache Middleware

client.use(createCacheMiddleware({
	ttl: 60000,
	maxSize: 100
}));

Timing Middleware

client.use(createTimingMiddleware({
	onTiming: (timing) => console.log(`Request took ${timing.duration}ms`)
}));

Custom Middleware

client.use({
	name: 'custom-middleware',
	onRequest(config) {
		config.headers.set('X-Request-ID', generateUUID());
		return config;
	},
	onResponse(response, config) {
		console.log(`Response received: ${response.status}`);
		return response;
	},
	onError(error, config) {
		console.error(`Request failed: ${error.message}`);
		return null;
	}
});

Testing

npm test
npm run test:coverage

Architecture

stealth-io/
  src/
    core/           # Constants, symbols, and error classes
    utils/          # Utility functions (headers, query, url, streams)
    types/          # Request and response type definitions
    http/           # Transport, abort, retry, rate-limiter, pattern-retry
    middleware/     # Middleware pipeline and built-in middleware
    client/         # Main StealthClient class
    index.js        # Main entry point
  test/             # Jest tests

Team

| channdev | | ------------------------------------------------------------------------------------ | | channdev |

Lead Author

Contributing

See CONTRIBUTING.md for details on how to contribute to this project.

License

MIT