Skip to content
Warlock.js v4

Cache Driver Interface

Every custom cache driver must implement this interface. All built-in drivers follow this contract.

Driver configuration object.

options: Options;

Unique driver identifier.

name: string;

Example:

const driver = cache.driver("redis");
console.log(driver.name); // "redis"

Store a value in cache. The third argument accepts three shapes — a positional TTL (number or duration string) for the common case, or a rich CacheSetOptions object.

type CacheTtl = number | string;
set(
key: string | GenericObject,
value: any,
ttlOrOptions?: CacheTtl | CacheSetOptions,
): Promise<any>;

Examples:

// Positional TTL
await cache.set("product.123", product, 3600); // seconds
await cache.set("product.123", product, "1h"); // duration string
// Rich options
await cache.set("product.123", product, {
ttl: "1h",
tags: ["products"],
onConflict: "create",
});

See Set Options for the full option reference.

Retrieve a value from cache. Returns the value if found and not expired, otherwise null.

get<T = any>(key: string | GenericObject): Promise<T | null>;

Example:

const product = await cache.get("product.123");
if (product === null) {
console.log("Product not in cache");
}

Check if a key exists in cache without retrieving its value. Returns true if key exists and is not expired, false otherwise.

has(key: string | GenericObject): Promise<boolean>;

Example:

const exists = await cache.has("rate-limit.user.456");

Delete a single key from cache.

remove(key: string | GenericObject): Promise<void>;

Example:

await cache.remove("product.123");

Clear all cached data.

flush(): Promise<void>;

Example:

await cache.flush();

Delete all keys within a namespace. Namespace is the first segment of a key before the first dot.

removeNamespace(namespace: string): Promise<any>;

Example:

// Removes all keys like "product.123", "product.456", etc.
await cache.removeNamespace("product");

Get a value from cache. If not found or expired, execute the callback function and cache the result. Includes automatic stampede prevention within a single process.

remember<T = any>(
key: string | GenericObject,
ttl: CacheTtl,
callback: () => Promise<T>,
): Promise<T>;

Example:

const user = await cache.remember("user.789", "1h", async () => {
return await database.users.find(789);
});

See Stampede Prevention for details.

Retrieve and delete a key atomically.

pull<T = any>(key: string | GenericObject): Promise<T | null>;

Example:

const order = await cache.pull("order-session.abc123");

Store a value permanently with no expiration.

forever<T = any>(key: string | GenericObject, value: T): Promise<T>;

Example:

await cache.forever("config.app", { version: "1.0.0" });

Atomically increase a numeric value by the given amount (default 1). Returns the new value. Throws if the stored value is not numeric.

increment(key: string | GenericObject, value?: number): Promise<number>;

Example:

const count = await cache.increment("product.views.123", 1);

Atomically decrease a numeric value by the given amount (default 1). Returns the new value. Throws if the stored value is not numeric.

decrement(key: string | GenericObject, value?: number): Promise<number>;

Example:

const stock = await cache.decrement("inventory.widget", 5);

Atomically read, transform, and write a cached value. The callback receives the current value (or null on miss) and returns the next value. Returning null removes the entry.

update<T = any>(
key: string | GenericObject,
fn: (current: T | null) => T | null | Promise<T | null>,
options?: { ttl?: CacheTtl },
): Promise<T | null>;

Example:

await cache.update<User>("user.1", (current) => ({
...(current ?? defaultUser),
lastSeenAt: Date.now(),
}));

Throws CacheUnsupportedError on the file driver. See Update & Merge.

Shallow-merge a partial object into a cached value. Treats missing keys as {}.

merge<T extends Record<string, any> = Record<string, any>>(
key: string | GenericObject,
partial: Partial<T>,
options?: { ttl?: CacheTtl },
): Promise<T>;

Example:

await cache.merge<User>("user.1", { name: "Jane" });

Throws CacheUnsupportedError on the file driver.

Obtain a list accessor bound to the key. Returns a CacheListAccessor<T> for queue/stack/recent-N workflows.

list<T = any>(key: string | GenericObject): CacheListAccessor<T>;

Example:

const recent = cache.list<Event>("recent.events");
await recent.push(event);
await recent.trim(0, 99);

