131 lines
3.2 KiB
TypeScript
131 lines
3.2 KiB
TypeScript
import type { CacheProvider, ICache } from './types';
|
|
|
|
/**
|
|
* A cache wrapper that automatically prefixes all keys with a namespace
|
|
* Used to provide isolated cache spaces for different services
|
|
*/
|
|
export class NamespacedCache implements CacheProvider {
|
|
private readonly prefix: string;
|
|
|
|
constructor(
|
|
private readonly cache: CacheProvider,
|
|
private readonly namespace: string
|
|
) {
|
|
this.prefix = `${namespace}:`;
|
|
}
|
|
|
|
async get<T = unknown>(key: string): Promise<T | null> {
|
|
return this.cache.get(`${this.prefix}${key}`);
|
|
}
|
|
|
|
async set<T>(key: string, value: T, options?: number | { ttl?: number }): Promise<T | null> {
|
|
return this.cache.set(`${this.prefix}${key}`, value, options);
|
|
}
|
|
|
|
async del(key: string): Promise<void> {
|
|
return this.cache.del(`${this.prefix}${key}`);
|
|
}
|
|
|
|
async exists(key: string): Promise<boolean> {
|
|
return this.cache.exists(`${this.prefix}${key}`);
|
|
}
|
|
|
|
async keys(pattern: string = '*'): Promise<string[]> {
|
|
const fullPattern = `${this.prefix}${pattern}`;
|
|
const keys = await this.cache.keys(fullPattern);
|
|
// Remove the prefix from returned keys for cleaner API
|
|
return keys.filter(k => k.startsWith(this.prefix)).map(k => k.substring(this.prefix.length));
|
|
}
|
|
|
|
async clear(): Promise<void> {
|
|
// Clear only keys with this namespace prefix
|
|
const keys = await this.cache.keys(`${this.prefix}*`);
|
|
if (keys.length > 0) {
|
|
await Promise.all(keys.map(key => this.cache.del(key.substring(this.prefix.length))));
|
|
}
|
|
}
|
|
|
|
getNamespace(): string {
|
|
return this.namespace;
|
|
}
|
|
|
|
getFullPrefix(): string {
|
|
return this.prefix;
|
|
}
|
|
|
|
// CacheProvider methods
|
|
getStats() {
|
|
return this.cache.getStats();
|
|
}
|
|
|
|
async health(): Promise<boolean> {
|
|
return this.cache.health();
|
|
}
|
|
|
|
async waitForReady(timeout?: number): Promise<void> {
|
|
return this.cache.waitForReady(timeout);
|
|
}
|
|
|
|
isReady(): boolean {
|
|
return this.cache.isReady();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adapter to convert ICache to CacheProvider
|
|
*/
|
|
export class CacheAdapter implements CacheProvider {
|
|
constructor(private readonly cache: ICache) {}
|
|
|
|
async get<T = unknown>(key: string): Promise<T | null> {
|
|
return this.cache.get(key);
|
|
}
|
|
|
|
async set<T>(key: string, value: T, options?: number | { ttl?: number }): Promise<T | null> {
|
|
const ttl = typeof options === 'number' ? options : options?.ttl;
|
|
await this.cache.set(key, value, ttl);
|
|
return null;
|
|
}
|
|
|
|
async del(key: string): Promise<void> {
|
|
return this.cache.del(key);
|
|
}
|
|
|
|
async exists(key: string): Promise<boolean> {
|
|
return this.cache.exists(key);
|
|
}
|
|
|
|
async clear(): Promise<void> {
|
|
return this.cache.clear();
|
|
}
|
|
|
|
async keys(pattern: string): Promise<string[]> {
|
|
return this.cache.keys(pattern);
|
|
}
|
|
|
|
getStats() {
|
|
return {
|
|
hits: 0,
|
|
misses: 0,
|
|
errors: 0,
|
|
hitRate: 0,
|
|
total: 0,
|
|
uptime: process.uptime(),
|
|
};
|
|
}
|
|
|
|
async health(): Promise<boolean> {
|
|
return this.cache.ping();
|
|
}
|
|
|
|
async waitForReady(): Promise<void> {
|
|
// ICache doesn't have waitForReady, so just check connection
|
|
if (!this.cache.isConnected()) {
|
|
throw new Error('Cache not connected');
|
|
}
|
|
}
|
|
|
|
isReady(): boolean {
|
|
return this.cache.isConnected();
|
|
}
|
|
}
|