Cache Manager
The cache manager is the main interface for working with cache in your application. It manages drivers, handles configuration, and provides a unified API for all cache operations.
import { cache } from "@warlock.js/cache";Before using the cache, configure and initialize it:
import { cache } from "@warlock.js/cache";import cacheConfigurations from "./config/cache";
// Set configurationscache.setCacheConfigurations(cacheConfigurations);
// Initialize (connects to the default driver)await cache.init();:::info Important
@warlock.js/cache requires manual setup. Unlike the Warlock framework where initialization is automatic, you must call setCacheConfigurations() and init() before using any cache operations.
:::
Basic Operations
Section titled “Basic Operations”The cache manager provides core cache operations that work with all drivers.
Store a value in cache. The third argument accepts three shapes:
import { cache, CACHE_FOR } from "@warlock.js/cache";
// 1. Number of secondsawait cache.set(`products.${productId}`, product, CACHE_FOR.ONE_HOUR);await cache.set(`products.${productId}`, product, 3600);
// 2. Human-readable duration string (parsed via ms)await cache.set(`products.${productId}`, product, "1h");await cache.set(`session.${sessionId}`, session, "30m");await cache.set(`report.${reportId}`, report, "7d");
// 3. Rich options objectawait cache.set(`products.${productId}`, product, { ttl: "1h", tags: ["products"], onConflict: "create",});If the third argument is omitted, the driver’s default TTL is used, or the value is stored forever (Infinity).
See Set Options for the full options-object reference — ttl, expiresAt, tags, onConflict, and per-call driver overrides.
Retrieve a value:
const product = await cache.get(`products.${productId}`); // null if not foundRemove
Section titled “Remove”Delete a specific key:
await cache.remove(`products.${productId}`);Clear the entire cache:
await cache.flush();Check if a key exists without fetching:
const exists = await cache.has(`products.${productId}`);Get and remove in one operation:
const verifyToken = await cache.pull(`users.${userId}.verify-token`);Forever
Section titled “Forever”Store without expiration:
await cache.forever("app.config", { version: "1.0.0" });// Remove manually when no longer neededawait cache.remove("app.config");Remember (get or compute)
Section titled “Remember (get or compute)”remember() gets a value from cache, or if it doesn’t exist, executes a callback to generate it, caches the result, and returns it. It also prevents cache stampedes within a single process — concurrent callers for the same key share the in-flight promise.
const products = await cache.remember( `products.category.${categoryId}`, "30m", async () => db.products.findByCategory(categoryId),);set() vs remember()
Section titled “set() vs remember()”| Method | When to use | Behavior |
|---|---|---|
set() | You already have the value | Stores it directly |
remember() | Value needs to be computed on cache miss | Gets from cache OR runs the callback and caches the result |
// set() — value in handconst profile = await db.users.getProfile(userId);await cache.set(`users.${userId}.profile`, profile, "1d");
// remember() — let the cache handle itconst profile = await cache.remember(`users.${userId}.profile`, "1d", () => db.users.getProfile(userId),);Update
Section titled “Update”Atomically read, transform, and write a cached value:
await cache.update<User>(`user.${userId}`, (current) => ({ ...(current ?? defaultUser), lastSeenAt: Date.now(),}));See Update & Merge for the callback contract, TTL preservation rules, and driver support matrix.
Shallow-merge a partial object into an existing cached value:
await cache.merge<User>(`user.${userId}`, { theme: "dark" });Obtain a typed list accessor for queue / stack / recent-N workflows:
const recent = cache.list<Event>("recent.events");await recent.push(event);const last10 = await recent.slice(0, 10);See Cache Lists for the full accessor API.
Similar (vector-based retrieval)
Section titled “Similar (vector-based retrieval)”Look up entries by cosine similarity instead of exact key match. Pair with set({ vector }):
await cache.set("doc.refunds", policy, { vector: await embed(policy.text) });
const hits = await cache.similar(await embed(userQuestion), { topK: 5, threshold: 0.7,});See Similarity Retrieval for the full API and capability matrix — only memory drivers and pg (with vector config) support it; others throw CacheUnsupportedError.
Driver Management
Section titled “Driver Management”Get Current Driver
Section titled “Get Current Driver”const currentDriver = cache.currentDriver;currentDriver is undefined until init() or use() completes. Every data op guards with an init check, so calling get / set before init throws CacheDriverNotInitializedError.
Switch Default Driver
Section titled “Switch Default Driver”Change the active driver:
await cache.use("redis");use() is async because it loads the driver if not already loaded.
Runtime driver options
Section titled “Runtime driver options”Most options live in setCacheConfigurations({ options }) — table names, TTLs, URL strings, anything declarative and serializable. Some options can only be built at runtime — pg’s client: pg.Pool, a pre-wired Redis client from your app’s connection module, anything you don’t want sitting inside a static config object.
Pass those via the second argument to use / load / driver:
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
cache.setCacheConfigurations({ default: "pg", drivers: { pg: PgCacheDriver }, options: { pg: { table: "cache" } }, // static defaults});
await cache.use("pg", { client: pool }); // runtime injectionRuntime options merge over the config defaults per-key — runtime wins on collision:
// config: { ttl: 60, table: "cache" }// runtime: { ttl: 120, client: pool }// final: { ttl: 120, table: "cache", client: pool }Once a driver is loaded, options are frozen on the instance. Calling load / driver / use a second time with new options throws CacheConfigurationError — no silent overwrites:
await cache.load("pg", { client: poolA });await cache.load("pg", { client: poolB }); // throwsIf you need a different configuration, register a second driver name (pg-tenant-b) — driver identity stays one-to-one with config.
Calling without options on an already-loaded driver still returns the cached instance silently. The throw only fires when options would otherwise be silently dropped.
Per-call Driver Override
Section titled “Per-call Driver Override”When most writes go to your default driver but one call needs a different one, use the driver option instead of switching:
// Routes this single write to "redis" without mutating currentDriverawait cache.set("audit.event", event, { driver: "redis", ttl: "30d" });Get Specific Driver
Section titled “Get Specific Driver”Obtain a driver instance without changing the default:
const redisDriver = await cache.driver("redis");const memoryDriver = await cache.driver("memory");
await redisDriver.set("key", "value");await memoryDriver.set("temp", 123);Register Custom Driver
Section titled “Register Custom Driver”Add drivers at runtime:
import { CustomCacheDriver } from "./CustomCacheDriver";
cache.registerDriver("custom", CustomCacheDriver);await cache.use("custom");Driver Options
Section titled “Driver Options”Get Current Options
Section titled “Get Current Options”const options = cache.options;Update Options
Section titled “Update Options”cache.setOptions({ globalPrefix: "newprefix", ttl: "2h",});Global Prefix
Section titled “Global Prefix”All built-in drivers support a globalPrefix option to avoid key conflicts, especially on shared storage like Redis. Think of it like a database name — it separates your keys from other applications. The prefix can be a string or a function (the function form runs per call, which is great for per-tenant scoping).
Set it in your configuration or update at runtime with setOptions().
Namespaces
Section titled “Namespaces”Namespaces help organize cache keys hierarchically using dot notation. See Namespaces for a complete guide, including cache.namespace(prefix, options?) for scoped handles.
await cache.set(`orders.${userId}.recent`, orders);await cache.set(`orders.${userId}.total`, total);
// Remove all keys in a namespaceawait cache.removeNamespace(`orders.${userId}`);
// Or grab a scoped handle when you'll touch the prefix more than onceconst orders = cache.namespace(`orders.${userId}`, { ttl: "1h" });await orders.set("recent", recent);await orders.set("total", total);await orders.clear();Advanced Features
Section titled “Advanced Features”Each has dedicated documentation:
- Set Options — rich
CacheSetOptionsobject:ttl,expiresAt,tags,onConflict,driver - Update & Merge — atomic read-modify-write
- Cache Lists — queue / stack / recent-N buffer accessor
- Cache Tags — group and invalidate related entries
- Atomic Operations —
increment,decrement, conditional writes - Bulk Operations —
many()andsetMany()batching - Stampede Prevention —
remember()and distributed-lock recipes - Event System — observe every cache operation
Cache Keys
Section titled “Cache Keys”Always use dot notation strings:
-
✅
"user.1" -
✅
"posts.123.comments" -
✅
"app.config.settings" -
❌
{ id: 1 }(objects work but reduce readability) -
❌
"user:1"(colons are rewritten to dots — be explicit)
:::tip Recommended
Dot notation makes keys predictable, readable, and easier to debug. If you need to generate keys from complex data, see parseCacheKey.
:::
Disconnect
Section titled “Disconnect”Disconnect from the current driver:
await cache.disconnect();Troubleshooting
Section titled “Troubleshooting”- Always call
init()before using cache operations — operations throwCacheDriverNotInitializedErrorotherwise. - Use
await cache.driver('name')to access a specific driver without changing the default — useful when you need to read from one driver and write to another in the same flow (e.g. rate-limit in memory, results in Redis). - Use
{ driver: "name" }insetoptions when the override is per-call — cleaner than switching and switching back.
Related Documentation
Section titled “Related Documentation”- Cache Configurations — configure drivers and options
- Set Options — the headline v2 feature
- Cache Driver Interface — complete method reference
- Errors — error classes and how to react
- Best Practices — production-ready caching strategies