TypeScript Integration
Cachemate is written natively in TypeScript with end-to-end type safety, generic cache return types, and compile-time contract enforcement.
Strict Client Options
In TypeScript, ClientOptions requires memoryMaxMb to be explicitly declared. This guarantees that your application deliberately bounds its in-memory fallback footprint.
import { Client, type ClientOptions, type TableRegistry } from 'cachemate';
const options: ClientOptions = {
// Required in TS: In-memory LRU fallback cap in Megabytes
memoryMaxMb: 256,
// Optional: Array of Redis connection URLs or descriptor objects
redisUrls: [
'redis://10.0.1.1:6379',
{ url: 'redis://10.0.1.2:6379', maxMemoryMb: 512 },
],
// Optional: Table registry mapping logical/physical names to tags and version keys
registry: {
entries: {
users: { tag: 'usr', versionKey: 'cm:ver:users' },
accounts: { tag: 'acc', versionKey: 'cm:ver:accounts' },
},
alias: {
userAccounts: 'accounts',
},
},
// Optional: Application identifier prefix for logs
appName: 'payment-svc',
// Optional: Custom log handler callback
log: (msg: string) => console.debug(`[LOG] ${msg}`),
// Optional: Default TTL for join cache queries (seconds, default: 60)
defaultTtl: 180,
// Optional: Default TTL for flat namespace cache (seconds, default: 20)
flatTtl: 30,
// Optional: Global key prefix (default: 'cm')
keyPrefix: 'cm',
};
const client = new Client(options);
Generic Methods & Type Inference
Cachemate's core caching methods (cache, flat, rows) accept generics to type-check both the loader function and the resolved cache value.
1. cache<T>() — Multi-Table Join Cache
interface UserProfileWithRole {
id: string;
email: string;
role: 'admin' | 'member' | 'guest';
permissions: string[];
}
const profile = await client.cache<UserProfileWithRole>({
tables: ['users', 'roles'],
filters: { userId: 'usr_123', includePermissions: true },
ttlSeconds: 300,
loader: async (): Promise<UserProfileWithRole> => {
const user = await db.users.findUniqueOrThrow({ where: { id: 'usr_123' } });
const role = await db.roles.findUniqueOrThrow({ where: { id: user.roleId } });
return {
id: user.id,
email: user.email,
role: role.name as 'admin' | 'member' | 'guest',
permissions: role.permissions,
};
},
});
// profile is strongly typed as UserProfileWithRole
console.log(profile.role);
2. flat<T>() — Namespace Cache
For arbitrary entity or key-value caching independent of table version counters:
interface GeoLocationData {
city: string;
country: string;
coordinates: { lat: number; lng: number };
}
const geo = await client.flat<GeoLocationData>({
ns: 'ip-lookup',
filters: { ip: '198.51.100.42' },
ttlSeconds: 86400, // 24 hours
loader: async () => {
const res = await fetch('https://ip-api.example.com/198.51.100.42');
return (await res.json()) as GeoLocationData;
},
});
3. rows<T, M>() — Two-Tier Row Collection Cache
When caching paginated lists or search results, rows<T, M> separates the list query from individual row entities.
interface ProductRow {
id: string;
title: string;
price: number;
}
interface PaginationMeta {
page: number;
totalPages: number;
totalCount: number;
}
const result = await client.rows<ProductRow, PaginationMeta>({
namespace: 'products-search',
tables: ['products'],
filters: { category: 'electronics', page: 2 },
ttlSeconds: 600,
rowId: (product) => product.id,
getByAll: async () => {
const { items, meta } = await api.searchProducts({ category: 'electronics', page: 2 });
return { rows: items, meta };
},
getAllById: async (ids: string[]) => {
return await api.getProductsByIds(ids);
},
});
// result.rows is ProductRow[]
// result.meta is PaginationMeta | undefined
console.log(result.rows[0].price);
if (result.meta) {
console.log(`Page ${result.meta.page} of ${result.meta.totalPages}`);
}
Compiling and Module Resolution
Cachemate supports dual ESM and CJS modules. For best results in modern TypeScript projects:
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}