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

@casoon/trackr

v0.3.0

Published

Privacy-first, GDPR-native analytics for static sites.

Readme

@casoon/trackr

Privacy-first, GDPR-native analytics for static sites. No cookies, no persistent IDs, self-hosted.

Live Demo

UTM-Parameter Test:

Features

  • Privacy by Default - No cookies, no localStorage, no fingerprinting
  • Lightweight - Client script < 1KB gzipped
  • Self-Hosted - Your data stays on your infrastructure
  • UTM Tracking - Automatic extraction of campaign parameters
  • SPA Support - Auto-tracks pushState/replaceState/hashchange navigation
  • OS Detection - Server-side detection (Android, iOS, Windows, macOS, Linux, ChromeOS)
  • Bot Filtering - Common crawlers and headless browsers excluded
  • Flexible Storage - Postgres, external API, GA4 Measurement Protocol, or fan-out multi-adapter
  • Webhook - Forward events to any HTTP endpoint with HMAC-SHA256 signing and retry
  • Batching - Buffer events and flush in batches (size- or time-based) to reduce network calls
  • Pixel Tracking - Transparent GIF endpoint for email/no-JS contexts
  • Astro-First - Designed for Astro, works with any static site

Installation

npm install @casoon/trackr

Quick Start

1. Add the client script

---
// src/components/Analytics.astro
---
<script>
  import { init } from "@casoon/trackr/client";
  init({ endpoint: "/api/track" });
</script>

Or via CDN (no build step):

<script src="https://unpkg.com/@casoon/trackr"></script>
<script>
  trackr.init({ siteId: "your-site-id", endpoint: "https://your-api.com/collect" });
</script>

2. Create the API endpoint

// src/pages/api/track.ts
import { createHandler } from "@casoon/trackr/server";
import { postgres } from "@casoon/trackr/storage/postgres";

const handler = createHandler({
  storage: postgres(import.meta.env.DATABASE_URL),
  privacy: { anonymizeIp: true, stripPii: true },
  botFilter: true
});

export const POST = async ({ request }) => handler(request);

3. Set up the database

Run the bundled migration against your Postgres instance:

psql "$DATABASE_URL" -f node_modules/@casoon/trackr/migrations/001_create_trackr_events.sql

This creates the trackr_events table with all required columns and indexes. See Production Setup for details.

Custom Events

import { track } from "@casoon/trackr/client";

track("signup_click", { plan: "pro" });

Storage Adapters

Postgres (recommended for GDPR compliance)

import { postgres } from "@casoon/trackr/storage/postgres";
const storage = postgres(process.env.DATABASE_URL);

External API

import { api } from "@casoon/trackr/storage/api";
const storage = api({
  url: "https://plausible.io/api/event",
  transform: (event) => ({ ... })
});

GA4 Measurement Protocol (optional, privacy proxy)

Forwards events server-side — the GA script never loads in the user's browser.

import { ga4 } from "@casoon/trackr/storage/ga4";
const storage = ga4({
  measurementId: "G-XXXXXXXXXX",
  apiSecret: process.env.GA4_API_SECRET,
  nonPersonalizedAds: true,   // default true
  stripQueryParams: true,      // strip query strings from URLs
  debug: false
});

GDPR note: GA4 forwarding sends anonymized session IDs. Enable only if you have a legal basis or user consent for GA4 data transfer to Google's US servers.

Webhook

Forward events to any HTTP endpoint. Supports HMAC-SHA256 payload signing and retry with exponential backoff.

import { webhook } from "@casoon/trackr/storage/webhook";

const storage = webhook({
  url: "https://api.example.com/events",
  secret: process.env.WEBHOOK_SECRET,        // signs payload → X-Trackr-Signature header
  headers: { Authorization: "Bearer ..." },
  retry: { attempts: 3, baseDelay: 500 },    // retries on 5xx with exponential backoff
});

Multi (fan-out to multiple adapters)

import { multi } from "@casoon/trackr/storage/multi";
import { postgres } from "@casoon/trackr/storage/postgres";
import { ga4 } from "@casoon/trackr/storage/ga4";

const storage = multi(
  postgres(process.env.DATABASE_URL),
  ga4({ measurementId: "G-XXXXXXXXXX", apiSecret: process.env.GA4_API_SECRET })
);

Batch (buffer & flush)

