Files
HealthLog/src/lib/queries/prefetch-medications.ts
T
Marc-André Bombeck b346e78cc7 perf(medications): one compliance round trip and a memoised formatter hot path
Intl.DateTimeFormat instances are memoised per locale, timezone and
options signature — construction dominated the 365-day ledger build and
the hot path drops about fivefold. A batched GET /api/medications/compliance
serves every card from one request through the same per-medication cache
cells the detail page reads, replacing the per-card fan-out; the list
response stays untouched so the due line never waits on a ledger build.
The medications and compliance buckets serve stale-while-revalidate,
interactive writes evict while background bulk sync marks stale, the
per-id route defers ownership and schedule reads until a cache miss,
and the card body reserves constant slots for the status and cycle
lines so late data stops shifting the layout.
2026-06-11 21:17:17 +02:00

60 lines
2.6 KiB
TypeScript

import type { QueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/query-keys";
import { apiGet } from "@/lib/api/api-fetch";
/**
* v1.16.7 — intent-based data prefetch for the medications page.
*
* The nav links already prefetch the route's JS chunk (next/link
* default in production); what still serialised the first visit was
* the data hop — the list query only fired once the page chunk had
* mounted. Wiring this to the nav link's hover / touch / focus intent
* puts `/api/medications` (the response carries the per-medication
* `nextDueAt` due times) in flight while the navigation commits.
*
* v1.16.8 — the batched card-compliance read
* (`/api/medications/compliance`) prefetches alongside the list, so the
* compliance bars + the status line resolve in the same paint as the
* cards instead of swapping in late. Two independent requests on
* purpose: the list response must stay fast (the due line never waits
* on a compliance ledger build), and the compliance prefetch rides the
* same server-side cache cells the page query would warm anyway.
*
* The 15 s prefetch window only bounds how old a cache entry may be
* before an intent re-fires the request — the mounted page query rides
* the provider-default `staleTime` (5 min) and consumes whatever this
* prefetch parked. The server caches both reads per user (60 s list /
* 15 min compliance, each with an SWR window), so even a missed window
* stays cheap.
*/
export const MEDICATIONS_LIST_STALE_TIME_MS = 15_000;
export function prefetchMedicationsList(queryClient: QueryClient): void {
void queryClient.prefetchQuery({
queryKey: queryKeys.medications(),
queryFn: () => apiGet("/api/medications"),
staleTime: MEDICATIONS_LIST_STALE_TIME_MS,
});
void queryClient.prefetchQuery({
queryKey: queryKeys.medicationComplianceSummary(),
queryFn: () => apiGet("/api/medications/compliance"),
staleTime: MEDICATIONS_LIST_STALE_TIME_MS,
});
}
/**
* Intent props for a nav link pointing at `/medications` — fires the
* list prefetch on hover / touch / keyboard focus, i.e. before the
* router even commits. `prefetchQuery` dedupes internally (in-flight
* promise reuse + the stale window above), so the three handlers and a
* subsequent route-commit prefetch collapse into at most one request.
*/
export function medicationsPrefetchIntentProps(queryClient: QueryClient): {
onPointerEnter: () => void;
onTouchStart: () => void;
onFocus: () => void;
} {
const fire = () => prefetchMedicationsList(queryClient);
return { onPointerEnter: fire, onTouchStart: fire, onFocus: fire };
}