import { getCache } from './commons/cache/index.js'; import { appEvents } from './events.js'; import pino from 'pino'; import path from 'path'; const logFile = path.join(process.cwd(), 'logs', 'cache.json'); const fileTransport = pino.transport({ target: 'pino/file', options: { destination: logFile, mkdir: true } }); const logger = pino({ level: process.env.PINO_LOG_LEVEL || 'info', base: { product: 'cache' }, timestamp: pino.stdTimeFunctions.isoTime, }, pino.multistream([ { stream: fileTransport, level: 'info' } ])); export class AppCache { static instance; // Dependencies: key -> [dependencies] // Defines what each type DEPENDS ON. // If 'categories' changes, any type that has 'categories' in its dependency list must be invalidated. static DEPENDENCIES = { 'posts': ['categories', 'pictures'], // posts depend on categories and pictures 'pages': ['categories', 'pictures', 'translations'], 'categories': ['types'], 'translations': [], // widget/category translations (wt:* keys) 'feed': ['posts', 'pages', 'categories'], 'auth': [] // No dependencies, standalone }; constructor() { } static getInstance() { if (!AppCache.instance) { AppCache.instance = new AppCache(); } return AppCache.instance; } async get(type) { const cache = getCache(); const val = await cache.get(type); return val; } async set(type, data, ttl) { const cache = getCache(); await cache.set(type, data, ttl); } /** * Silent cache invalidation — clears cache for the given type and * cascades to dependents. Does NOT emit SSE events. * Use `notify()` in route handlers for explicit SSE. */ async invalidate(type) { const cache = getCache(); if (type === 'feed') { await cache.flush('*-feed*'); await cache.flush('home-feed*'); } else if (type === 'translations') { await cache.flush('wt:*'); await cache.flush('page-details-*'); } else { await cache.del(type); } // Find types that depend on this type const dependents = Object.keys(AppCache.DEPENDENCIES).filter(key => AppCache.DEPENDENCIES[key].includes(type)); logger.info({ type, dependents }, 'Cache invalidated'); if (dependents.length > 0) { await Promise.all(dependents.map(dep => this.invalidate(dep))); } } /** * Flush cache entries by pattern. Silent — no SSE. */ async flush(pattern) { const cache = getCache(); await cache.flush(pattern); logger.info({ pattern: pattern || 'all' }, 'Cache flushed'); } /** * Emit exactly 1 SSE event to notify clients of a change. * Call this in route handlers AFTER cache invalidation. * * @param type - Entity type (e.g. 'post', 'page', 'category', 'picture') * @param id - Entity ID (null for list-level / system changes) * @param action - The mutation that occurred */ notify(type, id, action) { logger.info({ type, id, action }, 'Cache notify'); appEvents.emitUpdate(type, action, { id }, 'cache'); } inspect() { const cache = getCache(); return { info: cache.info(), dependencies: AppCache.DEPENDENCIES, entries: cache.entries(), }; } } export const appCache = AppCache.getInstance(); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FjaGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2FjaGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3BELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFeEMsT0FBTyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQ3hCLE9BQU8sSUFBSSxNQUFNLE1BQU0sQ0FBQztBQUV4QixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxNQUFNLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFFL0QsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQztJQUNqQyxNQUFNLEVBQUUsV0FBVztJQUNuQixPQUFPLEVBQUUsRUFBRSxXQUFXLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUU7Q0FDakQsQ0FBQyxDQUFDO0FBRUgsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUNmO0lBQ0ksS0FBSyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxJQUFJLE1BQU07SUFDM0MsSUFBSSxFQUFFLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRTtJQUMxQixTQUFTLEVBQUUsSUFBSSxDQUFDLGdCQUFnQixDQUFDLE9BQU87Q0FDM0MsRUFDRCxJQUFJLENBQUMsV0FBVyxDQUFDO0lBQ2IsRUFBRSxNQUFNLEVBQUUsYUFBYSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUU7Q0FDM0MsQ0FBQyxDQUNMLENBQUM7QUFHRixNQUFNLE9BQU8sUUFBUTtJQUNULE1BQU0sQ0FBQyxRQUFRLENBQVc7SUFFbEMsc0NBQXNDO0lBQ3RDLHFDQUFxQztJQUNyQyxzR0FBc0c7SUFDOUYsTUFBTSxDQUFDLFlBQVksR0FBNkI7UUFDcEQsT0FBTyxFQUFFLENBQUMsWUFBWSxFQUFFLFVBQVUsQ0FBQyxFQUFFLDBD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