Files
labelmain-demo/components/login/libs/session-cache.ts
2026-04-23 14:47:14 +08:00

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)
}
}
}