Source

database/redis.client.js

import { env } from "@config/env";
import { getLogger } from "@utils/asyncLocalStorage";
import Redis from "ioredis";
let redisClient = null;
let isConnected = false;
/**
 * Establish Redis connection for caching and pub/sub use cases.
 * @category Database
 */
export function connectToRedis() {
    const logger = getLogger();
    if (isConnected && redisClient) {
        logger.info("Redis already connected");
        return;
    }
    try {
        logger.info("Attempting to connect to Redis...");
        redisClient = new Redis(env.REDIS_URI, {
            db: env.REDIS_DB_INDEX,
        });
        setupRedisEventListeners(redisClient);
        isConnected = true;
        logger.info("Successfully connected to Redis");
    }
    catch (err) {
        logger.error({
            error: err instanceof Error ? err.message : "Unknown error",
        }, "Failed to connect to Redis");
        throw err;
    }
}
/**
 * Internal helper that wires Redis connection lifecycle logging.
 * @category Database
 * @param {Redis} client Connected Redis instance.
 */
function setupRedisEventListeners(client) {
    const logger = getLogger();
    client.on("connect", () => {
        logger.info("Redis connection established");
    });
    client.on("ready", () => {
        isConnected = true;
        logger.info("Redis client ready");
    });
    client.on("error", (err) => {
        logger.error({ error: err.message }, "Redis connection error");
    });
    client.on("close", () => {
        isConnected = false;
        logger.warn("Redis connection closed");
    });
    client.on("reconnecting", () => {
        logger.info("Redis reconnecting...");
    });
    client.on("end", () => {
        isConnected = false;
        logger.warn("Redis connection ended");
    });
}
/**
 * Gracefully close the Redis connection if active.
 * @category Database
 * @returns {Promise<void>} Resolves when the connection quits.
 */
export async function closeRedisConnection() {
    const logger = getLogger();
    if (!redisClient || !isConnected) {
        logger.info("Redis not connected, skipping close");
        return;
    }
    try {
        logger.info("Closing Redis connection...");
        await redisClient.quit();
        isConnected = false;
        redisClient = null;
        logger.info("Redis connection closed successfully");
    }
    catch (err) {
        logger.error({
            error: err instanceof Error ? err.message : "Unknown error",
        }, "Error closing Redis connection");
    }
}
/**
 * Snapshot of the Redis client status.
 * @category Database
 * @returns {object} High-level connection flags.
 */
export function getRedisStatus() {
    return {
        isConnected,
        status: redisClient?.status,
    };
}
/**
 * Convenience helpers for interacting with Redis commands.
 * @category Database
 */
export const redisUtils = {
    /**
     * Write a value to Redis, optionally with TTL.
     * @param {string} key Redis key to store.
     * @param {string} value Serialized payload.
     * @param {number} [ttlSeconds] Optional TTL in seconds.
     * @returns {Promise<void>} Promise that resolves when the command completes.
     */
    async set(key, value, ttlSeconds) {
        if (!redisClient) {
            throw new Error("Redis client not connected");
        }
        if (ttlSeconds) {
            await redisClient.setex(key, ttlSeconds, value);
        }
        else {
            await redisClient.set(key, value);
        }
    },
    /**
     * Fetch a value from Redis.
     * @param {string} key Redis key to read.
     * @returns {Promise<string | null>} Stored string or null when missing.
     */
    async get(key) {
        if (!redisClient) {
            throw new Error("Redis client not connected");
        }
        return await redisClient.get(key);
    },
    /**
     * Delete a Redis key.
     * @param {string} key Redis key to remove.
     * @returns {Promise<number>} Number of keys removed.
     */
    async del(key) {
        if (!redisClient) {
            throw new Error("Redis client not connected");
        }
        return await redisClient.del(key);
    },
    /**
     * Determine if a key exists in Redis.
     * @param {string} key Redis key to check.
     * @returns {Promise<boolean>} True when the key exists.
     */
    async exists(key) {
        if (!redisClient) {
            throw new Error("Redis client not connected");
        }
        const result = await redisClient.exists(key);
        return result === 1;
    },
    /**
     * Publish a message to a channel.
     * @param {string} channel Channel name.
     * @param {string} message Payload to publish.
     * @returns {Promise<number>} Subscribers that received the message.
     */
    async publish(channel, message) {
        if (!redisClient) {
            throw new Error("Redis client not connected");
        }
        return await redisClient.publish(channel, message);
    },
    /**
     * Subscribe to a channel and invoke a callback when messages arrive.
     * @param {string} channel Channel to subscribe to.
     * @param {Function} callback Handler executed per message.
     * @returns {Promise<void>} Resolves once subscribed.
     */
    async subscribe(channel, callback) {
        if (!redisClient) {
            throw new Error("Redis client not connected");
        }
        await redisClient.subscribe(channel);
        redisClient.on("message", (receivedChannel, message) => {
            if (receivedChannel === channel) {
                callback(message);
            }
        });
    },
};