Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

64 lines
1.2 KiB
TypeScript

type CacheEntry<T> = {
data?: T;
promise?: Promise<T>;
expiresAt: number;
};
const clientCache = new Map<string, CacheEntry<unknown>>();
interface CacheOptions {
ttlMs?: number;
force?: boolean;
}
export async function withClientCache<T>(
key: string,
loader: () => Promise<T>,
options: CacheOptions = {},
): Promise<T> {
const { ttlMs = 30_000, force = false } = options;
if (typeof window === "undefined") {
return loader();
}
const now = Date.now();
const cached = clientCache.get(key) as CacheEntry<T> | undefined;
if (!force && cached) {
if (cached.data !== undefined && cached.expiresAt > now) {
return cached.data;
}
if (cached.promise) {
return cached.promise;
}
}
const promise = loader()
.then((value) => {
clientCache.set(key, {
data: value,
expiresAt: Date.now() + ttlMs,
});
return value;
})
.catch((error) => {
clientCache.delete(key);
throw error;
});
clientCache.set(key, {
promise,
expiresAt: now + ttlMs,
});
return promise;
}
export function invalidateClientCache(prefix: string): void {
for (const key of clientCache.keys()) {
if (key.startsWith(prefix)) {
clientCache.delete(key);
}
}
}