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                          51x 51x 51x 51x 2x 1476x 1476x   3268x 29688x 15054x 15054x 15054x 11936x 11936x 11936x   6386x 3268x 1883x 7x     16x 1385x 1385x   13x 10x   1476x 1476x 247x     247x 16x 10x 1486x 247x 3042x 6x 247x   10x 1476x     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
}