Base Cache Driver
Abstract class that all built-in drivers extend. Provides key parsing, TTL handling, logging, and default implementations of optional methods.
Example: Extending BaseCacheDriver
Section titled “Example: Extending BaseCacheDriver”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 }}Core Implemented Methods
Section titled “Core Implemented Methods”setOptions(options)
Section titled “setOptions(options)”Update driver configuration.
setOptions(options: Options): any;parseKey(key)
Section titled “parseKey(key)”Normalize a cache key using internal parseCacheKey utility.
parseKey(key: string | GenericObject): string;connect()
Section titled “connect()”Default implementation logs “connecting” and “connected” events. Override to add backend-specific logic.
connect(): Promise<any>;disconnect()
Section titled “disconnect()”Default implementation logs “disconnecting” and “disconnected” events. Override to add backend-specific logic.
disconnect(): Promise<void>;TTL Management Methods
Section titled “TTL Management Methods”resolveSetOptions(ttlOrOptions)
Section titled “resolveSetOptions(ttlOrOptions)”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:
normalizeToOptions(input)— coerce the polymorphic 3rdsetarg into aCacheSetOptionsshape.resolveTtl(ttl, expiresAt, fallback)— apply caller > driver-default precedence + mutual-exclusion check.expiresAtToTtl(expiresAt)— Date/epoch → relative seconds.
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 explicitly | same 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
ttlandexpiresAtsupplied together. expiresAtin the past.- Invalid duration string like
"2 weeksandahalf".
ttl (getter)
Section titled “ttl (getter)”Return the default TTL from driver options in seconds, parsing duration strings if necessary. Returns Infinity when no default is configured.
get ttl(): number;getExpiresAt(ttl)
Section titled “getExpiresAt(ttl)”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 nowprepareDataForStorage(data, ttl)
Section titled “prepareDataForStorage(data, ttl)”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 }parseCachedData(key, data)
Section titled “parseCachedData(key, data)”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 expiredLogging Method
Section titled “Logging Method”log(operation, key?)
Section titled “log(operation, key?)”Log a cache operation with consistent formatting. Uses the driver name as the log channel.
log(operation: string, key?: string): void;Supported operations:
| Operation | Level | Usage |
|---|---|---|
fetching | info | Before getting a value |
fetched | success | After successfully retrieving a value |
caching | info | Before setting a value |
cached | success | After successfully storing a value |
removing | info | Before removing a key |
removed | success | After successfully removing a key |
clearing | info | Before clearing a namespace |
cleared | success | After successfully clearing a namespace |
flushing | info | Before flushing all cache |
flushed | success | After successfully flushing |
expired | error | When a cached entry has expired |
notFound | error | When a key is not found |
connecting | info | Before connecting to backend |
connected | success | After successful backend connection |
disconnecting | info | Before disconnecting from backend |
disconnected | success | After successful disconnection |
error | error | For operation failures |
Example:
this.log("caching", "user:123");// Logs: "redis: caching user:123"Data Storage Format
Section titled “Data Storage Format”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.