const { getRedisClient } = require('./redisClient') // الگوی cache-aside عمومی: هر جایی یه کوئری/محاسبه‌ی سنگین و پرتکرار داره، به‌جای // شمارنده‌ی incremental (که نگه‌داشتنِ درستش نیاز به ردیابیِ دقیقِ *همه‌ی* نقاطِ // تغییر داره و راحت drift پیدا می‌کنه)، فقط نتیجه‌ی همون کوئریِ فعلی رو با یه TTLِ // کوتاه کش می‌کنه. اگه Redis در دسترس نباشه، هر بار مستقیم fetchFn اجرا می‌شه — // دقیقاً همون رفتارِ قبل از اضافه‌شدنِ کش (کندتر، ولی همیشه درست) async function getOrSetCache (key, ttlSeconds, fetchFn) { const redis = getRedisClient() if (!redis) return fetchFn() try { const cached = await redis.get(key) if (cached != null) return JSON.parse(cached) } catch (err) { console.error(`[cache] read error for ${key}:`, err.message) } const fresh = await fetchFn() try { await redis.set(key, JSON.stringify(fresh), { EX: ttlSeconds }) } catch (err) { console.error(`[cache] write error for ${key}:`, err.message) } return fresh } // موقعِ تغییرِ داده (پیام جدید/خونده‌شدنِ پیام، لایک/آنلایک، پست جدید و...) باید // صریحاً صدا زده بشه تا کشِ مرتبط بلافاصله باطل بشه، نه اینکه تا انقضای TTL کهنه بمونه async function invalidateCache (key) { const redis = getRedisClient() if (!redis) return try { await redis.del(key) } catch (err) { console.error(`[cache] invalidate error for ${key}:`, err.message) } } // برای وقتی چندین کلید هم‌زمان باید باطل بشن (مثلاً کشِ فید همه‌ی صفحه‌های اکسپلور) async function invalidateCachePattern (pattern) { const redis = getRedisClient() if (!redis) return try { const keys = [] for await (const key of redis.scanIterator({ MATCH: pattern, COUNT: 100 })) { keys.push(key) } if (keys.length) await redis.del(keys) } catch (err) { console.error(`[cache] pattern invalidate error for ${pattern}:`, err.message) } } module.exports = { getOrSetCache, invalidateCache, invalidateCachePattern }