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

@netcov-io/client

v0.1.0

Published

TypeScript client for the NetCov API (netcov.io) — internet/broadband coverage data (Italy coverage + address geocoding, ANNCSU / Istat – Agenzia delle Entrate data)

Readme

@netcov-io/client

A typed, zero-dependency TypeScript client for the NetCov API (netcov.io) — internet/broadband coverage data (Italy coverage + address geocoding) (comuni, addresses, street numbers and fixed-broadband coverage for Italy). Works on Node.js 18+ and in every modern browser (uses the global fetch).

Fonte: Istat – Agenzia delle Entrate, Archivio Nazionale dei Numeri Civici e delle Strade Urbane (ANNCSU)

Attribution is mandatory (CC BY 4.0 data): surface the attribution string wherever you display, redistribute or export geocoded data. It is included on every response as the attribution field and exported as ATTRIBUTION from this package.

Install

npm i @netcov-io/client

AI coding agents

If you are an AI coding agent (or working with one), read SKILL.md and run the interactive onboarding:

npx @netcov-io/client init

It resolves NETCOV_API_KEY (flag, env, or .env), validates it with GET {baseUrl}/credits, and prints a copy-paste first call. No key yet? Sign up at https://netcov.io/portal (about a minute), then re-run init. LLM reference: https://netcov.io/llms.txt.

Quickstart

import { NetCovClient } from '@netcov-io/client';

const client = new NetCovClient({ apiKey: 'your-api-key' });

const { results, headers } = await client.geocode('Milano, Corso Buenos Aires 1', {
  limit: 5,
});
console.log(results[0].latitude, results[0].longitude);
console.log(headers.remaining); // tokens left in the burst bucket

Configuration

new NetCovClient({
  baseUrl: 'http://localhost:8788', // default
  apiKey: 'your-api-key',
  // fetch: myFetch,        // optional custom fetch implementation
  // signal: abortSignal,   // optional default abort signal
  // retryServerErrors: true, // opt-in 502/503/504 retry (default false)
});

Values can also come from environment variables:

| Variable | Default | | --- | --- | | NETCOV_BASE_URL | http://localhost:8788 (dev server) | | NETCOV_API_KEY | (none — no Authorization header is sent) |

Constructor options take precedence over environment variables.

Browser usage & key security

API keys must not ship in public JS bundles — anything in client-side code is extractable. Keep secret keys server-side (or in a backend proxy) and never commit them.

Browser use requires an origin-locked key (restricted to your site's origin). Use a key with an origin allowlist for frontend deployments so a leaked key cannot be reused elsewhere.

API

Every method resolves with the typed JSON payload of the endpoint, with the parsed X-RateLimit-* headers attached as a non-enumerable headers property. The property does not affect JSON.stringify() or Object.keys(), but is directly accessible (and destructurable).

const data = await client.geocode('Roma');
data.headers;            // { limit, remaining, reset, dailyRemaining, monthlyRemaining, retryAfter }
JSON.stringify(data);    // unchanged API payload

geocode(address, opts?)

Geocode a free-form Italian address.

const { results } = await client.geocode('Milano, Corso Buenos Aires 1');
// opts: { limit?: number /* 1-20 */, strict?: boolean, signal?: AbortSignal }

GeocodeResult fields: query, match, latitude, longitude, comune, provincia, regione, cap, civico, confidence (high | comune | low), source, score.

The response carries a top-level notice: string | null. It is null for civic-level (high) matches and set (e.g. "no civic-level match; fell back to the comune centroid (Bari)") when the address fell back to the comune centroid. Pass strict: true to reject non-civic matches and no-match queries with a 404 address_not_found error instead of the lenient fallback.

coverageItaly(params, opts?)

Fixed-broadband coverage for a point in Italy, by address or lat+lon (or both).

const { cells } = await client.coverageItaly({ lat: 43.9760736, lon: 10.944691 });
const byAddress = await client.coverageItaly({ address: 'Via Crespole e Fabbriche 125, Pistoia' });
// opts: { limit?: number, strict?: boolean, signal?: AbortSignal }

CoverageCell fields: technologies (technology ∈ adsl | vdsl | vdsl-vectoring | ftth | ftb | fttc | fttn | docsis | docsis-3.0 | docsis-3.1 | 2g | 3g | lte | 4g | 5g | 5g-fwa | fwa | satellite | other), best_speed_mbps, best_speed_tier (no-coverage | basic | fast | ultrafast | gigabit), operator (string | null).

For address queries the same notice / strict semantics as geocode apply (notice is always null for coordinate queries).

autocomplete({ city?, address?, limit? }, opts?)

Street suggestions when address is given (filtered by city); comune suggestions when address is empty.

const streets = await client.autocomplete({ city: 'Firenze', address: 'via del tirat' });
const comuni = await client.autocomplete({ city: 'To', limit: 10 });
// opts: { signal?: AbortSignal }

Street results use kind: 'street' with a street field; comune results use kind: 'comune' with a name field (plus regione).

groupTechnologies(coverage)

Group a coverage response per technology for UI rendering — one row per technology with the best declared downlink, its tier and the deduplicated operator list, sorted by max downlink descending (nulls last). Same grouping semantics as the coverage summary (operators are semicolon-split, trimmed and deduplicated).

import { groupTechnologies } from '@netcov-io/client';

const coverage = await client.coverageItaly({ lat: 43.9760736, lon: 10.944691 });
for (const g of groupTechnologies(coverage)) {
  console.log(g.technology, g.maxDownlinkMbps, g.bestSpeedTier, g.operators);
}
// [{ technology: 'ftth', maxDownlinkMbps: 1000, bestSpeedTier: 'gigabit', operators: ['Open Fiber'] }, …]

Errors & rate limits

Requests are governed by a token bucket that refills at rpm/60 tokens per second and holds at most ceil(rpm/12) tokens (~5 seconds of headroom). You can fire up to the burst cap instantly; afterwards you get roughly one request per refill interval (e.g. rpm 30 → burst of 3, then 1 every 2 s). The long-run rate is still rpm per minute. The 4th instant call over the burst cap returns HTTP 429 rate_limit_exceeded with a Retry-After header.

All failures throw an ApiError subclass — catch them all with err instanceof ApiError:

| Error class | When | Fields of interest | | --- | --- | --- | | NetworkError | request never reached the server (status: 0) | cause | | AuthenticationError | HTTP 401 — missing/invalid/revoked token | status | | RateLimitError | HTTP 429 — rate/quota cap exceeded | retryAfter, headers | | ServerError | HTTP 5xx — service/DB unavailable | status | | ApiError | any other 4xx/5xx, or non-JSON body | status, code, body |

Every error carries status, code, headers (RateLimitInfo | null) and, on 429 responses, retryAfter (seconds, parsed from the Retry-After header — both integer seconds and HTTP-date formats are supported). requestId / docUrl come straight from the error envelope — quote requestId in support tickets.

See the API error catalog for every code: https://netcov.io/errors.

Handling 429 / Retry-After

import { RateLimitError } from '@netcov-io/client';

try {
  const data = await client.geocode('Milano');
} catch (err) {
  if (err instanceof RateLimitError) {
    const waitMs = (err.retryAfter ?? 60) * 1000;
    setTimeout(() => retry(), waitMs); // respect Retry-After, don't hammer
  } else {
    throw err;
  }
}

Retrying 502/503/504 (opt-in)

By default server errors throw immediately. Pass retryServerErrors: true to retry idempotent GETs on 502/503/504 up to 3 extra times with backoff 0.5s, 1s, 2s. A retry_after envelope field or Retry-After header on the failed response overrides the scheduled delay for that attempt. HTTP 429 responses are never retried automatically.

const client = new NetCovClient({ apiKey: 'your-api-key', retryServerErrors: true });

Response headers

Every response carries X-RateLimit-Limit (the rpm), X-RateLimit-Remaining (tokens left in the burst bucket), X-RateLimit-Reset (seconds until the next token refills), X-RateLimit-Daily-Remaining and X-RateLimit-Monthly-Remaining. They are exposed in parsed form:

interface RateLimitInfo {
  limit: number | null;          // rpm
  remaining: number | null;      // burst-bucket tokens left (cap ceil(rpm/12))
  reset: number | null;          // seconds until the next token refills
  dailyRemaining: number | null;
  monthlyRemaining: number | null;
  retryAfter: number | null;     // seconds
}

Design notes

  • Zero runtime dependencies. Only fetch (global) is used.
  • Response shape. Methods return the API payload directly, with headers attached non-enumerably — your data stays clean and serializable.
  • Enums. Technology, SpeedTier and Confidence are string enums, so runtime checks (result.confidence === Confidence.High) and string comparisons both work.
  • Dual module support. ESM (import) and CommonJS (require) builds are shipped under dist/esm and dist/cjs.

Development

npm install
npm run build     # tsc (ESM + CJS) + dist marker files
npm test          # vitest: mocked-fetch unit tests + live integration (see below)

Integration tests hit the local dev API at http://127.0.0.1:8788 and are skipped unless NETCOV_API_KEY is set. The CI key lives in the gitignored .env.test:

node --env-file=.env.test node_modules/.bin/vitest run

Never commit the API key.

License

MIT — code. Geocoding data is © Istat – Agenzia delle Entrate, Archivio Nazionale dei Numeri Civici e delle Strade Urbane (ANNCSU), CC BY 4.0.