62 lines
1.9 KiB
TypeScript
62 lines
1.9 KiB
TypeScript
import type { AnalyticsOverview, AnalyticsPeriod, ApiAppDependencies } from "./app-dependencies.js";
|
|
|
|
export const PUBLIC_ANALYTICS_OVERVIEW_CACHE_TTL_MS = 5_000;
|
|
export type PublicAnalyticsOverviewScope = "platform-global";
|
|
|
|
type PublicAnalyticsOverviewDependencies = Pick<ApiAppDependencies, "getAnalyticsOverview">;
|
|
|
|
type CacheEntry = {
|
|
dependencies: PublicAnalyticsOverviewDependencies;
|
|
expiresAt: number;
|
|
value?: AnalyticsOverview;
|
|
pending?: Promise<AnalyticsOverview>;
|
|
};
|
|
|
|
type CacheKey = `${PublicAnalyticsOverviewScope}:${AnalyticsPeriod}`;
|
|
|
|
const cache = new Map<CacheKey, CacheEntry>();
|
|
|
|
/**
|
|
* Coalesces simultaneous public dashboard reads and keeps the expensive
|
|
* aggregate query off Postgres for a short, bounded freshness window.
|
|
*/
|
|
export async function getCachedPublicAnalyticsOverview(input: {
|
|
dependencies: PublicAnalyticsOverviewDependencies;
|
|
scope: PublicAnalyticsOverviewScope;
|
|
period: AnalyticsPeriod;
|
|
now?: number;
|
|
}): Promise<AnalyticsOverview> {
|
|
const now = input.now ?? Date.now();
|
|
const key: CacheKey = `${input.scope}:${input.period}`;
|
|
const current = cache.get(key);
|
|
if (current?.dependencies === input.dependencies && current.expiresAt > now) {
|
|
if (current.value) return current.value;
|
|
if (current.pending) return current.pending;
|
|
}
|
|
|
|
const pending = input.dependencies.getAnalyticsOverview({ period: input.period, portalId: null });
|
|
const next: CacheEntry = {
|
|
dependencies: input.dependencies,
|
|
expiresAt: now + PUBLIC_ANALYTICS_OVERVIEW_CACHE_TTL_MS,
|
|
pending,
|
|
};
|
|
cache.set(key, next);
|
|
try {
|
|
const value = await pending;
|
|
if (cache.get(key) === next) {
|
|
next.value = value;
|
|
next.pending = undefined;
|
|
}
|
|
return value;
|
|
} catch (error) {
|
|
if (cache.get(key) === next) {
|
|
cache.delete(key);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function resetPublicAnalyticsOverviewCacheForTests(): void {
|
|
cache.clear();
|
|
}
|