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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user