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                          65x 65x 65x 65x 2x 2184x 2184x   5949x 63283x 31580x 31580x 31580x 25821x 25821x 25821x   11708x 5949x 2947x 7x     16x 3002x 3002x   13x 10x   2184x 2184x 368x     368x 16x 10x 2194x 368x 5637x 6x 368x   10x 2184x     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
}