Quick Start
Installation
Section titled “Installation”npm install @warlock.js/cacheyarn add @warlock.js/cachepnpm add @warlock.js/cacheimport { cache, MemoryCacheDriver } from "@warlock.js/cache";
cache.setCacheConfigurations({ default: "memory", drivers: { memory: MemoryCacheDriver }, options: { memory: { ttl: "1h" } }, // or a number of seconds});await cache.init();:::danger Call init() once at startup
Any cache operation before init() throws. Do this once in your main entry file, not on every request.
:::
Verify it works
Section titled “Verify it works”A 4-line sanity check you can run immediately after setup:
await cache.set("hello", "world", "1m");console.log(await cache.get("hello")); // "world"await cache.remove("hello");console.log(await cache.get("hello")); // nullIf you see "world" then null, your cache is working.
The whole arc in 15 lines
Section titled “The whole arc in 15 lines”Cache a value, read it back, then graduate to the two patterns you’ll actually
reach for in production — cached() to memoize a function, and swr() to serve
hot data instantly while it refreshes in the background. Paste this after init():
// 1. Cache a value, read it back.await cache.set("greeting", "hello", "5m");await cache.get("greeting"); // "hello"
// 2. Memoize a function — declare the strategy once, call it anywhere.const getUser = cached((id: number) => db.users.find(id), "user", "1h");await getUser(42); // miss → runs the queryawait getUser(42); // hit → no queryawait getUser.invalidate(42); // drop "user.42"
// 3. Serve stale-but-instant while a refresh runs in the background.const product = await cache.swr( "product.42", { freshTtl: "1m", staleTtl: "1h" }, // fresh 1m, stale up to 1h () => db.products.find(42),);Three escalating tools: set/get for direct control, cached()
when freshness can lag a little and you call the same function from many places,
and swr() when a slow upstream must never make a fast
endpoint slow. The rest of this page zooms in on each.
Your first cached function — the 101 pattern
Section titled “Your first cached function — the 101 pattern”The most common use for a cache is memoizing an expensive function — a database query, an API call, a heavy computation. Here’s the pattern you’ll reach for 90% of the time:
// Before: every call hits the database (~50ms each)async function getUser(id: number) { return db.users.findById(id);}
// After: the first call hits the database, the next thousand don't (~1ms each)async function getUser(id: number) { return cache.remember( `user.${id}`, // cache key — one per user "1h", // how long to cache (see "Duration strings" below) () => db.users.findById(id), // only runs on cache miss );}That’s it. remember() checks the cache first; if it finds the value it returns it, otherwise it runs your callback, caches the result, and returns it.
Bonus: when 1000 concurrent requests call getUser(42) at the same instant on a cold cache, remember() only runs the callback once — the other 999 wait for that one result. This is called stampede prevention, and it’s the main reason to prefer remember() over a manual get + set pair.
Core Patterns
Section titled “Core Patterns”set / get
Section titled “set / get”Store and retrieve data directly:
// Duration string (preferred — more readable)await cache.set(`products.${id}`, product, "1h");
// Or numeric seconds — both workawait cache.set(`products.${id}`, product, 3600);
// Returns null if missing or expiredconst product = await cache.get(`products.${id}`);remember (get or compute)
Section titled “remember (get or compute)”Covered above — this is the function you’ll use most.
Invalidate — remove / removeNamespace
Section titled “Invalidate — remove / removeNamespace”Drop one key, or every key under a prefix:
// Drop a single keyawait cache.remove("user.1");
// Drop everything under "orders.{userId}." after checkoutawait cache.removeNamespace(`orders.${userId}`);Advanced: conditional writes
Section titled “Advanced: conditional writes”Once you’re comfortable with the basics, set() accepts a rich options object. The headline feature is onConflict, which lets you implement a distributed lock — a way to make sure only one process runs a job at a time, even across servers:
// "create" = succeed only if the key is missingconst lock = await cache.set("lock.import", 1, { onConflict: "create", ttl: "5m", // auto-release after 5 min if we crash});
if (lock.wasSet) { try { await runImport(); } finally { await cache.remove("lock.import"); // release the lock }}Full reference in Set Options. Skip this section on your first read if you just want to cache some data.
Duration strings
Section titled “Duration strings”TTL values accept a number of seconds or a human-readable duration string:
| Form | Meaning |
|---|---|
600 | 600 seconds (10 minutes) |
"1s", "30s" | Seconds |
"30m" | Minutes |
"1h", "12h" | Hours |
"7d", "30d" | Days |
"1w", "2 weeks" | Weeks |
"1y", "1 year" | Years |
Infinity | Never expires |
Compound forms like "1h30m" are not supported — use "90m" or the numeric equivalent. Duration strings work everywhere a TTL is accepted: set(), remember(), driver options, and inside CacheSetOptions.
Key Concepts
Section titled “Key Concepts”- TTL: Time-to-live (seconds or duration string — see above).
- Dot-notation keys: Use hierarchical keys like
"products.123"or"category.electronics.featured"to organize related cache entries. - Namespaces: Groups of related keys that can be invalidated together with
removeNamespace().
Next Steps
Section titled “Next Steps”- Configuration — set up Redis or other drivers
- Cache Manager — complete method reference
- Set Options —
ttl,expiresAt,tags,onConflict, per-calldriveroverride - Stampede Prevention — why
remember()is special - Cache Tags, Events, and more advanced features