75 lines
2.2 KiB
TypeScript
75 lines
2.2 KiB
TypeScript
import { weekMonday } from '~/utils/toronto'
|
|
|
|
const TTL_MS = 24 * 60 * 60 * 1000
|
|
|
|
interface CacheEntry<T> {
|
|
data: T
|
|
ts: number
|
|
}
|
|
|
|
function storageKey(key: string) {
|
|
return `cache:${key}`
|
|
}
|
|
|
|
export function useAppCache() {
|
|
function set<T>(key: string, data: T): void {
|
|
try {
|
|
localStorage.setItem(storageKey(key), JSON.stringify({ data, ts: Date.now() } satisfies CacheEntry<T>))
|
|
} catch { /* quota exceeded or unavailable */ }
|
|
}
|
|
|
|
/** Returns data only if fresh (< 24 h). Returns null if stale or absent. */
|
|
function get<T>(key: string): T | null {
|
|
try {
|
|
const raw = localStorage.getItem(storageKey(key))
|
|
if (!raw) return null
|
|
const entry = JSON.parse(raw) as CacheEntry<T>
|
|
if (Date.now() - entry.ts > TTL_MS) return null
|
|
return entry.data
|
|
} catch { return null }
|
|
}
|
|
|
|
/** Returns data regardless of age — for offline fallback when cache is stale. */
|
|
function peek<T>(key: string): T | null {
|
|
try {
|
|
const raw = localStorage.getItem(storageKey(key))
|
|
if (!raw) return null
|
|
return (JSON.parse(raw) as CacheEntry<T>).data
|
|
} catch { return null }
|
|
}
|
|
|
|
/** Returns age in ms, or null if absent. */
|
|
function age(key: string): number | null {
|
|
try {
|
|
const raw = localStorage.getItem(storageKey(key))
|
|
if (!raw) return null
|
|
return Date.now() - (JSON.parse(raw) as CacheEntry<unknown>).ts
|
|
} catch { return null }
|
|
}
|
|
|
|
function invalidate(key: string): void {
|
|
localStorage.removeItem(storageKey(key))
|
|
}
|
|
|
|
/** Removes all cache entries whose key starts with prefix. */
|
|
function invalidatePrefix(prefix: string): void {
|
|
const full = storageKey(prefix)
|
|
const toRemove: string[] = []
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const k = localStorage.key(i)
|
|
if (k?.startsWith(full)) toRemove.push(k)
|
|
}
|
|
toRemove.forEach(k => localStorage.removeItem(k))
|
|
}
|
|
|
|
/**
|
|
* Cache key for schedule data (intervals or slots) for the ISO week containing
|
|
* the given UTC ISO timestamp. Desktop and mobile both key to week-Monday.
|
|
*/
|
|
function weekKey(utcIso: string): string {
|
|
return weekMonday(utcIso.slice(0, 10))
|
|
}
|
|
|
|
return { set, get, peek, age, invalidate, invalidatePrefix, weekKey }
|
|
}
|