UNPKG

@pulzar/core

Version:

Next-generation Node.js framework for ultra-fast web applications with zero-reflection DI, GraphQL, WebSockets, events, and edge runtime support

148 lines 4.85 kB
import { logger } from "../../utils/logger"; export class RedisSessionStore { redis; keyPrefix; serializer; constructor(options) { this.redis = options.redis; this.keyPrefix = options.keyPrefix || "session:"; this.serializer = options.serializer || { stringify: JSON.stringify, parse: JSON.parse, }; } getKey(sessionId) { return `${this.keyPrefix}${sessionId}`; } async get(sessionId) { try { const key = this.getKey(sessionId); const data = await this.redis.get(key); if (!data) return null; const session = this.serializer.parse(data); // Check expiration (Redis might not have TTL set correctly) if (session.expiresAt < new Date()) { await this.delete(sessionId); return null; } return session; } catch (error) { logger.error("Failed to get session from Redis", { sessionId, error }); return null; } } async set(sessionId, data) { try { const key = this.getKey(sessionId); const serialized = this.serializer.stringify(data); // Calculate TTL in seconds const ttl = Math.max(0, Math.floor((data.expiresAt.getTime() - Date.now()) / 1000)); if (ttl > 0) { await this.redis.setex(key, ttl, serialized); } else { // Session already expired, don't store it logger.warn("Attempted to store expired session", { sessionId, expiresAt: data.expiresAt, }); } } catch (error) { logger.error("Failed to set session in Redis", { sessionId, error }); throw error; } } async delete(sessionId) { try { const key = this.getKey(sessionId); await this.redis.del(key); } catch (error) { logger.error("Failed to delete session from Redis", { sessionId, error }); throw error; } } async touch(sessionId) { try { const key = this.getKey(sessionId); const ttl = await this.redis.ttl(key); if (ttl > 0) { await this.redis.expire(key, ttl); } } catch (error) { logger.error("Failed to touch session in Redis", { sessionId, error }); throw error; } } async clear() { try { const pattern = `${this.keyPrefix}*`; const keys = await this.redis.keys(pattern); if (keys.length > 0) { await this.redis.del(...keys); } } catch (error) { logger.error("Failed to clear sessions from Redis", { error }); throw error; } } async cleanup() { // Redis handles TTL automatically, but we can implement additional cleanup if needed try { const pattern = `${this.keyPrefix}*`; const keys = await this.redis.keys(pattern); let cleaned = 0; for (const key of keys) { const ttl = await this.redis.ttl(key); if (ttl === -1) { // Key exists but has no TTL, remove it await this.redis.del(key); cleaned++; } } if (cleaned > 0) { logger.debug("Cleaned up Redis sessions without TTL", { cleaned }); } } catch (error) { logger.error("Failed to cleanup Redis sessions", { error }); } } /** * Get session statistics */ async getStats() { try { const pattern = `${this.keyPrefix}*`; const keys = await this.redis.keys(pattern); let keysWithoutTTL = 0; let totalTTL = 0; let validTTLs = 0; for (const key of keys) { const ttl = await this.redis.ttl(key); if (ttl === -1) { keysWithoutTTL++; } else if (ttl > 0) { totalTTL += ttl; validTTLs++; } } return { totalSessions: keys.length, keysWithoutTTL, avgTTL: validTTLs > 0 ? Math.round(totalTTL / validTTLs) : 0, }; } catch (error) { logger.error("Failed to get Redis session stats", { error }); return { totalSessions: 0, keysWithoutTTL: 0, avgTTL: 0 }; } } } //# sourceMappingURL=redis-session.store.js.map