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                          73x 73x 73x 73x 2x 2192x 2192x   6015x 63857x 31846x 31846x 31846x 26027x 26027x 26027x   11834x 6015x 2999x 7x     16x 3016x 3016x   13x 10x   2192x 2192x 374x     374x 16x 10x 2202x 374x 5697x 6x 374x   10x 2192x     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
}