Repository navigation
Expand file tree
/
Copy pathrow-count-cache.ts
More file actions
74 lines (60 loc) · 1.7 KB
/
Copy pathrow-count-cache.ts
File metadata and controls
74 lines (60 loc) · 1.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import { getPlatformCacheVersion } from "@/lib/platform/cache";
const ROW_COUNT_TTL_MS = 5 * 60_000;
const ROW_COUNT_MAX_ENTRIES = 200;
interface RowCountEntry {
cacheVersion: number;
expiresAt: number;
promise: Promise<number>;
}
const rowCountCache = new Map<string, RowCountEntry>();
function buildKey(sourceId: string, tableKey: string) {
return `${sourceId}::${tableKey}`;
}
function isFresh(entry: RowCountEntry, now: number, version: number) {
return entry.cacheVersion === version && entry.expiresAt > now;
}
function trimCache() {
while (rowCountCache.size > ROW_COUNT_MAX_ENTRIES) {
const oldestKey = rowCountCache.keys().next().value;
if (oldestKey === undefined) {
break;
}
rowCountCache.delete(oldestKey);
}
}
export async function getCachedRowCount(
sourceId: string,
tableKey: string,
fetchCount: () => Promise<number>,
): Promise<number> {
const key = buildKey(sourceId, tableKey);
const version = getPlatformCacheVersion();
const now = Date.now();
const existing = rowCountCache.get(key);
if (existing && isFresh(existing, now, version)) {
rowCountCache.delete(key);
rowCountCache.set(key, existing);
return existing.promise;
}
if (existing) {
rowCountCache.delete(key);
}
const countPromise = fetchCount();
const entry: RowCountEntry = {
cacheVersion: version,
expiresAt: now + ROW_COUNT_TTL_MS,
promise: countPromise,
};
countPromise.catch(() => {
const current = rowCountCache.get(key);
if (current === entry) {
rowCountCache.delete(key);
}
});
rowCountCache.set(key, entry);
trimCache();
return countPromise;
}
export function clearRowCountCacheForTests() {
rowCountCache.clear();
}