Skip to content

Choosing a Driver

@warlock.js/cache ships eight drivers. They all speak the same API — you can swap one for another without touching call sites — but they trade off durability, scale, and capabilities. This page helps you pick.

| Your situation | Driver | |---|---| | Local dev, single process | memory | | Session-style data that should stay alive while used | memoryExtended | | Bounded memory footprint (cap the entry count) | lru | | Small persistent cache, no server (CLI, build cache) | file | | Production, multiple instances, shared cache + locks | redis | | You already run Postgres, or need vector similarity | pg | | Disable caching without changing code | null | | Asserting cache behavior in tests | mock |

  1. Running more than one instance? You need a shared store — go redis (or pg if Postgres is already in your stack). In-memory drivers are per-process, so each instance would hold its own copy and invalidation wouldn’t propagate.
  2. Need vector similarity in production? pg with the vector config block — real pgvector index, same cache.similar() code.
  3. Single process, just want speed? memory. Add maxSize to cap it, or reach for lru when you want a hard entry ceiling with eviction.
  4. Need it to survive a restart but don’t want a server? file.

| Driver | Survives restart | Multi-instance | Native locks | Native lists | Similarity | Eviction | |---|:---:|:---:|:---:|:---:|:---:|:---:| | memory | ❌ | ❌ | emulated | read-modify-write | brute force | maxSize (LRU) | | memoryExtended | ❌ | ❌ | emulated | read-modify-write | brute force | maxSize (LRU) | | lru | ❌ | ❌ | emulated | read-modify-write | brute force | capacity (LRU) | | file | ✅ | ⚠️ same host | emulated | read-modify-write | ❌ | TTL only | | redis | ✅ | ✅ | native NX/XX | read-modify-write¹ | ❌ (backlog) | TTL / Redis policy | | pg | ✅ | ✅ | emulated | read-modify-write | ✅ pgvector | TTL only | | null | n/a | n/a | n/a | n/a | returns [] | n/a |

¹ Lists are stored as a single JSON entry and mutated read-modify-write on every driver today. Native Redis list commands (LPUSH / LRANGE / LTRIM) are planned for v2.1.

:::note update() / merge() support The file driver throws CacheUnsupportedError for update() and merge() (no atomic read-modify-write primitive on disk yet). All other drivers support them. See Update & Merge. :::

  • memory — an in-process Map. Fastest option, zero setup, gone on restart. The default for dev and single-instance apps. Optional maxSize evicts the least-recently-used entry past the limit.
  • memoryExtendedmemory with sliding expiration: every read refreshes the entry’s TTL. Use it for data that should stay cached as long as it’s actively used (session-ish reads) and expire only after a quiet period.
  • lru — a hard capacity ceiling (default 1000) with least-recently-used eviction. Reach for it when bounding memory matters more than keeping every entry.
  • file — JSON-on-disk. Survives restarts without running a server, but it’s single-host and doesn’t support update()/merge(). Good for CLIs and build caches.
  • redis — the production workhorse. Shared across instances, native SET NX/XX for real distributed locks, native list commands for O(1) list ops. Similarity isn’t wired yet.
  • pg — Postgres-backed and durable. The only driver with a real similarity index (pgvector, HNSW or IVFFlat). Bring your own pg.Pool/Client; the driver doesn’t own the connection lifecycle.
  • null — a black hole. Every write is a no-op, every read a miss. Swap it in to disable caching globally without touching call sites.
  • mock — an in-memory store plus a callLog of every operation, for behavioral assertions in tests. See Testing.

You’re not locked into one. Register several and route per call with the driver option — keep your default hot path in memory while sending durable or audit writes to redis:

await cache.set("user.1", user, "1h"); // default driver
await cache.set("audit.event", event, { driver: "redis", ttl: "30d" });

See Configuration for registering drivers and Set Options for per-call overrides.