97 lines
2.0 KiB
TypeScript
97 lines
2.0 KiB
TypeScript
const SESSION_CACHE_TTL_MS = 5000
|
|
|
|
type SessionCacheSource = "memory" | "shared" | "redis"
|
|
|
|
interface SessionCacheEntry<T> {
|
|
data: T
|
|
expiresAt: number
|
|
}
|
|
|
|
const sessionCache = new Map<string, SessionCacheEntry<any>>()
|
|
const inflightSessionLoads = new Map<string, Promise<any | null>>()
|
|
|
|
const getNow = () => Date.now()
|
|
|
|
const getValidCachedSession = <T>(redisKey: string) => {
|
|
const cacheEntry = sessionCache.get(redisKey)
|
|
|
|
if (!cacheEntry) {
|
|
return null
|
|
}
|
|
|
|
if (cacheEntry.expiresAt <= getNow()) {
|
|
sessionCache.delete(redisKey)
|
|
return null
|
|
}
|
|
|
|
return cacheEntry.data as T
|
|
}
|
|
|
|
export const getCachedSession = <T>(redisKey: string) =>
|
|
getValidCachedSession<T>(redisKey)
|
|
|
|
export const setCachedSession = <T>(redisKey: string, data: T) => {
|
|
sessionCache.set(redisKey, {
|
|
data,
|
|
expiresAt: getNow() + SESSION_CACHE_TTL_MS,
|
|
})
|
|
}
|
|
|
|
export const deleteCachedSession = (redisKey: string) => {
|
|
sessionCache.delete(redisKey)
|
|
inflightSessionLoads.delete(redisKey)
|
|
}
|
|
|
|
export const getSessionCacheTtlMs = () => SESSION_CACHE_TTL_MS
|
|
|
|
export const getOrLoadSession = async <T>(
|
|
redisKey: string,
|
|
loader: () => Promise<T | null>
|
|
): Promise<{
|
|
data: T | null
|
|
source: SessionCacheSource
|
|
}> => {
|
|
const cachedData = getValidCachedSession<T>(redisKey)
|
|
|
|
if (cachedData) {
|
|
return {
|
|
data: cachedData,
|
|
source: "memory",
|
|
}
|
|
}
|
|
|
|
const inflightLoad = inflightSessionLoads.get(redisKey)
|
|
|
|
if (inflightLoad) {
|
|
return {
|
|
data: (await inflightLoad) as T | null,
|
|
source: "shared",
|
|
}
|
|
}
|
|
|
|
const loadPromise = (async () => {
|
|
const loadedData = await loader()
|
|
|
|
if (loadedData) {
|
|
setCachedSession(redisKey, loadedData)
|
|
} else {
|
|
deleteCachedSession(redisKey)
|
|
}
|
|
|
|
return loadedData
|
|
})()
|
|
|
|
inflightSessionLoads.set(redisKey, loadPromise)
|
|
|
|
try {
|
|
return {
|
|
data: await loadPromise,
|
|
source: "redis",
|
|
}
|
|
} finally {
|
|
if (inflightSessionLoads.get(redisKey) === loadPromise) {
|
|
inflightSessionLoads.delete(redisKey)
|
|
}
|
|
}
|
|
}
|