Tag-based cache invalidation through the database you already have — an in-memory L1 kept coherent across instances by an invalidation bus (Postgres LISTEN/NOTIFY first), with an optional Drizzle L2. No Redis.
Note
Published and following semver. Version history is in the changelog; full documentation lives at nest-native.dev/cache.
Caching expensive reads is easy; knowing when to throw them away — on every instance — is the hard part. 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:
- 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.
- On Postgres, invalidation is transactional.
publishInTxrunspg_notifyinside your business transaction: delivered on commit, dropped on rollback. "Wrote the row, crashed before invalidating, stale forever" — the dual-write problem applied to caches — cannot happen.
import { StalefreeCache } from '@stalefree/core';
const cache = new StalefreeCache({ defaultTtlMs: 30_000 });
// read-through with single-flight; reads declare tags
const project = await cache.wrap(
`org:${orgId}:project:${id}`,
() => repo.findById(id),
{ tags: [`org:${orgId}:projects`, `project:${id}`] },
);
// mutations evict by tag — locally, in L2, and on every other instance
await cache.invalidateTags([`project:${id}`]);| Package | What it is |
|---|---|
@stalefree/core (npm) |
The framework-agnostic, zero-dependency engine: StalefreeCache (L1 LRU + reverse tag index, wrap single-flight, fail-open), the invalidation buses (., ./socket, ./postgres), and the Drizzle L2 stores (./sqlite, ./postgres, ./mysql) |
@nest-native/cache (npm) |
The thin NestJS DI adapter: CacheModule.forRoot/forRootAsync + CacheService; NestJS 10, 11, and 12 |
| Runtime | Supported line |
|---|---|
| Node.js | >=22 (>=22.12 when paired with NestJS 12 — its own require(esm) floor) |
NestJS (@nest-native/cache peer) |
^10.0.0 || ^11.0.0 || ^12.0.0 |
drizzle-orm (@stalefree/core optional peer) |
^0.44.0 || ^0.45.0 |
Each NestJS major in that range is exercised in CI, not just declared: 10 and
11 typecheck the adapter, and 12 gets the full run (typecheck + both suites)
on an install that provably resolves @nestjs/*@12 inside the adapter
workspace. The devDependencies and the lockfile stay on 11.x on purpose, so
both ends of the range are tested claims — see the
support policy.
| Deployment | Bus | Import |
|---|---|---|
| One process | (none needed) | @stalefree/core |
| Several processes, one machine (the app + worker split; 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 |
No tier requires infrastructure you don't already run. See the
coherence docs for the recipes — including
the mandatory invalidateTagsInTx + publishInTx pairing when an L2 store is
configured, and the MySQL COLLATE utf8mb4_bin migration note.
The nest-native reference app
runs this as one of its nine chapters: reads cached at the API seam, mutations
invalidating by tag, and an e2e spec that proves freshness against a
deliberately long TTL — invalidation, provably not expiry. A two-process
bare-Express sample lives in sample/
(the framework-neutrality proof: no @nestjs/* anywhere).
npm test/npm run test:cov— hermetic suite, 100% coverage gate on the corenpm run test:nestjs— the adapter's own suite; the enforced coverage gate measures@stalefree/core, the thin DI shell is exercised herenpm run infra:up && npm run test:full— adds gated round-trips against real Postgres + MySQL (Docker, local-only)npm run sample— the two-process invalidation smoke- CI adds a NestJS 12 compatibility leg (
nestjs-latest-major):@nestjs/*@^12installed on top of the 11.x lockfile with--no-save, then the adapter typecheck and both suites again - The binding constitution is GUIDELINES_NEST_CACHE.md;
mainis PR-only
MIT licensed. Part of the nest-native family. Not affiliated with the NestJS core team.