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

@stalefree/core

v0.1.0

Published

Framework-agnostic tag-based cache invalidation through the database you already have — in-memory L1, optional Drizzle L2, and an invalidation bus over Postgres LISTEN/NOTIFY (transactional: delivered on commit, dropped on rollback)

Readme

@stalefree/core

The idea

Caching expensive reads is easy; knowing when to throw them away is the hard part — especially across instances. The usual answer bolts on Redis for the pub/sub. stalefree's answer: the invalidation travels through the database your instances already share.

Two rules make it safe:

  1. Every entry has a TTL — no exceptions. The bus is best-effort by contract, so the TTL is the delivery backstop: a lost invalidation message means stale until TTL, never stale forever. (The API rejects infinite TTLs; a cache whose correctness depends on a bus message arriving is a design bug.)
  2. On Postgres, invalidation can be transactional. publishInTx runs pg_notify inside your business transaction: Postgres delivers it on commit and drops it on rollback. "Wrote the row, crashed before invalidating, stale forever" — the dual-write problem applied to caches — cannot happen.

Usage

npm install @stalefree/core
# drizzle-orm only if you use the L2 stores (optional peer)
import { StalefreeCache } from '@stalefree/core';

const cache = new StalefreeCache({ defaultTtlMs: 30_000 });

// read-through with single-flight: concurrent misses share ONE loader run
const project = await cache.wrap(
  `project:${id}`,
  () => db.select()...,
  { tags: [`org:${orgId}`, `project:${id}`] },
);

// after a mutation: evict every entry carrying the tag — here and, with a
// bus configured, on every other instance
await cache.invalidateTags([`project:${id}`]);

Coherence across instances: pick your bus

| Deployment | Bus | Import | | --- | --- | --- | | One process | (none needed — it's coherent by definition) | @stalefree/core | | Several processes, one machine (the classic app + worker split; also SQLite's whole story) | Unix-socket hub/peer mesh with crash re-election | @stalefree/core/socket | | Several machines sharing Postgres | LISTEN/NOTIFY, with transactional publish | @stalefree/core/postgres |

// Postgres, cross-machine — the flagship
import { PostgresInvalidationBus } from '@stalefree/core/postgres';

const bus = new PostgresInvalidationBus({
  connect: () => new pg.Client({ connectionString, keepAlive: true }),
  db,                       // your base drizzle handle (fire-and-forget publishes)
});
bus.start();
const cache = new StalefreeCache({ bus, defaultTtlMs: 30_000 });

// transactional invalidation: atomic with your write. With an L2 store
// configured, delete its rows IN THE SAME TX (publishInTx only evicts L1s —
// without this line every instance would refill stale from L2):
await db.transaction(async (tx) => {
  await tx.update(projects)...;
  await store.invalidateTagsInTx(tx, [`project:${id}`]); // L2 dies with the commit
  await bus.publishInTx(tx, { tags: [`project:${id}`] }); // L1s evict on commit
});

A missed notification (listener reconnecting, worker down) costs staleness until TTL, nothing more — the same backstop philosophy on every tier.

Optional shared L2

Add a Drizzle-backed shared tier so a fresh instance starts warm and instances share loader work — @stalefree/core/{sqlite,postgres,mysql} export a stalefreeCacheTable() factory (add it to your schema, generate a migration) and a store:

import { PostgresCacheStore, stalefreeCacheTable } from '@stalefree/core/postgres';
export const cacheTable = stalefreeCacheTable();
const cache = new StalefreeCache({ store: new PostgresCacheStore(db, cacheTable), bus, ... });

On Postgres, consider hand-editing the generated migration to CREATE UNLOGGED TABLE — cache rows are transient, and skipping WAL roughly doubles write throughput at the cost of an empty (not wrong) table after a crash. Schedule store.prune(Date.now()) to clear expired rows.

Semantics worth knowing

  • Fail-open: store/bus failures are reported to onError and the call proceeds (a degraded cache is a slower app, never a broken one). Loader errors are yours: they propagate untouched and cache nothing.
  • Keys/tags are allow-listed ([A-Za-z0-9_:.-], length-capped) — they travel through SQL, NOTIFY payloads, and socket frames, so the charset is the injection defense. Put the tenant dimension IN the key (org:7:project:42), never secrets.
  • undefined is never cached (indistinguishable from a miss); null is.
  • Cross-instance stampede control is out of scope for v1 — single-flight is per-process.

MIT licensed. The @nest-native/cache adapter wires this into NestJS. Part of the nest-native family; not affiliated with the NestJS core team.