All files / approval-engine-2/domain data-redis.ts

0% Statements 0/62
0% Branches 0/10
0% Functions 0/17
0% Lines 0/59

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164                                                                                                                                                                                                                                                                                                                                       
import { Redis, Cluster } from "ioredis";
import { CachesConfig } from "../../config/caches.config.js";
import { injectable, inject } from "tsyringe";
 
@injectable()
export class AppDataRedisService {
  public redisClient: Redis | Cluster;
  private pingInterval: NodeJS.Timeout | null = null;
  private isClusterMode: boolean;
 
  constructor(
    @inject(CachesConfig)
    private CachesConfig: CachesConfig,
  ) {
    const { host, port, password, isredisproduction } =
      this.CachesConfig.redis.connection;
    this.isClusterMode = isredisproduction === "true";
 
    if (this.isClusterMode) {
      // Dragonfly - Redis Cluster
      this.redisClient = new Cluster(
        [
          {
            host: host,
            port: Number(port),
          },
        ],
        {
          redisOptions: {
            password: password,
            tls: {
              rejectUnauthorized: false,
            },
          },
        },
      );
 
      this.redisClient.on("connect", () => {
        console.log("Redis Cluster connected successfully!");
        this.pingInterval = setInterval(async () => {
          try {
            const res = await this.redisClient.get("healthcheck");
            console.log("Healthcheck (cluster) get response:", res ?? "null");
          } catch (err) {
            console.error("Error during Redis cluster healthcheck:", err);
          }
        }, 60000);
      });
 
      this.redisClient.on("error", (err: Error) => {
        console.error("Redis Cluster Error:", err);
      });
 
      this.redisClient.on("end", () => {
        console.log("Redis cluster connection closed.");
        if (this.pingInterval) clearInterval(this.pingInterval);
      });
 
      this.redisClient.on("reconnecting", (attempt: number) => {
        console.log(`Reconnecting to Redis Cluster... Attempt: ${attempt}`);
      });
    } else {
      // Upstash - Redis Standalone
      this.redisClient = new Redis({
        host: host,
        port: Number(port),
        password: password,
        tls: {
          rejectUnauthorized: false,
        },
      });
 
      this.redisClient.on("connect", () => {
        console.log("Redis Client connected successfully!");
        this.pingInterval = setInterval(async () => {
          try {
            const res = await this.redisClient.ping();
            console.log("Healthcheck (client) ping response:", res);
          } catch (err) {
            console.error("Error during Redis client ping:", err);
          }
        }, 60000);
      });
 
      this.redisClient.on("error", (err: Error) => {
        console.error("Redis Client Error:", err);
      });
 
      this.redisClient.on("end", () => {
        console.log("Redis client connection closed.");
        if (this.pingInterval) clearInterval(this.pingInterval);
      });
 
      this.redisClient.on("reconnecting", (attempt: number) => {
        console.log(`Reconnecting to Redis... Attempt: ${attempt}`);
      });
    }
  }
 
  /**
   * Unified scan method that works with both standalone and cluster modes
   */
  async scanKeys(pattern: string): Promise<string[]> {
    const keys: string[] = [];
 
    try {
      let cursor = "0";
      do {
        const [nextCursor, matchedKeys] = await this.redisClient.scan(
          cursor,
          "MATCH",
          pattern,
          "COUNT",
          "100",
        );
        cursor = nextCursor;
        keys.push(...matchedKeys);
      } while (cursor !== "0");
 
      console.log(
        `[scanKeys] Found ${keys.length} keys for pattern: ${pattern}`,
      );
      return keys;
    } catch (error) {
      console.error(
        `[scanKeys] Error scanning keys for pattern ${pattern}:`,
        error,
      );
      return [];
    }
  }
 
  async disconnect(): Promise<void> {
    try {
      await this.redisClient.quit();
      console.log("Redis disconnected successfully.");
    } catch (error) {
      console.error("Error disconnecting Redis:", error);
    }
  }
 
  async hGet(key: string, field: string): Promise<string | null> {
    try {
      const result = await this.redisClient.hget(key, field);
      return result ?? null;
    } catch (error) {
      console.error("Error during hGet:", error);
      throw error;
    }
  }
 
  multi(): ReturnType<Redis["multi"]> {
    return this.redisClient.multi();
  }
 
  async expire(key: string, seconds: number): Promise<number> {
    return await this.redisClient.expire(key, seconds);
  }
 
  async hGetAll(key: string): Promise<Record<string, string>> {
    return await this.redisClient.hgetall(key);
  }
}