Files
Kordant/packages/shared-notifications/src/services/redis.service.ts
Senior Engineer 574bcf2264 FRE-4521 Implement Redis integration for rate limiting and deduplication
- Add ioredis dependency for Redis connection pooling
- Create RedisService singleton with connection management
- Add Redis config (url, dedupWindowSeconds) to notification.config.ts
- Implement NotificationService.checkRateLimit using Redis INCR+EXPIRE
- Implement NotificationService.deduplicateNotification using Redis SET/NX
- Add configurable rate limit windows and thresholds via env vars
- Add 29 unit tests covering Redis operations, rate limiting, and dedup
- All tests pass, TypeScript compiles cleanly for new files
2026-05-01 16:13:17 -04:00

74 lines
1.9 KiB
TypeScript

import { Redis, RedisOptions } from 'ioredis';
export interface RedisServiceConfig {
url?: string;
options?: RedisOptions;
}
export class RedisService {
private static instance: RedisService;
private client: Redis;
private constructor(config?: RedisServiceConfig) {
const url = config?.url || process.env.REDIS_URL || 'redis://localhost:6379';
this.client = new Redis(url, config?.options || {});
}
static getInstance(config?: RedisServiceConfig): RedisService {
if (!RedisService.instance) {
RedisService.instance = new RedisService(config);
}
return RedisService.instance;
}
getClient(): Redis {
return this.client;
}
async isConnected(): Promise<boolean> {
return this.client.status === 'ready';
}
async increment(key: string, expirySeconds?: number): Promise<number> {
const current = await this.client.incr(key);
if (current === 1 && expirySeconds) {
await this.client.expire(key, expirySeconds);
}
return current;
}
async getCounter(key: string): Promise<number> {
const value = await this.client.get(key);
return value ? parseInt(value, 10) : 0;
}
async setWithExpiry(key: string, value: string, seconds: number): Promise<'OK' | 1> {
return this.client.setex(key, seconds, value);
}
async setIfNotExists(key: string, value: string, seconds: number): Promise<boolean> {
const result = await this.client.set(key, value, 'EX', seconds, 'NX');
return result === 'OK';
}
async get(key: string): Promise<string | null> {
return this.client.get(key);
}
async ttl(key: string): Promise<number> {
return this.client.ttl(key);
}
async delete(key: string): Promise<number> {
return this.client.del(key);
}
async getTTL(key: string): Promise<number> {
return this.client.ttl(key);
}
async close(): Promise<void> {
await this.client.quit();
}
}