Cache

CacheService

Servizio singleton di cache con TTL, background refresh, request deduplication e auto cleanup. Punto di ingresso primario al caching HTTP del package.

Cosa fa

CacheService e il singleton che orchestra il caching delle richieste HTTP. Funzionalita principali:

  • Background Refresh — quando una entry si avvicina alla scadenza (raggiunto threshold), avvia un refresh asincrono in background senza bloccare il chiamante
  • Request Deduplication — richieste concorrenti per la stessa chiave condividono la stessa Promise
  • Auto Cleanup — pulizia periodica delle entry scadute (ogni 60s se la strategy supporta TTL)
  • TTL Support — Time-to-live per entry, con verifica di "troppo vecchio" via maxAge globale

Delega la persistenza effettiva a una ICacheStrategy iniettata.

Singleton pattern

class CacheService implements ICacheService {
  static getInstance(
    httpClient?: IHttpClient,
    cacheStrategy?: ICacheStrategy,
    logger?: ILogger
  ): CacheService

  static resetInstance(): void
}
  • Prima chiamata: httpClient e obbligatorio, throw altrimenti
  • Chiamate successive: ritorna l'istanza esistente, ignorando i parametri
  • resetInstance() — distrugge l'istanza corrente, utile per testing
const cache = CacheService.getInstance(httpClient, strategy)
// ... altrove:
const sameInstance = CacheService.getInstance() // stessa istanza

Configurazione init()

cache.init(backgroundRefreshConfig?: Partial<BackgroundRefreshConfig>): void
CampoTipoDefaultDescrizione
enabledbooleantrueAbilita il background refresh
thresholdnumber75Percentuale di TTL utilizzato oltre cui scatta il refresh (75 = al 75%)
maxAgenumber3_600_000Eta massima assoluta in ms (1 ora). Oltre, cache miss forzato
cache.init({
  enabled: true,
  threshold: 80,     // refresh quando resta il 20% del TTL
  maxAge: 7_200_000, // max 2 ore
})

Chiama init() per avviare anche la pulizia automatica (interval 60s se la strategy supporta TTL).

API principale

fetchWithCache(config, cacheKey?, ttl?, forceRefresh?, skipSaveCache?)

fetchWithCache<T>(
  config: AxiosRequestConfig,
  cacheKey?: string,
  ttl?: number,
  forceRefresh?: boolean,
  skipSaveCache?: boolean
): Promise<AxiosResponse<T>>

Il metodo principale. Flusso:

  1. Compone cacheKey di default come ${method}_${url} se non passata
  2. Se una richiesta con la stessa key e gia pending, ritorna quella (dedup)
  3. Se !forceRefresh: legge da cache. Se trovata e non "troppo vecchia" (maxAge), ritorna subito e — se threshold raggiunto — avvia background refresh
  4. Altrimenti esegue la richiesta HTTP, salva in cache (a meno di skipSaveCache) e ritorna
const users = await cache.fetchWithCache<User[]>(
  { url: '/users', method: 'GET' },
  'users-list',
  300_000 // 5 minuti
)

// Forza refresh
const fresh = await cache.fetchWithCache<User[]>(
  { url: '/users', method: 'GET' },
  'users-list',
  300_000,
  true // forceRefresh
)

getCache(key, config)

Lettura diretta dalla cache, senza fallback al network. Ritorna null se assente o scaduta. Avvia background refresh se threshold raggiunto.

const cached = await cache.getCache<User[]>('users-list', { url: '/users' })
if (cached) console.log('hit', cached.data)

setCache(key, response, ttl?)

Salva manualmente una AxiosResponse in cache (es. risposta arrivata da un altro flusso).

clearCache(keyPrefix)

Rimuove dalla cache tutte le entry con chiave che inizia con keyPrefix.

cache.clearCache('users-') // invalida tutte le entry users-*

invalidateAll()

Svuota completamente la cache. Chiamata internamente da AuthService.logout().

prefetch(config, cacheKey?, ttl?)

Forza un fetch fresco e popola la cache. Utile dopo il login per dati frequentemente acceduti.

await cache.prefetch<User>({ url: '/me', method: 'GET' }, 'current-user', 600_000)

cleanExpiredItems()

Rimuove subito tutte le entry scadute (oltre alla pulizia automatica ogni 60s).

setAuthHandler(handler)

Propaga un IAuthHandler al client HTTP interno per retry automatico su 401.

destroy()

Ferma l'interval di cleanup e svuota i pending requests. Chiamare prima di resetInstance() o all'unmount dell'app.

Background refresh — meccanica

Una entry e considerata "da refreshare" se:

  1. backgroundRefreshConfig.enabled === true
  2. La entry ha expiry
  3. (eta / TTL) * 100 >= threshold
  4. eta < maxAge

Il refresh viene eseguito in background con header X-Background-Refresh: true aggiunto alla request — utile per il backend per loggarlo/escluderlo dalle metriche.

Se due chiamate concorrenti triggerano il refresh, la seconda viene dedup automaticamente: la Promise del refresh in corso e tracciata in pendingRequests.

Esempio: integrazione completa

import {
  CacheService,
  CacheStrategyFactory,
  HttpClientFactory,
  AuthStrategyFactory,
  AuthServiceFactory,
} from '@pzeta/postgrest'

// 1. HTTP client + auth
const httpClient = HttpClientFactory.createFetchClient()
const strategy = AuthStrategyFactory.createJwtStrategy()
const auth = AuthServiceFactory.create(httpClient, strategy)
httpClient.setAuthStrategy(strategy)
httpClient.setAuthHandler(auth)

// 2. Cache singleton
const cache = CacheService.getInstance(
  httpClient,
  CacheStrategyFactory.createWithFallback()
)
cache.init({ threshold: 75, maxAge: 3_600_000 })
cache.setAuthHandler(auth)

// 3. Uso
const orders = await cache.fetchWithCache<Order[]>(
  { url: '/orders', method: 'GET' },
  'orders-list',
  60_000
)

Cleanup all'unmount

window.addEventListener('beforeunload', () => {
  CacheService.getInstance().destroy()
  CacheService.resetInstance()
})

Vedi anche