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

netfixer

v1.2.3

Published

Adaptive network-aware request scheduler for the web

Readme

netfixer

Make fetch resilient on bad networks.

Most retry libraries ask one question: did the request fail?
Netfixer asks a different one: should this request go out at all right now?

It monitors connection quality in real time — latency, throughput, not just navigator.onLine — and decides when requests should be sent, which ones go first, and how to retry them safely.

// Without netfixer: send and hope
const res = await fetch("/api/data");

// With netfixer: network-aware, prioritized, retried automatically
const res = await scheduler.fetch("/api/data", { netPriority: "normal" });

Why netfixer?

navigator.onLine lies. A phone on a congested 2G tower is technically "online". A laptop on a flaky hotel Wi-Fi is technically "online". Your requests time out anyway.

Netfixer measures actual latency and throughput, classifies the connection into good / degraded / poor / offline, and routes your requests accordingly — holding background traffic when things are struggling, letting critical requests through regardless, and retrying everything with proper backoff when things recover.


Features

  • 📶 Real-time network quality — good, degraded, poor, offline
  • 🚦 Priority-based scheduling — critical, normal, background
  • 🔁 Automatic retry with exponential backoff + jitter
  • 💾 Optional queue persistence across page reloads (localStorage / sessionStorage)
  • ⏱ Per-request and global timeout
  • 🕒 Max queue age — force-send after a configurable wait
  • 🌍 Cross-browser — works without navigator.connection (Firefox, Safari)
  • 🧠 HEAD/GET hybrid probing — ~83% less bandwidth than polling with GET

Installation

npm install netfixer

Quick start

import { NetworkMonitor, RequestScheduler } from "netfixer";

const monitor = new NetworkMonitor({ pingUrl: "/ping" });
monitor.start();

const scheduler = new RequestScheduler({ monitor });

const res = await scheduler.fetch("/api/data");
const data = await res.json();

How it works

Netfixer has two parts.

NetworkMonitor

Probes your connection every few seconds using lightweight HEAD requests (with occasional GET for throughput recalibration). Classifies the connection into one of four states:

| State | Meaning | |---|---| | good | Low latency, normal throughput | | degraded | Slower than usual, still usable | | poor | Very slow or unreliable | | offline | No connectivity |

RequestScheduler

Wraps fetch() with priority, queueing, and retry. Each request declares its priority. The scheduler checks the current network state and decides: send now, queue for later, or force-send after a timeout.

Default rules:

| Network state | Allowed priorities | |---|---| | good | critical, normal, background | | degraded | critical, normal | | poor | critical | | offline | (nothing sent) |

Queued requests flush in priority order (critical first), then FIFO within the same tier.


Examples

Payment, auth, checkout — must go through

await scheduler.fetch("/api/checkout", {
  method: "POST",
  body: JSON.stringify(order),
  headers: { "Content-Type": "application/json" },
  netPriority: "critical",
});

Sent even on a poor connection. Retried up to 3 times on failure.

Dashboard, feed, profile — user-facing

await scheduler.fetch("/api/dashboard", {
  netPriority: "normal",
  timeoutMs: 5_000,
});

Sent on good or degraded. Queued silently on poor or offline.

Analytics, telemetry — eventually

await scheduler.fetch("/api/events", {
  method: "POST",
  body: JSON.stringify(event),
  headers: { "Content-Type": "application/json" },
  netPriority: "background",
  persist: true,
  maxQueueAgeMs: 30_000,
});

Sent only on a good connection. Persists across page reloads. Force-sent after 30 seconds regardless of network state.


API

NetworkMonitor

new NetworkMonitor(options?)

| Option | Type | Default | Description | |---|---|---|---| | pingUrl | string | "" | URL for active probing. Required for reliable detection on Firefox and Safari. | | pingIntervalMs | number | 5000 | Interval between probes in ms. | | pingTimeoutMs | number | 5000 | Per-probe timeout in ms. | | thresholds | Partial<NetworkThresholds> | See below | Custom classification thresholds. | | downlinkResampleEvery | number | 6 | Number of HEAD probes before a full GET throughput recalibration. | | logging | boolean | false | Enable debug logs. |

Default thresholds:

{
  absoluteGood:  2,    // Mbps — always "good" above this
  absolutePoor:  0.15, // Mbps — always "poor" below this
  ratioGood:     0.75, // ratio to EWMA baseline → "good"
  ratioDegraded: 0.35, // ratio to EWMA baseline → "degraded"
  maxLatency:    400,  // ms — ceiling for "good"
}

