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                          47x 47x 47x 47x 2x 1332x 1332x   3064x 27953x 14474x 14474x 14474x 11544x 11544x 11544x   5994x 3064x 1767x 7x     16x 1297x 1297x   13x 10x   1332x 1332x 221x     221x 16x 10x 1342x 221x 2860x 6x 221x   10x 1332x     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
}