feat: add caching for backend objects
This commit is contained in:
74
app/composables/useAppCache.ts
Normal file
74
app/composables/useAppCache.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
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 }
|
||||
}
|
||||
26
app/composables/useBookingDraft.ts
Normal file
26
app/composables/useBookingDraft.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { Database } from '~/types/supabase'
|
||||
|
||||
type Boat = Database['public']['Tables']['boats']['Row']
|
||||
|
||||
export interface BookingDraft {
|
||||
boat: Boat
|
||||
startTime: string
|
||||
endTime: string
|
||||
}
|
||||
|
||||
const _draft = ref<BookingDraft | null>(null)
|
||||
|
||||
export function useBookingDraft() {
|
||||
function set(boat: Boat, startTime: string, endTime: string): void {
|
||||
_draft.value = { boat, startTime, endTime }
|
||||
}
|
||||
|
||||
/** Reads and clears the draft — call once in the destination page. */
|
||||
function take(): BookingDraft | null {
|
||||
const val = _draft.value
|
||||
_draft.value = null
|
||||
return val
|
||||
}
|
||||
|
||||
return { set, take }
|
||||
}
|
||||
10
app/composables/useOfflineStatus.ts
Normal file
10
app/composables/useOfflineStatus.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
const _isOnline = ref(typeof navigator !== 'undefined' ? navigator.onLine : true)
|
||||
|
||||
if (import.meta.client) {
|
||||
window.addEventListener('online', () => { _isOnline.value = true })
|
||||
window.addEventListener('offline', () => { _isOnline.value = false })
|
||||
}
|
||||
|
||||
export function useOfflineStatus() {
|
||||
return { isOnline: readonly(_isOnline) }
|
||||
}
|
||||
Reference in New Issue
Block a user