See Cache Lists for the full accessor API.

Retrieve multiple values in one operation. Returns an array in the same order as the input keys. Missing or expired keys return null in their positions.

many(keys: (string | GenericObject)[]): Promise<any[]>;

Example:

const [user, product, order] = await cache.many([
"user.123",
"product.456",
"order.789",
]);

Store multiple key-value pairs in one operation.

setMany(items: Record<string, any>, ttl?: CacheTtl): Promise<void>;

Example:

await cache.setMany({
"user.123": { id: 123, name: "Alice" },
"user.456": { id: 456, name: "Bob" },
}, "1h");

Create a tagged cache instance to group related keys. Returns a TaggedCacheDriver instance.

tags(tags: string[]): TaggedCacheDriver;

Example:

const tagged = cache.tags(["products", "featured"]);
await tagged.set("item.1", product1);
await tagged.invalidate(); // drops every key tagged "products" or "featured"

Vector-based retrieval. Returns the nearest stored entries to vector by cosine similarity, ordered by descending score.

similar<T = any>(
vector: number[],
options: CacheSimilarOptions,
): Promise<CacheSimilarHit<T>[]>;

Example:

const hits = await cache.similar(await embed(query), {
topK: 5,
threshold: 0.7,
tags: ["docs"],
});

Drivers that don’t index vectors throw CacheUnsupportedError. See Similarity Retrieval for the full API and capability matrix.

Normalize a cache key for driver storage. Sanitizes special characters and applies global prefix.

parseKey(key: string | GenericObject): string;

Key parsing rules:

  • String keys: strip {}"[], replace : and , with .
  • Object keys: JSON stringify then apply string rules
  • Global prefix is prepended if configured

Example:

const parsed = cache.parseKey({ namespace: "product", id: 123 });
// "app.namespace.product.id.123"

Update driver configuration at runtime.

setOptions(options: Options): any;

Establish connection to the cache backend.

connect(): Promise<any>;

Close connection to the cache backend.

disconnect(): Promise<void>;

Optional reference to the underlying backend client (Redis connection, Memcached instance, etc.).

client?: ClientType;

Event subscription methods. Return the driver instance for chaining.

on(event: CacheEventType, handler: CacheEventHandler): this;
off(event: CacheEventType, handler: CacheEventHandler): this;
once(event: CacheEventType, handler: CacheEventHandler): this;

See Event System for the full event catalog.

Toggle per-driver operation logging at runtime.

setLoggingState(shouldLog: boolean): any;
export interface CacheDriver<ClientType, Options> {
options: Options;
name: string;
// Core
set(
key: string | GenericObject,
value: any,
ttlOrOptions?: CacheTtl | CacheSetOptions,
): Promise<any>;
get<T = any>(key: string | GenericObject): Promise<T | null>;
has(key: string | GenericObject): Promise<boolean>;
remove(key: string | GenericObject): Promise<void>;
flush(): Promise<void>;
removeNamespace(namespace: string): Promise<any>;
// Convenience
remember<T = any>(
key: string | GenericObject,
ttl: CacheTtl,
callback: () => Promise<T>,
): Promise<T>;
pull<T = any>(key: string | GenericObject): Promise<T | null>;
forever<T = any>(key: string | GenericObject, value: T): Promise<T>;
// Atomic + mutation
increment(key: string | GenericObject, value?: number): Promise<number>;
decrement(key: string | GenericObject, value?: number): Promise<number>;
update<T = any>(
key: string | GenericObject,
fn: (current: T | null) => T | null | Promise<T | null>,
options?: { ttl?: CacheTtl },
): Promise<T | null>;
merge<T extends Record<string, any> = Record<string, any>>(
key: string | GenericObject,
partial: Partial<T>,
options?: { ttl?: CacheTtl },
): Promise<T>;
// Bulk
many(keys: (string | GenericObject)[]): Promise<any[]>;
setMany(items: Record<string, any>, ttl?: CacheTtl): Promise<void>;
// Collections
list<T = any>(key: string | GenericObject): CacheListAccessor<T>;
tags(tags: string[]): TaggedCacheDriver;
// Similarity
similar<T = any>(
vector: number[],
options: CacheSimilarOptions,
): Promise<CacheSimilarHit<T>[]>;
// Infrastructure
parseKey(key: string | GenericObject): string;
setOptions(options: Options): any;
setLoggingState(shouldLog: boolean): any;
connect(): Promise<any>;
disconnect(): Promise<void>;
client?: ClientType;
// Events
on(event: CacheEventType, handler: CacheEventHandler): this;
off(event: CacheEventType, handler: CacheEventHandler): this;
once(event: CacheEventType, handler: CacheEventHandler): this;
}

