| name | mongez-cache-custom-drivers |
| description | Build custom cache backends by extending `BaseCacheEngine` — IndexedDB, cookies, remote stores — and override `convertValue` / `parseValue` or `set` / `get` when the envelope shape needs to change.
TRIGGER when: code declares `class X extends BaseCacheEngine` or imports `BaseCacheEngine` from `@mongez/cache`; user asks "how do I build a custom cache driver", "how do I back the cache with IndexedDB / cookies", or "how do I override the `{data, expiresAt}` envelope / serialization"; `import { BaseCacheEngine } from "@mongez/cache"`.
SKIP: picking among the shipped drivers — use `mongez-cache-drivers`; in-memory runtime driver — use `mongez-cache-runtime`; encrypted drivers — use `mongez-cache-encryption`; everyday `cache.set` / `cache.get` usage — use `mongez-cache-basic-usage`.
|
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) => ,
setItem: (key: string, value: string) => ,
removeItem: (key: string) => ,
clear: () => ,
};
}
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) => ,
setItem: (key: string, value: string) => {
set(key, value);
},
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's persist slot directly — it accepts async adapters. See recipes.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: () => {
},
};
}
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; }
protected parseValue(value: any) { return value; }
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");