Skip to content
Warlock.js v4

Base Cache Driver

Abstract class that all built-in drivers extend. Provides key parsing, TTL handling, logging, and default implementations of optional methods.

import { BaseCacheDriver } from "@warlock.js/cache";
import type { CacheDriver } from "@warlock.js/cache";
interface MyOptions {
globalPrefix?: string;
ttl?: number;
}
export class MyDriver extends BaseCacheDriver<MyDriver, MyOptions>
implements CacheDriver<MyDriver, MyOptions> {
public name = "myDriver";
// Implement required abstract methods
public async removeNamespace(namespace: string) {
// Implementation
}
public async set(key: string | GenericObject, value: any, ttl?: number) {
// Implementation
}
public async get(key: string | GenericObject) {
// Implementation
}
public async remove(key: string | GenericObject) {
// Implementation
}
public async flush() {
// Implementation
}
}

Update driver configuration.

setOptions(options: Options): any;

Normalize a cache key using internal parseCacheKey utility.

parseKey(key: string | GenericObject): string;

Default implementation logs “connecting” and “connected” events. Override to add backend-specific logic.

connect(): Promise<any>;

Default implementation logs “disconnecting” and “disconnected” events. Override to add backend-specific logic.

disconnect(): Promise<void>;

Normalize the third argument of set() into a single shape every driver can act on. Handles TTL parsing (number, duration string, or CacheSetOptions object), expiresAt → relative-TTL conversion, and mutual-exclusion validation.

Returns { ttl, tags, onConflict, vector }. ttl is always a final numeric seconds value — already merged with the driver-level default (options.<driver>.ttl). When neither caller nor driver provide a TTL, ttl is Infinity (no expiration). Drivers do not need to fall back to this.ttl themselves.

vector is the optional embedding to index for similarity — drivers that don’t support similarity should throw CacheUnsupportedError when it’s present.

protected resolveSetOptions(
ttlOrOptions?: CacheTtl | CacheSetOptions,
): { ttl: number; tags?: string[]; onConflict: CacheConflictPolicy; vector?: number[] };

Internally composed of three pure helpers exported from utils — useful when writing custom drivers that need the same normalization without inheriting from BaseCacheDriver:

The merge is centralized so that every shape that lacks a caller-provided TTL respects the driver-level default:

Caller passes…Resolved ttl
nothing (set(k, v))this.ttl (driver default, or Infinity)
undefined / null explicitlysame as above
{ tags: [...] } (options without ttl)same as above
{ ttl: "1h" }3600
60 (positional seconds)60
"30m" (positional duration)1800
{ expiresAt: futureDate }computed seconds-until-deadline

Every driver’s set() method should call this as its first step. It’s the single normalization point for the v2 options surface — anything that accepts CacheTtl | CacheSetOptions in the interface funnels through here.

Example (inside a custom driver’s set):

public async set(
key: CacheKey,
value: any,
ttlOrOptions?: CacheTtl | CacheSetOptions,
) {
const { ttl, tags, onConflict, vector } = this.resolveSetOptions(ttlOrOptions);
if (vector) {
throw new CacheUnsupportedError(
"'mydriver' does not index vectors — use a memory driver or pg with pgvector.",
);
}
// `ttl` is final — no need for `?? this.ttl`. It's already either the
// caller's value or the driver-level default, with `Infinity` when neither
// applies.
}

The base class also ships a default similar() that throws CacheUnsupportedError — drivers that support similarity override it with a real impl. See Make Your Own Driver for the full extension surface.

Throws CacheConfigurationError on:

  • Both ttl and expiresAt supplied together.
  • expiresAt in the past.
  • Invalid duration string like "2 weeksandahalf".

Return the default TTL from driver options in seconds, parsing duration strings if necessary. Returns Infinity when no default is configured.

get ttl(): number;

Calculate millisecond timestamp when a cache entry expires. Returns undefined if TTL is not set.

getExpiresAt(ttl?: number): number | undefined;

Example:

const expiresAt = driver.getExpiresAt(3600); // 1 hour from now

Wrap a value with TTL and expiration metadata for backend storage.

prepareDataForStorage(data: any, ttl?: number): CacheData;

Example:

const wrapped = driver.prepareDataForStorage({ status: "active" }, 3600);
// { data: { status: "active" }, ttl: 3600, expiresAt: 1713399600000 }

Unwrap stored data, check expiration, log expiry if needed, and return the value or null. Returns the original value if valid, null if expired.

parseCachedData(key: string | GenericObject, data: CacheData): any;

Example:

const value = driver.parseCachedData("product:123", storedData);
// Returns value if not expired, null and logs error if expired

Log a cache operation with consistent formatting. Uses the driver name as the log channel.

log(operation: string, key?: string): void;

Supported operations:

OperationLevelUsage
fetchinginfoBefore getting a value
fetchedsuccessAfter successfully retrieving a value
cachinginfoBefore setting a value
cachedsuccessAfter successfully storing a value
removinginfoBefore removing a key
removedsuccessAfter successfully removing a key
clearinginfoBefore clearing a namespace
clearedsuccessAfter successfully clearing a namespace
flushinginfoBefore flushing all cache
flushedsuccessAfter successfully flushing
expirederrorWhen a cached entry has expired
notFounderrorWhen a key is not found
connectinginfoBefore connecting to backend
connectedsuccessAfter successful backend connection
disconnectinginfoBefore disconnecting from backend
disconnectedsuccessAfter successful disconnection
errorerrorFor operation failures

Example:

this.log("caching", "user:123");
// Logs: "redis: caching user:123"

The base driver automatically wraps cached values with metadata:

interface CacheData {
data: any; // The actual cached value
ttl?: number; // Time to live in seconds
expiresAt?: number; // Millisecond timestamp when entry expires
}

Your driver’s set() and get() implementations should use prepareDataForStorage() when storing and parseCachedData() when retrieving to handle this format transparently.

Example: Custom Driver with BaseCacheDriver

Section titled “Example: Custom Driver with BaseCacheDriver”
import { BaseCacheDriver } from "@warlock.js/cache";
import type { CacheDriver } from "@warlock.js/cache";
interface MyCacheOptions {
globalPrefix?: string;
ttl?: number;
storage: Map<string, any>;
}
export class MyCacheDriver extends BaseCacheDriver<MyCacheDriver, MyCacheOptions>
implements CacheDriver<MyCacheDriver, MyCacheOptions> {
public name = "myCache";
public async removeNamespace(namespace: string) {
namespace = await this.parseKey(namespace);
this.log("clearing", namespace);
// Remove all keys starting with namespace
for (const key of this.options.storage.keys()) {
if (key.startsWith(namespace)) {
this.options.storage.delete(key);
}
}
this.log("cleared", namespace);
}
public async set(key: string | GenericObject, value: any, ttl?: number) {
key = await this.parseKey(key);
this.log("caching", key);
const data = this.prepareDataForStorage(value, ttl);
this.options.storage.set(key, data);
this.log("cached", key);
return value;
}
public async get(key: string | GenericObject) {
key = await this.parseKey(key);
this.log("fetching", key);
const data = this.options.storage.get(key);
if (!data) {
this.log("notFound", key);
return null;
}
return this.parseCachedData(key, data);
}
public async remove(key: string | GenericObject) {
key = await this.parseKey(key);
this.log("removing", key);
this.options.storage.delete(key);
this.log("removed", key);
}
public async flush() {
this.log("flushing");
this.options.storage.clear();
this.log("flushed");
}
}

See Cache Driver Interface for the complete interface definition.