@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
JavaScript
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