interface CacheEntry<T> {
value: T;
expiresAt: number;
}
export class LRUCache<T> {
private cache = new Map<string, CacheEntry<T>>();
private readonly maxSize: number;
private readonly ttlMs: number;
constructor(maxSize: number, ttlMs: number) {
this.maxSize = maxSize;
this.ttlMs = ttlMs;
}
get(key: string): T | undefined {
const entry = this.cache.get(key);
if (!entry) return undefined;
if (Date.now() > entry.expiresAt) {
this.cache.delete(key);
return undefined;
}
this.cache.delete(key);
this.cache.set(key, entry);
return entry.value;
}
set(key: string, value: T): void {
this.cache.delete(key);
if (this.cache.size >= this.maxSize) {
const firstKey = this.cache.keys().next().value;
if (firstKey !== undefined) this.cache.delete(firstKey);
}
this.cache.set(key, {
value,
expiresAt: Date.now() + this.ttlMs,
});
}
invalidate(key: string): void {
this.cache.delete(key);
}
invalidatePrefix(prefix: string): void {
for (const key of this.cache.keys()) {
if (key.startsWith(prefix)) {
this.cache.delete(key);
}
}
}
clear(): void {
this.cache.clear();
}
get size(): number {
return this.cache.size;
}
}
export const gitCache = new LRUCache<unknown>(2000, 5 * 60 * 1000);
export const sessionCache = new LRUCache<unknown>(500, 2 * 60 * 1000);
export async function cached<T>(
cache: LRUCache<T>,
key: string,
factory: () => Promise<T>
): Promise<T> {
const existing = cache.get(key);
if (existing !== undefined) return existing;
const value = await factory();
if (value !== null && value !== undefined) {
if (Array.isArray(value) && value.length === 0) {
} else {
cache.set(key, value);
}
}
return value;
}
export function invalidateRepoCache(owner: string, repo: string): void {
gitCache.invalidatePrefix(`${owner}/${repo}:`);
}
|