File
Blob: src/client/lib/query.ts
| 1 | import { QueryClient } from "@tanstack/react-query"; |
| 2 | import { useEffect, useRef } from "react"; |
| 3 | |
| 4 | import { apiRequest } from "@/client/lib/http"; |
| 5 | |
| 6 | export const queryClient = new QueryClient({ |
| 7 | defaultOptions: { |
| 8 | queries: { |
| 9 | // tessera is an IdP/admin surface, so server state should be fresh by |
| 10 | // explicit invalidation and route entry, not ambient browser events. |
| 11 | gcTime: 0, |
| 12 | refetchOnReconnect: false, |
| 13 | refetchOnWindowFocus: false, |
| 14 | retry: false, |
| 15 | }, |
| 16 | }, |
| 17 | }); |
| 18 | |
| 19 | export const queryKeys = { |
| 20 | accountApps: (userId: string | null) => ["account", userId, "apps"] as const, |
| 21 | accountIdentities: (userId: string | null) => ["account", userId, "identities"] as const, |
| 22 | accountSessions: (userId: string | null) => ["account", userId, "sessions"] as const, |
| 23 | adminClients: (userId: string | null) => ["admin", userId, "clients"] as const, |
| 24 | adminInvites: (userId: string | null) => ["admin", userId, "invites"] as const, |
| 25 | adminLauncherApps: (userId: string | null) => ["admin", userId, "launcher-apps"] as const, |
| 26 | adminUsers: (userId: string | null) => ["admin", userId, "users"] as const, |
| 27 | consentClient: (userId: string | null, clientId: string | null) => ["consent-client", userId, clientId] as const, |
| 28 | inviteLookup: (token: string | null) => ["invite", token] as const, |
| 29 | launcherApps: () => ["launcher", "apps"] as const, |
| 30 | preloginClient: (clientId: string | null, signedQuery: string | null) => |
| 31 | ["prelogin-client", clientId, signedQuery] as const, |
| 32 | }; |
| 33 | |
| 34 | export const fetchJson = async <T>(url: string): Promise<T> => { |
| 35 | const res = await apiRequest(url); |
| 36 | if (!res.ok) throw new Error(`HTTP ${res.status}`); |
| 37 | return (await res.json()) as T; |
| 38 | }; |
| 39 | |
| 40 | export const useSessionQueryCacheBoundary = (userId: string | null, isPending: boolean): void => { |
| 41 | const previousResolvedUserId = useRef<string | null | undefined>(undefined); |
| 42 | |
| 43 | useEffect(() => { |
| 44 | if (isPending) return; |
| 45 | |
| 46 | if (previousResolvedUserId.current === undefined) { |
| 47 | previousResolvedUserId.current = userId; |
| 48 | return; |
| 49 | } |
| 50 | |
| 51 | if (previousResolvedUserId.current !== userId) { |
| 52 | queryClient.clear(); |
| 53 | previousResolvedUserId.current = userId; |
| 54 | } |
| 55 | }, [isPending, userId]); |
| 56 | }; |