feat: add server-side TTL cache + in-flight dedup to request handler

10s default TTL deduplicates concurrent requests from multiple clients
polling the same integration. 83 users hitting the same widget every
30s now produce ~6 upstream calls/min instead of ~166.
This commit is contained in:
Thomas Camlong committed 2026-07-01 20:18:37 +02:00
1 parent 781378dc76
commit 8571b4dcb7
2 files changed
+228 -209

No files matched your search

@@ -1,14 +1,44 @@
interface Options<TData, TInput extends Record<string, unknown>> {
requestAsync: (input: TInput) => Promise<TData>;
cacheTtlMs?: number;
}
type CacheEntry<TData> = { data: TData; timestamp: Date; expiresAt: number };
// ponytail: module-level cache keyed by JSON(input). Ceiling: unbounded keys if inputs are high-cardinality; upgrade to LRU if needed.
const cache = new Map<string, CacheEntry<unknown>>();
const inflight = new Map<string, Promise<CacheEntry<unknown>>>();
const DEFAULT_TTL_MS = 10_000;
export const createRequestHandler = <TData, TInput extends Record<string, unknown>>(
options: Options<TData, TInput>,
) => ({
handler: (input: TInput) => ({
async getDataAsync(): Promise<{ data: TData; timestamp: Date }> {
const data = await options.requestAsync(input);
return { data, timestamp: new Date() };
const ttl = options.cacheTtlMs ?? DEFAULT_TTL_MS;
const key = JSON.stringify(input);
const cached = cache.get(key) as CacheEntry<TData> | undefined;
if (cached && Date.now() < cached.expiresAt) {
return { data: cached.data, timestamp: cached.timestamp };
}
const existing = inflight.get(key);
if (existing) return existing as Promise<CacheEntry<TData>>;
const promise = options.requestAsync(input).then((data) => {
const entry: CacheEntry<TData> = { data, timestamp: new Date(), expiresAt: Date.now() + ttl };
cache.set(key, entry as CacheEntry<unknown>);
inflight.delete(key);
return entry;
}).catch((err) => {
inflight.delete(key);
throw err;
});
inflight.set(key, promise as Promise<CacheEntry<unknown>>);
return promise;
},
}),
});