All files / web/src/lib redis.ts

47.74% Statements 53/111
71.42% Branches 5/7
25% Functions 1/4
47.74% Lines 53/111

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 1122x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 70x 70x 70x 70x     70x 70x 70x 70x 69x 69x 1x 1x 1x 1x 1x 1x 1x                                                                   70x 2x 2x 2x 2x 2x       2x 2x 2x 2x 2x 2x 2x                                          
/**
 * Redis client utility
 *
 * Provides optional Redis connectivity - uses Redis when REDIS_URL is set,
 * otherwise returns null so callers can fall back to in-memory storage.
 *
 * This allows the app to work without Redis in development while using
 * Redis in production for cross-instance state sharing.
 */
 
import Redis from 'ioredis'
 
// Singleton Redis client
let redisClient: Redis | null = null
let redisInitialized = false
 
/**
 * Get the Redis client, or null if Redis is not configured
 *
 * Returns the same instance on subsequent calls (singleton pattern).
 * Returns null if REDIS_URL is not set in environment.
 */
export function getRedisClient(): Redis | null {
  const redisUrl = process.env.REDIS_URL
 
  // If we've already initialized with a client, return it
  if (redisInitialized && redisClient) {
    return redisClient
  }
 
  // If we initialized but got null AND REDIS_URL is still not set, return null
  // But if REDIS_URL is NOW set (e.g., build-time vs runtime), try again
  if (redisInitialized && !redisUrl) {
    return null
  }
 
  redisInitialized = true
 
  if (!redisUrl) {
    console.log('[Redis] REDIS_URL not set, using in-memory storage')
    return null
  }

  try {
    redisClient = new Redis(redisUrl, {
      maxRetriesPerRequest: 3,
      retryStrategy(times) {
        if (times > 3) {
          console.error('[Redis] Max retries reached, giving up')
          return null
        }
        const delay = Math.min(times * 200, 2000)
        console.log(`[Redis] Retrying connection in ${delay}ms (attempt ${times})`)
        return delay
      },
      lazyConnect: false,
    })

    redisClient.on('connect', () => {
      console.log('[Redis] Connected successfully')
    })

    redisClient.on('error', (err) => {
      console.error('[Redis] Connection error:', err.message)
    })

    redisClient.on('close', () => {
      console.log('[Redis] Connection closed')
    })

    return redisClient
  } catch (err) {
    console.error('[Redis] Failed to create client:', err)
    return null
  }
}
 
/**
 * Check if Redis is available
 */
export function isRedisAvailable(): boolean {
  const client = getRedisClient()
  return client !== null && client.status === 'ready'
}
 
/**
 * Create a new Redis client for pub/sub (Socket.IO adapter needs separate clients)
 *
 * Returns null if REDIS_URL is not set.
 */
export function createRedisClient(): Redis | null {
  const redisUrl = process.env.REDIS_URL
  if (!redisUrl) {
    return null
  }

  try {
    return new Redis(redisUrl, {
      maxRetriesPerRequest: 3,
      retryStrategy(times) {
        if (times > 3) {
          return null
        }
        return Math.min(times * 200, 2000)
      },
    })
  } catch (err) {
    console.error('[Redis] Failed to create pub/sub client:', err)
    return null
  }
}