import { type QueryClient, useQueryClient, type UseQueryOptions, } from "@tanstack/react-query"; import { useEffect } from "react"; import type { components, paths } from "./generated/schema"; import { useAuthenticatedQuery } from "./authenticated-query"; import { apiErrorCode, isBrokerageAccessLoss } from "./problem-details"; type InvestmentResourceMeta = components["schemas"]["InvestmentResourceMetaDto"]; type Resource = { [key: string]: unknown; data: unknown; meta: InvestmentResourceMeta; sync?: unknown; }; type ResourcePath = { [P in keyof paths]: paths[P]["get"] extends { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- HTTP success response in the generated contract. responses: { 200: { content: { "application/json": Resource } } }; } ? P : never; }[keyof paths]; type ResourceQueryKey = readonly [ "get", ResourcePath, { params?: { path?: object; query?: object } }?, ]; function invalidateResourceAccess( client: QueryClient, group: string, error: unknown, ) { if (!isBrokerageAccessLoss(apiErrorCode(error))) return; const affected = { predicate: (cached: { meta?: Record }) => cached.meta?.accessGroup === group, }; void client.cancelQueries(affected); for (const cached of client.getQueryCache().findAll(affected)) cached.setState({ data: undefined, dataUpdatedAt: 0, error: error as Error, fetchStatus: "idle", status: "error", }); } function useResourceQuery< T extends Resource, E = unknown, K extends readonly unknown[] = ResourceQueryKey, >( options: UseQueryOptions, ) { const client = useQueryClient(); const group = typeof options.meta?.accessGroup === "string" ? options.meta.accessGroup : undefined; const query = useAuthenticatedQuery(options); useEffect(() => { if (group) invalidateResourceAccess(client, group, query.error); }, [client, group, query.error]); const data: T["data"] | undefined = query.data?.data; const meta: T["meta"] | undefined = query.data?.meta; // Project resource fields; refetch still returns the transport envelope. // queryKey is kept for screen-sync cache bookkeeping. return { // eslint-disable-next-line @tanstack/query/no-rest-destructuring -- Single projection point; consumers already depend on the whole query state. ...query, data: data ?? undefined, meta, queryKey: options.queryKey, sync: query.data && "sync" in query.data ? query.data.sync : undefined, }; } export { type InvestmentResourceMeta, useResourceQuery, };