Skip to content

nest-native/cache

Repository files navigation

stalefree

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.

@stalefree/core on npm @nest-native/cache on npm MIT license Core test coverage

Note

Published and following semver. Version history is in the changelog; full documentation lives at nest-native.dev/cache.

The idea

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:

  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.
  2. On Postgres, invalidation is transactional. publishInTx runs pg_notify inside 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}`]);

Packages

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

Coherence across instances: pick your bus

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.

See it running

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).

Development

  • npm test / npm run test:cov — hermetic suite, 100% coverage gate on the core
  • npm 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
  • The binding constitution is GUIDELINES_NEST_CACHE.md; main is PR-only

MIT licensed. Part of the nest-native family. Not affiliated with the NestJS core team.

About

Tag-based cache invalidation through the database you already have — @stalefree/core (framework-agnostic, Postgres LISTEN/NOTIFY bus, no Redis) + @nest-native/cache (NestJS adapter)

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors