cache-coherence-watcher
v1.0.1
Published
Standalone MongoDB change-stream watcher for cache-coherence: catches writes from any source, including ones that never touch the app's own Mongoose model.
Readme
cache-coherence-watcher
Standalone MongoDB change-stream watcher for cache-coherence: catches cache-invalidating writes from any source, including ones that never touch your app's own Mongoose model.
Why this exists
cache-coherence's Mongoose hook auto-invalidates on every write through your app's own model — but it structurally cannot see writes from anywhere else: another service touching the same database, a raw driver script, a one-off mongosh fix, or a filter-based updateMany whose middleware never tells the hook which documents actually changed.
This package is the belt-and-suspenders layer: a separate process that reads MongoDB's change stream directly — the database's own internal write log — and evicts the corresponding cache entry for every changed document, regardless of what wrote it. A change-stream event carries a real document id per affected document even for bulk writes, closing the exact gap the in-process hook can't.
Requires MongoDB running as a replica set (even a single-node one — change streams need the oplog, which only exists under replication).
Install
As a standalone CLI:
npm install -g cache-coherence-watcherOr embedded in your own process:
npm install cache-coherence-watcherRun it: CLI
// watcher-config.json
{
"mongoUri": "mongodb://localhost:27017/my-app?replicaSet=rs0",
"dbName": "my-app",
"redisUrl": "redis://localhost:6379",
"defaultNamespace": "my-app",
"collections": [{ "name": "products" }],
"broadcast": true,
"httpPort": 3100
}cache-coherence-watcher ./watcher-config.jsondefaultNamespace must match the namespace your app's own TwoTierCache uses — that's what makes the key this watcher computes for a document line up with the key your app already cached it under.
Run it: embedded
import { startWatcher } from "cache-coherence-watcher";
const handle = await startWatcher({
mongoUri: process.env.MONGO_URI!,
dbName: "my-app",
redisUrl: process.env.REDIS_URL!,
defaultNamespace: "my-app",
collections: [{ name: "products" }],
});
// later, on shutdown:
await handle.close();What you get
- Resume-token persistence — a restarted watcher resumes from where it left off (a small Mongo collection tracks its position in the change stream), not from "now." If the oplog has rolled past a saved token by the time it reconnects, the token is dropped and the stream resumes from "now" instead — a bounded staleness window rather than hanging forever.
- Exponential backoff with full jitter on stream errors, so a fleet of watcher instances recovering from a shared outage doesn't retry in lockstep.
/health({ status, streamsOpen }, live per-request) and/metrics(Prometheus, viahttpPort) for orchestrator probes and scraping.- Broadcast by default (
broadcast: true) — evictions are published so app instances' local cache tiers drop the stale copy too, not just the shared Redis tier.
Discovery: explicit config vs. auto-discovery
Two ways to tell the watcher which collections to watch, config wins if present:
- Explicit
collectionslist (shown above) — deterministic, what every current example and test uses. - Self-describing Mongo registry — omit
collectionsand the watcher polls a__cache_coherence_registry__collection instead. Currently only the read side of this is implemented — nothing incache-coherenceyet writes to that collection automatically, so use explicit config until that's wired up. See the main repo's Known Limitations for the full, honest list.
Full docs
Architecture, the change-stream pipeline, resume-token/backoff details, and real benchmark numbers (time-to-consistency with the watcher on vs. off): github.com/Prajin0802/cache-coherence.
MIT licensed.
