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

@webgrip/edge-analytics

v0.1.0

Published

Counterscale reporting from the edge for Webgrip static sites: a Cloudflare Worker that reports pageviews server-side, so no script in the page and nothing for a blocker to stop.

Readme

@webgrip/edge-analytics

Counterscale reporting from the edge of a Workers Static Assets site. No script in the page, no cookie, and nothing for a blocker to stop.

What this is, and what it is not

Counterscale is the analytics product: MIT, self-hosted on Cloudflare Workers and Analytics Engine, cookieless. It is the collector and the dashboard.

This package is the ten lines that both Webgrip sites would otherwise each carry their own copy of: a Worker that reports the pageview and hands the request straight back to the asset binding.

It uses Counterscale's server module rather than the browser snippet. A script in the page is blocked for exactly the audience these sites care about; on twente.dev the existing RUM beacon is blocked by the maintainer's own browser. Reporting from inside the request that serves the page leaves nothing to block.

Two fields are deliberately never sent even though the tracker accepts them: ip and userAgent. Neither is needed to answer which channel filled the room, and not collecting is the only durable way to not leak.

Deploying the collector, once

Counterscale is one Worker with its own Analytics Engine dataset and dashboard, deployed once for every site that reports to it. Webgrip runs it at counterscale.webgrip.dev, named after the tool rather than after what it does, so analytics.webgrip.dev stays free for whatever comes later.

npx @counterscale/cli

The installer asks for a Cloudflare API token with Workers and Analytics Engine permissions, and for a dashboard password. Point the Worker at the hostname above, then note the reporting endpoint: https://counterscale.webgrip.dev/collect.

It cannot run on a homelab. Analytics Engine is a Cloudflare product with no self-hosted build, and that is the right side of the trade anyway: the collector sits in the request path of a public site, so it has to be somewhere that is always up. The homelab is where the archive belongs, not the intake.

Each site reports with its own siteId, so one deployment serves several sites and the dashboard filters between them.

Wiring a site

1. src/worker.mjs:

import { createAnalyticsFetch } from '@webgrip/edge-analytics';

export default {
  fetch: createAnalyticsFetch({
    siteId: 'twente-dev',
    reporterUrl: 'https://analytics.webgrip.dev/collect',
  }),
};

2. wrangler.toml:

main = "src/worker.mjs"

[assets]
directory = "./dist"
binding = "ASSETS"
html_handling = "auto-trailing-slash"
not_found_handling = "404-page"

# Page paths only. Every path listed here costs a Worker invocation; every path
# excluded stays a free, unmetered static asset request. A pageview pulls one
# document and a dozen assets, so excluding the asset directories is the
# difference between one invocation per view and thirteen. This is the cost
# knob; get it wrong and the free tier stops being free.
run_worker_first = [
  "/*",
  "!/_astro/*",
  "!/brand/*",
  "!/pagefind/*",
  "!/fonts/*",
]

3. pnpm add @counterscale/tracker — it is a peer dependency, so the site pins the tracker version it deploys.

What it costs

Nothing, with room. The Workers free plan allows 100,000 invocations a day and Analytics Engine 100,000 data points and 10,000 read queries a day. With run_worker_first scoped as above that is 100,000 pageviews a day.

Grafana

Counterscale ships a dashboard, and its data also sits in a plain Analytics Engine dataset, which exposes a SQL API over HTTP that Grafana can query directly. Use Counterscale's dashboard for the standard traffic questions and Grafana where the numbers have to sit beside everything else you already watch.

SELECT blob4 AS source, blob5 AS medium, blob6 AS campaign, SUM(_sample_interval) AS visits
FROM metricsDataset
WHERE timestamp > NOW() - INTERVAL '30' DAY
GROUP BY source, medium, campaign
ORDER BY visits DESC

Confirm the blob positions against Counterscale's own schema before trusting a panel; they are ordered by its collector, not by this package. And use SUM(_sample_interval) rather than COUNT(*): Analytics Engine samples under load and the sample interval is the weight that undoes it. At these volumes nothing is sampled and the two agree, which is exactly why the wrong one is easy to ship and hard to notice.

Retention is ninety days

Analytics Engine keeps data for ninety days, and that is shorter than the questions these sites ask. Anything meant to outlive the window has to be copied into a store you own before it closes. Plan for that when you wire the dashboards, not when the first quarter falls off the end.