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

@jameskh/proxywire

v2.0.0

Published

Proxy list parsing and proxy-aware fetch for Node and Bun. Normalizes provider export formats, builds HTTP/SOCKS dispatchers, and returns a fetch that actually goes through the proxy on both runtimes.

Readme

proxywire

Proxy list parsing and proxy-aware fetch for Node and Bun.

Two jobs, both of which tend to get reinvented badly:

  1. Turn whatever your provider exported into a URL you can dial. Residential proxy vendors ship lists in at least five mutually incompatible notations. proxywire accepts all of them and hands back one normalized URL.
  2. Give you a fetch that actually goes through the proxy. On both runtimes. This is less obvious than it sounds — see Bun below.

No framework, no globals, no logging to the console. Node 18+ or Bun.

Install

bun add @jameskh/proxywire   # or: npm i @jameskh/proxywire

Quick start

import { loadProxyFile, makeFetcher, maskProxy, shuffleProxies } from "@jameskh/proxywire";

const proxies = shuffleProxies(loadProxyFile("./proxies.txt"));

for (const proxy of proxies) {
  const { fetch, close } = makeFetcher(proxy, { timeoutMs: 45_000 });
  try {
    const response = await fetch("https://api.ipify.org?format=json");
    console.log(maskProxy(proxy), await response.json());
    break;
  } finally {
    await close();
  }
}

Proxy notations

normalizeProxy accepts every shape below and returns a dialable URL. Anything it cannot understand throws a ProxyParseError naming the source and line.

| Input | Result | | --- | --- | | socks5://bob:[email protected]:1080 | unchanged | | socks5://1.2.3.4:1080:bob:pw | socks5://bob:[email protected]:1080 | | bob:[email protected]:1080 | socks5://bob:[email protected]:1080 | | 1.2.3.4:1080@bob:pw | socks5://bob:[email protected]:1080 | | bob:pw:1.2.3.4:1080 | socks5://bob:[email protected]:1080 | | 1.2.3.4:1080:bob:pw | socks5://bob:[email protected]:1080 |

The last four carry no scheme, so one is applied — socks5 unless you say otherwise. Host and credentials are told apart by which field looks like a port, and credentials are percent-encoded so a password containing @ or : cannot silently rewrite the host.

normalizeProxy("bob:[email protected]:1080", { defaultProtocol: "http" });

// Resolve the scheme lazily when it comes from the environment, so a variable
// set after import time is still honoured.
normalizeProxy(line, { defaultProtocol: () => process.env.PROXY_PROTOCOL ?? "socks5" });

Supported schemes: http, https, socks4, socks5.

Reading a list

import { loadProxyFile, parseProxyList } from "@jameskh/proxywire";

loadProxyFile("./config/proxies.txt");        // [] when the file is absent
parseProxyList(text, { source: "PROXY_ENV" }); // same parser, any string

Blank lines and # comments are skipped, but line numbers are counted against the original text, so errors point at the line you actually have to fix.

Lists still holding the placeholder rows that ship in example files (user:pass@host:port and friends) are rejected outright — running against a template is a configuration mistake, and failing on it beats a confusing DNS error later. Pass rejectPlaceholders: false to skip the check, or commentPrefix to use something other than #.

Making requests

const { fetch, dispatcher, close } = makeFetcher(proxy, {
  timeoutMs: 30_000,             // applied unless you pass your own signal
  socksConnectTimeoutMs: 15_000,
  minTlsVersion: "TLSv1.2",
  logger: { debug: (m) => log.debug(m) },
});

Call close() when you are finished. Without it every request cycle leaks an undici Agent and its sockets, which exhausts file descriptors in any long-running process. makeFetcher() with no proxy returns a plain fetch and a close() that does nothing, so the calling code needs no branching.

SOCKS proxies get an undici Agent whose connect opens the tunnel by hand and starts TLS over it, since undici has no SOCKS support of its own. HTTP and HTTPS proxies go to ProxyAgent.

Bun

Bun ignores undici's dispatcher option. A request aimed at a dead proxy still succeeds and reports the machine's own address — every "proxied" call silently goes out unproxied, which is the worst possible failure mode for anything that uses proxies for a reason. Bun's own fetch takes a proxy option that it does honour, so makeFetcher uses that path under Bun and the dispatcher path under Node.

Bun's proxy option cannot do SOCKS. Rather than fall through to a dispatcher Bun will ignore, makeFetcher throws:

SOCKS proxies are not supported when running under Bun (1.2.3.4:1080).
Use an http:// proxy, or set allowSocksUnderBun to opt out of this check.

The proxy is masked to host:port in that message — credentials in a proxy URL are as sensitive as any other password and must never reach a log.

Diagnosing failures

fetch throws a bare TypeError: fetch failed for every network failure and puts the real reason in cause, so a dead proxy, a bad credential and a slow host are indistinguishable. describeError flattens the chain:

describeError(error); // "fetch failed -> connection refused (ECONNREFUSED)"

retryOnTransportFailure reruns an operation once on failure, which is enough for the connection drops a rotating pool produces on its own:

await retryOnTransportFailure("token request", () => fetch(url, init), { logger });

API

| Export | Purpose | | --- | --- | | makeFetcher(proxy?, options?) | { fetch, dispatcher?, close() } bound to one proxy | | makeProxyDispatcher(proxy, options?) | The undici dispatcher on its own | | closeDispatcher(dispatcher?) | Release a dispatcher's socket pool | | withRequestTimeout(init, ms) | Attach a timeout signal unless one exists | | retryOnTransportFailure(label, run, options?) | Run once more on failure | | normalizeProxy(input, options?) | Any notation to a dialable URL | | parseProxyList(text, options?) | Newline-separated list to URLs | | loadProxyFile(path, options?) | Same, from a file; [] when absent | | parseSchemelessProxy(input) | The four schemeless shapes, as parts | | isPlaceholderProxy(input) | True for example-file template rows | | maskProxy(proxy) | host:port, safe to log | | shuffleProxies(list) | Fisher-Yates copy, to spread load | | describeError(error) | Flatten an error and its cause chain | | IS_BUN | Runtime check used for the paths above | | SUPPORTED_PROXY_PROTOCOLS | ["http", "https", "socks4", "socks5"] | | ProxyParseError | Carries source and lineNumber |

Types: Fetcher, FetcherOptions, ProxyDispatcher, ProxyFetch, ProxyListOptions, ProxyLogger, ProxyParseOptions, ProxyProtocol.

Runtime support

The published package ships both compiled JavaScript and its TypeScript source. Node resolves dist/ with full .d.ts declarations; Bun resolves the bun export condition and reads src/ directly, so a linked checkout picks up edits with no build step.

Development

bun install
bun test
bun run typecheck
bun run build     # emits dist/ for Node consumers

Relative imports carry explicit .js extensions so the emitted ESM resolves under Node. TypeScript maps ./errors.js back to errors.ts at compile time.

License

MIT