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

@gamesheet-inc/cache-invalidation-contract

v0.1.0

Published

The stats widget's cache-invalidation contract: cache tags and URL rules, generation keys, and the cache-invalidate message v1.

Readme

Cache invalidation contract

@gamesheet-inc/cache-invalidation-contract is this contract's only copy. The edge-router and the widget app import it through the app-stats-widget-next workspace; cache-invalidation-service and project-forge pin a published version. The data-write-gateway's Go struct (src/Shared/Invalidation/message.go) mirrors §3 by hand, so a message change lists it among the consumers to update. Design: docs/superpowers/specs/2026-09-28-cache-invalidation-contract-package-design.md in app-stats-widget-next, which builds on the cache-invalidation design spec §7.

1. Cache tags (edge-router ↔ service)

Every response the edge Worker caches carries cf.cacheTags, derived from the public URL only (the Worker's own 60 s build-id lookup is the single untagged entry; see the edge-router constraints doc):

  • <env>-season-<id>
  • <env>-league-<id>
  • <env>-game-<id>
  • <env>-team-<id>
  • <env>-player-<id>
  • <env>-widget-config-<id>
  • constant <env>-stats-widget on every cached response

<env> is one of dev | qa | prod — the Worker's TAG_ENV var and the service's TAG_ENV var, which must match per env (the apex and next. prod Workers both use prod). Ids are decimal integers exactly as they appear in URLs. Tags are emitted lower-case; Cloudflare matches them case-insensitively. A Worker with no valid TAG_ENV emits no tags at all — never an unprefixed one.

Why the prefix: Cloudflare cache tags are zone-wide, dev/qa/prod share gamesheetstats.com, and the three databases have overlapping numeric id spaces. Without it a dev purge of season-19483 would evict prod's season 19483.

<env>-stats-widget is a break-glass lever (purge one env's whole widget edge cache) with its own runbook in the service repo. It is never exposed in a UI.

2. Generation keys (widget ↔ service; the widget's Upstash instance, per env)

  • gen:season:<id>
  • gen:league:<id>
  • gen:game:<id>
  • gen:player:<id>
  • gen:widget-config:<id>

Integer counters. A missing key reads as 0. The service is the only writer (INCR + EXPIRE 30 days on every bump); the widget only reads. There is no gen:team:* — every team-scoped widget key also carries its season, so the season counter covers it.

3. cache-invalidate topic payload v1 (publishers ↔ service)

{
	"v": 1,
	"source": "data-write-gateway.ScheduleGame.update",
	"reason": "optional free text",
	"seasons": [19483],
	"games": [123],
	"teams": [456, 789],
	"leagues": [],
	"players": [],
	"widgetConfigs": []
}

Message attributes: schema: cache-invalidate.v1, source. All arrays optional. Publishers send integer ids; the schema also accepts numeric strings of up to 19 digits and parses them. Every id must parse to a positive integer of at most 9007199254740991 (Number.MAX_SAFE_INTEGER); anything else is refused. Publishers never send tags or keys.

Exports

  • Tags: TAG_ENVS, TagEnv, parseTagEnv, SCOPE_TAG_KINDS, ScopeTagKind, STATS_WIDGET_TAG, tagFor, breakGlassTag.
  • URL rules: ScopeRef, resolveScopes, resolveCacheTags.
  • Generation keys: GEN_KINDS, GenKind, GEN_KEY_PREFIX, genKeyFor, GEN_TTL_SECONDS.
  • Message v1: CACHE_INVALIDATE_TOPIC, CACHE_INVALIDATE_SCHEMA, MESSAGE_FIELDS, MessageField, ScopeIdSchema, CacheInvalidateV1Schema, CacheInvalidateV1. zod is a required peer.

Changelog

A change to any exported value or rule is a new version. Each entry names the consumers that must move: the edge-router and the widget app (same PR, through the workspace), cache-invalidation-service and project-forge (a pin bump), and the data-write-gateway's Go struct (by hand, for a message change).

  • 0.1.0 — First release. Moves the edge-router's cache-tags.ts, the widget's generation-key names and the service's contract.ts, idSchema and cacheInvalidateV1Schema here, with no change to any tag, key or accepted message.