Import source: app/backend/src/lib/cache

Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
arsam
2026-09-18 23:19:11 +03:30
parent e2e7945032
commit 4b065a4062
+54
View File
@@ -0,0 +1,54 @@
import { redis } from '../redis.js';
export interface CacheOptions {
ttlSeconds: number;
graceSeconds?: number;
}
export async function cached<T>(key: string, ttlSeconds: number | CacheOptions, fetcher: () => Promise<T>): Promise<T> {
const opts: CacheOptions = typeof ttlSeconds === 'number'
? { ttlSeconds }
: ttlSeconds;
const graceSeconds = opts.graceSeconds ?? Math.max(Math.floor(opts.ttlSeconds * 0.25), 30);
const totalTtl = opts.ttlSeconds + graceSeconds;
const hit = await redis.get(key);
if (hit !== null) {
try {
const parsed = JSON.parse(hit) as T;
const ttlRemaining = await redis.ttl(key);
if (ttlRemaining >= 0 && ttlRemaining <= graceSeconds) {
fetcher()
.then((fresh) => {
if (fresh !== undefined) {
redis.set(key, JSON.stringify(fresh), 'EX', totalTtl).catch(() => {});
}
})
.catch(() => {});
}
return parsed;
} catch {
await redis.del(key);
}
}
const fresh = await fetcher();
if (fresh !== undefined) {
await redis.set(key, JSON.stringify(fresh), 'EX', totalTtl);
}
return fresh;
}
export async function bust(pattern: string): Promise<number> {
let cursor = '0';
let removed = 0;
do {
const [next, batch] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
cursor = next;
if (batch.length > 0) {
removed += await redis.del(...batch);
}
} while (cursor !== '0');
return removed;
}