stock-bot/libs/services/queue/src/service-cache.ts

169 lines
No EOL
4.4 KiB
TypeScript

import { createCache, type CacheProvider, type CacheStats } from '@stock-bot/cache';
import type { RedisConfig } from './types';
import { getServiceConfig, type ServiceConfig } from './service-registry';
/**
* Service-aware cache that uses the service's Redis DB
* Automatically prefixes keys with the service's cache namespace
*/
export class ServiceCache implements CacheProvider {
private cache: CacheProvider;
private prefix: string;
constructor(
serviceName: string,
redisConfig: RedisConfig,
isGlobalCache: boolean = false,
logger?: any
) {
// Get service configuration
const serviceConfig = getServiceConfig(serviceName);
if (!serviceConfig && !isGlobalCache) {
throw new Error(`Unknown service: ${serviceName}`);
}
// Determine Redis DB and prefix
let db: number;
let prefix: string;
if (isGlobalCache) {
// Global cache uses db:0
db = 0;
prefix = 'stock-bot:shared';
} else {
// Service cache uses service's DB
db = serviceConfig!.db;
prefix = serviceConfig!.cachePrefix;
}
// Create underlying cache with correct DB
const cacheConfig = {
redisConfig: {
...redisConfig,
db,
},
keyPrefix: prefix + ':',
logger,
};
this.cache = createCache(cacheConfig);
this.prefix = prefix;
}
// Implement CacheProvider interface
async get<T = any>(key: string): Promise<T | null> {
return this.cache.get<T>(key);
}
async set<T = any>(
key: string,
value: T,
options?:
| number
| {
ttl?: number;
preserveTTL?: boolean;
onlyIfExists?: boolean;
onlyIfNotExists?: boolean;
getOldValue?: boolean;
}
): Promise<T | null> {
return this.cache.set(key, value, options);
}
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(): CacheStats {
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();
}
// Enhanced cache methods (delegate to underlying cache if available)
async update<T = any>(key: string, value: T): Promise<T | null> {
if (this.cache.update) {
return this.cache.update(key, value);
}
// Fallback implementation
return this.cache.set(key, value, { preserveTTL: true });
}
async setIfExists<T = any>(key: string, value: T, ttl?: number): Promise<boolean> {
if (this.cache.setIfExists) {
return this.cache.setIfExists(key, value, ttl);
}
// Fallback implementation
const result = await this.cache.set(key, value, { onlyIfExists: true, ttl });
return result !== null;
}
async setIfNotExists<T = any>(key: string, value: T, ttl?: number): Promise<boolean> {
if (this.cache.setIfNotExists) {
return this.cache.setIfNotExists(key, value, ttl);
}
// Fallback implementation
const result = await this.cache.set(key, value, { onlyIfNotExists: true, ttl });
return result !== null;
}
async replace<T = any>(key: string, value: T, ttl?: number): Promise<T | null> {
if (this.cache.replace) {
return this.cache.replace(key, value, ttl);
}
// Fallback implementation
return this.cache.set(key, value, ttl);
}
async updateField<T = any>(key: string, updater: (current: T | null) => T, ttl?: number): Promise<T | null> {
if (this.cache.updateField) {
return this.cache.updateField(key, updater, ttl);
}
// Fallback implementation
const current = await this.cache.get<T>(key);
const updated = updater(current);
return this.cache.set(key, updated, ttl);
}
/**
* Get the actual Redis key with prefix
*/
getKey(key: string): string {
return `${this.prefix}:${key}`;
}
}
/**
* Factory function to create service cache
*/
export function createServiceCache(
serviceName: string,
redisConfig: RedisConfig,
options: { global?: boolean; logger?: any } = {}
): ServiceCache {
return new ServiceCache(serviceName, redisConfig, options.global, options.logger);
}