mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-10-19 14:17:20 +02:00
* perf: bound request work and resource usage * fix: address performance review regressions * fix: guard content expiration and dashboard state * fix: tolerate cache outages and retire derived paths * fix: revalidate expiration claims and status pagination * fix: retry interrupted repository metadata cleanup * fix: retire obsolete content cache storage * fix: activate tree metadata and recheck expiration claims * fix: fence cache publication across expiration * fix: recover staged trees and enforce cache lifecycle cleanup * fix: prune expired transforms during database outages * fix: finish conference removals and reload expiration candidates
23 lines
1.0 KiB
TypeScript
23 lines
1.0 KiB
TypeScript
/** Bounded TTL cache with one calculation per key and retry after failure. */
|
|
export class AsyncCache<T> {
|
|
private entries = new Map<string, { expires: number; value: Promise<T> }>();
|
|
constructor(private ttl: number, private capacity: number, private now = () => Date.now()) {}
|
|
get size() { this.prune(); return this.entries.size; }
|
|
clear() { this.entries.clear(); }
|
|
private prune() {
|
|
for (const [key, entry] of this.entries) if (entry.expires <= this.now()) this.entries.delete(key);
|
|
}
|
|
get(key: string, calculate: () => Promise<T>): Promise<T> {
|
|
this.prune();
|
|
const cached = this.entries.get(key);
|
|
if (cached) return cached.value;
|
|
if (this.entries.size >= this.capacity) this.entries.delete(this.entries.keys().next().value!);
|
|
const entry = { expires: Infinity, value: Promise.resolve().then(calculate) };
|
|
this.entries.set(key, entry);
|
|
entry.value.then(() => { entry.expires = this.now() + this.ttl; }, () => {
|
|
if (this.entries.get(key) === entry) this.entries.delete(key);
|
|
});
|
|
return entry.value;
|
|
}
|
|
}
|