Caching
Read this page in the documentation
Caching src/cache provides a two-tier cache: L1 is a bounded in-process LRU map, L2 is a Redis cluster shared by every process. RedisClusterCache is the shipped implementation of the CacheBackend contract; AbstractCacheManager is the base class to extend if you want a different L2 (Memcached, DynamoDB, an HTTP cache — anything). Like the query optimizers, this module is opt-in and not wired into the query pipeline. Nothing is cached until you cache it. That keeps correctness in your hands: the ORM never serves you a row it did not just read. Setting it up Type names at the root. Five of this module's type names already existed at the package root with different meanings, so the cache module's versions are aliased there: CacheEntry → CacheManagerEntry, CacheManagerOptions → CacheManagerConfig, L1CacheOptions → CacheManagerL1Options, RedisClusterCacheOptions → RedisClusterCacheConfig, RedisClusterNode → RedisClusterCacheNode. To import them under their real names, use the ts-prorm-orm/cache subpath instead of the root. (ts-prorm-orm/audit, /graph and /prisma-migrate exist for the same reason.) ioredis is loaded lazily and is not a dependency of the package — install it yourself (npm i ioredis) if you want L2. With no clusterNodes the cache logs a warning and runs L1-only, which is a perfectly reasonable single-process configuration. Options Option | Default | Meaning | --- | --- | --- | l1.enabled | true | In-process LRU layer. | l1.maxEntries | 1000 | LRU capacity. | l1.ttl | 60 | L1 entry lifetime, seconds. | l2.enabled | true | Redis layer. | l2.defaultTtl | 3600 | Redis SETEX lifetime, seconds. | key.prefix | 'orm:cache:' | Prepended to every key. | key.separator | ':' | Separator for composed keys. | readFromReplicas | false | Route GETs to replicas. | maxConnections | 10 | Connection pool size. | connectTimeout / commandTimeout | 10000 / 5000 | Milliseconds. | db | 0 | Redis database index. | retryStrategy | backoff to 3 s, give up after 10 tries | Cluster reconnect policy. | Read and write paths Invalidation is per model, not per row: a write to one user clears the cached reads for User, because the cache cannot know which cached queries that row would have matched. The API Every backend implements the same ten methods: Method | Behaviour | --- | --- | get(key) | L1 first; on an L1 miss, read L2 and promote the value into L1. | set(key, value, ttl?) | Writes both layers. L2 uses SETEX key ttl value. | del(key) | Removes from both. | exists(key) | Presence check across both. | mget(keys) / mset(entries) | Batched forms. | invalidatePattern(pattern) | Glob-style invalidation across both layers; returns how many keys went. | clear() | Everything under the prefix, and resets stats. | disconnect() / isConnected() | Lifecycle. | Values are strings — serialise structured data yourself. L2 failures never throw. A Redis error is logged and treated as a miss, so a cache outage degrades to going to the database rather than taking your application down. Invalidation There is no automatic invalidation — the cache does not know when you write. The usual pattern is to invalidate from a model hook, so every write path is covered whether or not the caller remembered: invalidatePattern uses Redis KEYS for the L2 sweep, which scans the whole keyspace — fine for occasional invalidation, but avoid it on a hot path against a large Redis. Prefer targeted del() calls where you can name the key. Design your keys so a pattern can express what a write invalidates: Statistics l1Hits versus l2Hits is the number to watch: a low l1Hits share with a healthy overall hitRate means your L1 is too small or its TTL too short for the access pattern. clearL1() empties just the in-process layer — useful in tests, and after a deploy that changes the shape of a cached value. Writing another backend Extend AbstractCacheManager, which gives you option normalisation, key prefixing and stats bookkeeping, and implement the ten abstract methods: Anything satisfying CacheBackend is interchangeable at the call site. Which cache do I want? Need | Use | --- | --- | Share cached values across processes or machines | RedisClusterCache (this guide) | Cache result sets inside one process, briefly | QueryResultCache — see Query optimization | Find the statements worth caching at all | SlowQueryLogger + analyzeQuery() | Avoid repeat queries within one request | Load once and pass it down; no cache needed | Related reading Query optimization — the in-process result cache Hooks — where invalidation belongs Redis store — using Redis as a data store rather than a cache