Wraps any adapter. Buffers events in memory and flushes on size threshold or time interval. Uses saveBatch() when the wrapped adapter supports it (e.g. webhook), otherwise calls save() for each event.

import { batch } from "@casoon/trackr/storage/batch";
import { webhook } from "@casoon/trackr/storage/webhook";

const storage = batch(
  webhook({ url: "https://api.example.com/events", secret: "s3cret" }),
  { maxSize: 20, maxWait: 10_000 }          // flush every 20 events or 10s
);

// Graceful shutdown
process.on("SIGTERM", () => storage.flush());

Pixel Tracking

For email open tracking or no-JS environments:

import { createPixelHandler } from "@casoon/trackr/server/pixel";
import { postgres } from "@casoon/trackr/storage/postgres";

const pixelHandler = createPixelHandler({
  storage: postgres(process.env.DATABASE_URL),
  privacy: { anonymizeIp: true }
});

// Returns a transparent 1x1 GIF + records a pageview
export const GET = async ({ request }) => pixelHandler(request);
<img src="https://your-api.com/pixel.gif?url=https://your-site.com/page" width="1" height="1" />

Privacy Features

  • IP Anonymization - Last octet removed before any processing
  • PII Filtering - Email, phone, tokens stripped from URLs
  • No Cookies - Session derived from anonymized IP + UA + date (daily rotating)
  • Bot Filtering - Common crawlers excluded
  • UTM Extraction - Campaign params captured client-side (prefix stripped: utm_source → source)
  • OS Detection - Server-side from User-Agent, not stored raw

Production Setup

Database migration

The migration file is included in the package at migrations/001_create_trackr_events.sql. It creates the trackr_events table and all recommended indexes:

CREATE TABLE IF NOT EXISTS trackr_events (
  id          UUID        DEFAULT gen_random_uuid() PRIMARY KEY,
  ts          TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  type        TEXT        NOT NULL CHECK (type IN ('pageview', 'event')),
  name        TEXT,
  url         TEXT        NOT NULL,
  referrer_domain TEXT,
  country     TEXT,
  device      TEXT        CHECK (device IN ('desktop', 'mobile', 'tablet')),
  browser     TEXT,
  os          TEXT,
  session_id  TEXT,
  utm         JSONB       DEFAULT NULL,
  props       JSONB       DEFAULT '{}'
);

Apply it with any Postgres client:

# psql
psql "$DATABASE_URL" -f node_modules/@casoon/trackr/migrations/001_create_trackr_events.sql

# or pipe it directly
node -e "require('fs').readFileSync('node_modules/@casoon/trackr/migrations/001_create_trackr_events.sql','utf8')" \
  | psql "$DATABASE_URL"

Recommended indexes

The migration already includes these indexes:

| Index | Purpose | |-------|---------| | idx_trackr_events_ts | Time-range queries (most common) | | idx_trackr_events_type | Filter pageviews vs. custom events | | idx_trackr_events_url | Per-page analytics | | idx_trackr_events_utm | GIN index on utm JSONB for campaign attribution | | idx_trackr_events_props | GIN index on props JSONB for custom event filtering |

Data retention

trackr stores no PII — session IDs rotate daily and IPs are never persisted. For compliance or storage reasons you may still want to schedule a periodic cleanup:

-- Delete events older than 90 days (run via pg_cron or a scheduled job)
DELETE FROM trackr_events WHERE ts < NOW() - INTERVAL '90 days';

Example analytics queries

-- Pageviews per day (last 30 days)
SELECT DATE_TRUNC('day', ts) AS day, COUNT(*) AS views
FROM trackr_events
WHERE type = 'pageview' AND ts > NOW() - INTERVAL '30 days'
GROUP BY 1 ORDER BY 1;

-- Top pages
SELECT url, COUNT(*) AS views
FROM trackr_events
WHERE type = 'pageview'
GROUP BY url ORDER BY views DESC LIMIT 20;

-- UTM source breakdown
SELECT utm->>'source' AS source, COUNT(*) AS sessions
FROM trackr_events
WHERE utm IS NOT NULL
GROUP BY 1 ORDER BY 2 DESC;

-- Custom event counts
SELECT name, COUNT(*) AS fires
FROM trackr_events
WHERE type = 'event'
GROUP BY name ORDER BY fires DESC;

License

LGPL-3.0-or-later