All files / layers / L1 useDataAge.ts

88.57% Statements 62/70
70% Branches 14/20
100% Functions 20/20
95.91% Lines 47/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82                          57x 57x 57x 57x 2x 1840x 1840x   4532x 43743x 22476x 22476x 22476x 18104x 18104x 18104x   8904x 4532x 2259x 7x     16x 2273x 2273x   13x 10x   1840x 1840x 301x     301x 16x 10x 1850x 301x 4270x 6x 301x   10x 1840x     7x   7x 5x 1x   5x       7x 5x 10x   5x       7x    
/**
 * @see frontend/docs/UX/layer-contracts/L1-data-layer.md §3.7
 *
 * Data-age -tarkistus non-SSE-näkymille. Palauttaa datan iän
 * millisekunteina ja binäärin stale-tilan.
 *
 * Ikäkynnys = gcTime - retention_margin = 30 min - 5 min = 25 min
 *
 * Ei renderöi mitään — puhdas hook.
 */
 
import { useState, useEffect } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import type { EntityPath } from './contracts'
import type { QueryParams } from '../interfaces'
import { normalizeFilters } from './mutationQueue'
 
const GC_TIME_MS = 30 * 60 * 1000
const RETENTION_MARGIN_MS = 5 * 60 * 1000
const AGE_THRESHOLD_MS = GC_TIME_MS - RETENTION_MARGIN_MS
 
const TICK_INTERVAL_MS = 60_000
 
expIort interface DataAgeResult {
  ageMs: number
  isStale: boolean
}
 
export function useDataAge(
  entityPath: EntityPath,
  normalizedParams?: QueryParams,
): DataAgeResult {
  const queryClient = useQueryClient()
  const normParams = normalizedParams ? normalizeFilters(normalizedParams) : null
 
  function compute(): DataAgeResult {
    const queries = queryClient
      .getQueryCache()
      .getAll()
      .filter((q) => {
        if (q.queryKey[0] !== entityPath) return false
        Eif (!normParams) return true
        const keyParams = q.queryKey[1] as QueryParams | undefined
        if (!keyParams) return true
        const normJson = JSON.stringify(normParams)
        const keyJson = JSON.stringify(keyParams)
        return normJson === keyJson
      })
 
    const timestamps = queries
      .map((q) => q.state.dataUpdatedAt)
      .filter((t): t is number => t > 0)
 
    if (timestamps.length === 0) {
      return { ageMs: Infinity, isStale: true }
    }
 
    const ageMs = Date.now() - Math.max(...timestamps)
    return { ageMs, isStale: ageMs > AGE_THRESHOLD_MS }
  }
 
  const [result, setResult] = useState<DataAgeResult>(compute)
 
  useEffect(() => {
    const id = setInterval(() => {
      setResult(compute())
    }, TICK_INTERVAL_MS)
    return () => clearInterval(id)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [entityPath, normalizedParams])
 
  useEffect(() => {
    const unsubscribe = queryClient.getQueryCache().subscribe(() => {
      setResult(compute())
    })
    return unsubscribe
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [entityPath, normalizedParams])
 
  return result
}