Custom Drivers
Custom drivers
Build a new backend by extending BaseCacheEngine. The base class handles the {data, expiresAt} envelope, expiration checks, prefix application, JSON conversion, and corruption recovery. The subclass only needs to point storage at the actual store.
Minimum viable driver
import { BaseCacheEngine } from "@mongez/cache";
class MyDriver extends BaseCacheEngine { public storage = { getItem: (key: string) => /* read */, setItem: (key: string, value: string) => /* write */, removeItem: (key: string) => /* delete */, clear: () => /* drop everything */, };}storage mirrors the Storage interface from the DOM spec, but you can supply any object with the four methods above. The base engine treats it as opaque.
Indexed DB
import { BaseCacheEngine } from "@mongez/cache";import { get, set, del, clear } from "idb-keyval";
class IndexedDbDriver extends BaseCacheEngine { public storage = { getItem: (key: string) => /* IDB is async — see notes below */, setItem: (key: string, value: string) => { set(key, value); // fire-and-forget }, removeItem: (key: string) => { del(key); }, clear: () => { clear(); }, };}Async caveat: the BaseCacheEngine.get expects a synchronous return from storage.getItem. IndexedDB is inherently async. If you need real IndexedDB-backed reads, either:
- Maintain an in-memory mirror that you populate from IDB on boot, read synchronously from the mirror, and write through to IDB on
setItem. - Use
@mongez/atom’spersistslot directly — it accepts async adapters. Seerecipes.md.
For most apps, option 2 is simpler.
Cookie driver
class CookieDriver extends BaseCacheEngine { public storage = { getItem: (key: string) => { if (typeof document === "undefined") return null; const match = document.cookie.match(new RegExp(`(?:^|; )${key}=([^;]*)`)); return match ? decodeURIComponent(match[1]) : null; }, setItem: (key: string, value: string) => { if (typeof document === "undefined") return; document.cookie = `${key}=${encodeURIComponent(value)};path=/;max-age=31536000;samesite=lax`; }, removeItem: (key: string) => { if (typeof document === "undefined") return; document.cookie = `${key}=;path=/;max-age=0`; }, clear: () => { /* enumerate document.cookie and expire each */ }, };}Cookies are SSR-friendly when the server can read the request’s Cookie header — for that path, wrap the same shape around your framework’s server-side cookie API.
Override the envelope shape
RunTimeDriver is the in-tree example of a driver that doesn’t want JSON. It overrides both:
class RunTimeDriver extends BaseCacheEngine { public storage = this; public data: Record<string, { value: any; expiresAt?: number }> = {};
public getItem(key: string, defaultValue?: any) { /* ... */ } public setItem(key: string, value: any) { /* ... */ } public removeItem(key: string) { /* ... */ }
protected convertValue(value: any) { return value; } // no JSON.stringify protected parseValue(value: any) { return value; } // no JSON.parse
public clear() { this.data = {}; return this; }}Override convertValue and parseValue whenever your storage backend already accepts structured data — IndexedDB, an in-memory map, a binary protocol, etc. The base engine still wraps in {data, expiresAt} so TTL keeps working.
Override set / get entirely
The encrypted drivers go one step further — they override set and get themselves to route values through the encrypt/decrypt pair. They still wrap in the {data, expiresAt} envelope before encrypting, so TTL keeps working. That pattern is the right move when:
- You need to transform the entire value (encrypt, compress, sign), not just the on-disk format.
- You’re willing to re-implement the envelope yourself if you also need TTL.
class CompressedDriver extends PlainLocalStorageDriver { public set(key: string, value: any) { this.storage.setItem(this.getKey(key), compress(JSON.stringify(value))); return this; }
public get(key: string, defaultValue: any = null) { const raw = this.storage.getItem(this.getKey(key)); if (raw === null) return defaultValue; try { return JSON.parse(decompress(raw)); } catch { return defaultValue; } }}If you override set and skip the envelope, you also lose TTL. Add your own expiry mechanism if needed (a per-key ${key}.expiresAt companion entry, or fold the timestamp into the compressed payload).
Wiring it up
setCacheConfigurations({ driver: new MyDriver(),});
cache.set("name", "Hasan");Or use it directly without going through setCacheConfigurations:
const driver = new MyDriver();driver.set("name", "Hasan");