Returned by conditional writes (onConflict: "create" or "update"):

type CacheSetResult<T = any> = {
wasSet: boolean; // did the write take effect?
existing: T | null; // prior value, populated on "create" rejections
};

See Set Options — Conditional writes.

Used by similar():

type CacheSimilarOptions = {
topK: number; // required — max hits returned
threshold?: number; // [0, 1]; hits scoring strictly below are dropped
tags?: string[]; // narrow candidate pool by tag (union)
};
type CacheSimilarHit<T = unknown> = {
key: string; // post-parseKey normalized key
value: T; // deep-cloned stored value
score: number; // cosine similarity [0, 1]
};

See Similarity Retrieval.

Returned by list():

interface CacheListAccessor<T = any> {
push(...items: T[]): Promise<number>;
unshift(...items: T[]): Promise<number>;
pop(): Promise<T | null>;
shift(): Promise<T | null>;
slice(start?: number, end?: number): Promise<T[]>;
all(): Promise<T[]>;
length(): Promise<number>;
trim(start: number, end: number): Promise<void>;
clear(): Promise<void>;
}

See Cache Lists.

Drivers throw specific error classes — consumers should catch these selectively:

  • CacheConfigurationError — bad options, invalid TTL string, conflicting ttl + expiresAt
  • CacheDriverNotInitializedError — operations called before init()
  • CacheUnsupportedError — driver doesn’t implement the requested op (today: update / merge on file)
  • CacheConcurrencyError — reserved for future WATCH/MULTI retry exhaustion
  • CacheConnectionError — reserved for driver connection failures

See Errors for the full catalog.

The event system emits:

  • hit — cache hit (value retrieved)
  • miss — cache miss (value not found or expired)
  • set — value stored in cache
  • removed — key removed from cache
  • flushed — entire cache cleared
  • expired — key expired (TTL reached)
  • connected — driver connected
  • disconnected — driver disconnected
  • error — error occurred during operation

See Event System.

  • Extend BaseCacheDriver to reuse key parsing, logging, TTL handling, event emission, and default implementations for has, remember, pull, forever, increment, decrement, many, setMany, tags, update, merge, and list.
  • Implement these required abstract methods: set(), get(), remove(), flush(), removeNamespace().
  • In set(), call this.resolveSetOptions(ttlOrOptions) to normalize the third argument into { ttl, tags, onConflict }.
  • Override update / merge / list only when your driver can offer stronger semantics (e.g. Redis-native commands). Throw CacheUnsupportedError from any op your driver can’t safely support.
import {
BaseCacheDriver,
type CacheDriver,
type CacheKey,
type CacheSetOptions,
type CacheSetResult,
type CacheTtl,
} from "@warlock.js/cache";
type MyCacheOptions = {
globalPrefix?: string;
ttl?: CacheTtl;
};
export class MyCacheDriver
extends BaseCacheDriver<any, MyCacheOptions>
implements CacheDriver<any, MyCacheOptions>
{
public name = "myCache";
public async set(
key: CacheKey,
value: any,
ttlOrOptions?: CacheTtl | CacheSetOptions,
): Promise<any> {
const { ttl, tags, onConflict } = this.resolveSetOptions(ttlOrOptions);
// ... store value with normalized options ...
if (tags?.length) {
await this.applyTags(this.parseKey(key), tags);
}
if (onConflict === "create" || onConflict === "update") {
const result: CacheSetResult = { wasSet: true, existing: null };
return result;
}
return this;
}
public async get(key: CacheKey) { /* ... */ }
public async remove(key: CacheKey) { /* ... */ }
public async flush() { /* ... */ }
public async removeNamespace(namespace: string) { /* ... */ }
}

See Make Your Own Cache Driver for a complete end-to-end example.