Methods:

monitor.start()                    // Start monitoring
monitor.stop()                     // Stop and clean up
monitor.getState()                 // => NetworkState
monitor.getInfo()                  // => { state, latency, downlink, rtt }
monitor.onChange(fn)               // Subscribe; returns unsubscribe()
monitor.onChange(({ state, latency, downlink }) => {
  console.log(`${state} — ${latency}ms — ${downlink} Mbps`);
});

RequestScheduler

new RequestScheduler(options)

| Option | Type | Default | Description | |---|---|---|---| | monitor | NetworkMonitor | (required) | Monitor instance. | | rules | Partial<PriorityRules> | See defaults | Override allowed priorities per state. | | initialBackoffMs | number | 1000 | Initial backoff delay. | | maxBackoffMs | number | 30000 | Maximum backoff delay. | | defaultTimeoutMs | number | 10000 | Default per-request timeout. | | retryableStatuses | number[] | [429, 500, 502, 503, 504] | HTTP statuses that trigger a retry. | | persist | boolean | false | Persist queued requests across reloads. | | maxQueueAgeMs | number \| null | null | Global max queue wait before force-send. | | logging | boolean | false | Enable debug logs. |

Per-request options (all RequestInit options plus):

| Option | Type | Default | Description | |---|---|---|---| | netPriority | "critical" \| "normal" \| "background" | "normal" | Priority tier. | | timeoutMs | number | scheduler default | Timeout for this request. | | persist | boolean | scheduler default | Persist this request across reloads. | | maxQueueAgeMs | number | scheduler default | Max queue wait for this request. |

Methods:

scheduler.fetch(url, init?)   // => Promise<Response>
scheduler.destroy()           // Stop timers, reject queued requests, clean up

Retry behavior

Requests are retried on network errors, timeouts, and configured HTTP statuses.

delay = min(initialBackoffMs × 2^retries, maxBackoffMs) + jitter(±20%)

Defaults: 3 max retries — 1s → 2s → 4s (with jitter), capped at 30s.


Queue persistence

With persist: true, queued requests survive page reloads. On next load they are restored and sent when the network allows.

Storage strategy: localStorage first, sessionStorage as fallback. Non-serializable bodies (ReadableStream, FormData, Blob, ArrayBuffer) are automatically excluded with a warning.

Note: the original Promise is lost on reload. Persisted requests are fire-and-forget — they will be sent, but no caller receives the response. This is intentional.


Custom rules

const scheduler = new RequestScheduler({
  monitor,
  rules: {
    degraded: ["critical"],        // only critical on degraded
    poor: ["critical", "normal"],  // allow normal on poor
  },
});

Browser compatibility

navigator.connection is unavailable in Firefox and Safari. Without pingUrl, these browsers default to poor until a real measurement is available.

Always configure pingUrl for reliable cross-browser behavior.


⚠️ Retry safety

Netfixer may send a request more than once — on retry, timeout, or after a reload. Your backend should handle duplicate requests safely.

For mutating endpoints (POST, PUT, DELETE), use idempotency keys or duplicate-safe logic server-side. Netfixer improves client-side reliability — it does not guarantee exactly-once delivery.


Lifecycle

monitor.stop();
scheduler.destroy();

Call this on SPA teardown, route unmount, or test cleanup. destroy() clears all timers, unsubscribes from the monitor, and rejects any queued requests immediately.


TypeScript

Netfixer is written in TypeScript and ships full type declarations.

type NetworkState    = "good" | "degraded" | "poor" | "offline";
type RequestPriority = "critical" | "normal" | "background";

interface NetworkInfo {
  state:    NetworkState;
  latency:  number; // ms
  downlink: number; // Mbps
  rtt:      number; // ms
}

interface SchedulerRequestInit extends RequestInit {
  netPriority?:   RequestPriority;
  timeoutMs?:     number;
  persist?:       boolean;
  maxQueueAgeMs?: number;
}

When to use netfixer

Good fit for apps that need:

  • resilient requests on flaky mobile networks
  • priority traffic (critical goes first, background waits)
  • automatic retry without boilerplate
  • background events or analytics that must eventually land
  • queue persistence across reloads

When not to use netfixer

Netfixer is a smart request layer, not a full offline platform. It is not a replacement for:

  • Service Workers + Background Sync API
  • IndexedDB-based sync engines
  • Conflict resolution or data reconciliation systems

If you need guaranteed delivery with conflict handling, look at those instead.


License

MIT