Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
55 lines
1.5 KiB
TypeScript
55 lines
1.5 KiB
TypeScript
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;
|
|
}
|