Set Options
The third argument to cache.set() accepts three shapes — a positional TTL (number or string) for the common case, and a rich options object when you need more than just expiration.
The three shapes
Section titled “The three shapes”import { cache } from "@warlock.js/cache";
// 1. Number of seconds (back-compat)await cache.set("name", "John Doe", 600);
// 2. Human-readable duration string (parsed via ms)await cache.set("name", "John Doe", "10m");await cache.set("session", token, "1h");await cache.set("report", report, "7d");
// 3. Rich options objectawait cache.set("user.1", user, { ttl: "1h", tags: ["users", "active"], onConflict: "create",});See Duration Strings for the full grammar of accepted string forms.
Options reference
Section titled “Options reference”| Key | Type | Purpose |
|---|---|---|
ttl | number | string | Relative expiry. Mutually exclusive with expiresAt. |
expiresAt | number | Date | Absolute deadline. Must be in the future. Mutually exclusive with ttl. |
tags | string[] | Inline tag list — equivalent to cache.tags([...]).set(...). |
onConflict | "create" | "update" | "upsert" | Conditional-write policy. Defaults to "upsert". |
driver | string | Per-call driver override by registered name. |
vector | number[] | Embedding indexed alongside the entry for similarity retrieval. Drivers without similarity support throw CacheUnsupportedError. |
:::info Validation rules
- Passing both
ttlandexpiresAtthrowsCacheConfigurationError. - An
expiresAtin the past throwsCacheConfigurationError. - Invalid duration strings (
"2 weeksandahalf") throwCacheConfigurationError. :::
TTL and expiresAt
Section titled “TTL and expiresAt”Use ttl for relative expiry (“cache for 1 hour from now”). Use expiresAt when the deadline is pinned to a specific wall-clock moment — JWT expiry, session close, scheduled campaign end.
// Relative — "1 hour from now"await cache.set("session", session, { ttl: "1h" });
// Absolute — aligned to JWT expiryawait cache.set("session", session, { expiresAt: jwt.exp * 1000 });
// Absolute — aligned to a Dateawait cache.set("campaign", data, { expiresAt: new Date("2026-12-31T23:59:59Z"),});Passing both is rejected at the call site — two ways to say the same thing silently drift over time.
Conditional writes — onConflict
Section titled “Conditional writes — onConflict”Controls what happens when the key already exists (or doesn’t).
// "create" — set only if the key is missingconst workerId = `worker.${process.pid}`; // any identifying valueconst result = await cache.set("lock.orders", workerId, { onConflict: "create", ttl: "5m",});// { wasSet: true, existing: null } on acquire// { wasSet: false, existing: <prior workerId> } if someone else holds it
if (!result.wasSet) { throw new Error(`Another worker owns this lock: ${result.existing}`);}
// "update" — set only if the key exists (don't resurrect expired entries)await cache.set("user.1", patch, { onConflict: "update" });
// "upsert" — the default. Overwrites regardless.await cache.set("user.1", user);Conditional writes return a CacheSetResult:
type CacheSetResult<T = any> = { wasSet: boolean; existing: T | null; // only populated on "create" rejections};Unconditional "upsert" writes return the driver instance or the stored value, matching the legacy shape.
:::tip Redis equivalence
"create" maps to Redis SET … NX natively; "update" maps to SET … XX. Other drivers emulate the behavior in-process. You get the same semantics regardless of driver.
:::
Inline tags
Section titled “Inline tags”Equivalent to the fluent form, terser at the call site:
// Inlineawait cache.set("user.1", user, { tags: ["users", "tenant-42"] });
// Fluent (useful when you already have a tagged instance)const users = cache.tags(["users"]);await users.set("user.1", user);Both invalidate via the same path:
await cache.tags(["users"]).invalidate();See Cache Tags for the full invalidation workflow.
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, pass driver in the options object. The manager loads (and connects) the override driver lazily on first use, then routes that single operation without mutating currentDriver.
// Audit events always land in Redis, regardless of default driverawait cache.set("audit.event", event, { driver: "redis", ttl: "30d",});
// Normal usage still uses the default driverawait cache.set("user.1", user, "1h");The override driver must be registered in your configuration:
cache.setCacheConfigurations({ default: "memory", drivers: { memory: MemoryCacheDriver, redis: RedisCacheDriver, }, options: { /* ... */ },});Distributed locking recipe
Section titled “Distributed locking recipe”A distributed lock makes sure only one process runs a block of code at a time, even across multiple servers. The onConflict: "create" pattern is the portable way to build one — Redis-native on Redis, single-process elsewhere.
async function withLock<T>( key: string, ttl: string, work: () => Promise<T>,): Promise<T | null> { // process.pid is just an identifying value so you can see which worker // holds the lock when debugging. Any unique string works. const acquired = await cache.set(key, process.pid, { onConflict: "create", ttl, // safety net: auto-release if we crash before remove() });
if (!acquired.wasSet) { return null; // someone else holds the lock — skip this work }
try { return await work(); } finally { await cache.remove(key); }}
// Usage — build a heavy report, but only run the job on one server even if// three servers received the request at the same time.const result = await withLock("lock.build-report.42", "2m", async () => { const report = await db.reports.generate(42); // expensive await cache.set("report.42", report, "1h"); return report;});See Stampede Prevention for the single-node variant using remember().
Back-compat
Section titled “Back-compat”Every call site using the old positional-TTL shape still works:
await cache.set("k", v); // no TTL — driver defaultawait cache.set("k", v, 3600); // secondsawait cache.set("k", v, undefined); // same as no TTLRelated Documentation
Section titled “Related Documentation”- Atomic Operations — counters,
update(),merge() - Cache Tags — tag-based invalidation
- Cache Manager — full API surface
- Errors —
CacheConfigurationErrorand friends