+12








fcd7c979df
Co-authored-by: Artur <145776561+arturict@users.noreply.github.com> Co-authored-by: arturict <arturict@users.noreply.github.com> Co-authored-by: Thomas <thomas@ajnart.fr> Co-authored-by: Afonso Jorge Ramos <afonsojorgeramos@gmail.com> Co-authored-by: Danura30082 <danura30082+github@gmail.com> Co-authored-by: Kushie <122986913+kushiemoon-dev@users.noreply.github.com> Co-authored-by: ajnart <thomas@ajnart.fr> Co-authored-by: ajnart <thomas@ajnart.dev> Co-authored-by: chenjun <chenjun@foxmail.com> Co-authored-by: Wikibear <wikibear@users.noreply.github.com> Co-authored-by: Bergasha <bergasha@gmail.com> Co-authored-by: leafyelin <43306196+leafyelin@users.noreply.github.com> Co-authored-by: Lucas Araujo <lucas.lra@gmail.com> Co-authored-by: GreenMonito <86085570+GreenMonito@users.noreply.github.com> Co-authored-by: Nerdy-Technician <roffo@nerdy-technician.social> Co-authored-by: Jun <34545896+jcstrong@users.noreply.github.com> Co-authored-by: Edmond Abraham <54119952+gitedmond@users.noreply.github.com> Co-authored-by: thrawnx <tight.bulb9384@fastmail.com> Co-authored-by: Chris <58918656+nordada@users.noreply.github.com> Co-authored-by: yi111 <153097222+Yi-111-a@users.noreply.github.com> Co-authored-by: Yanis <naamaneyanis@gmail.com> Co-authored-by: Wikibear <me@sej.de> Co-authored-by: alessiottavo <93441886+alessiottavo@users.noreply.github.com>
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
import type { QueryKey } from "@tanstack/react-query";
|
|
|
|
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;
|
|
|
|
interface ForbiddenTrpcError {
|
|
data: { code: "FORBIDDEN" };
|
|
}
|
|
|
|
export const isTrpcForbiddenError = (error: unknown): error is ForbiddenTrpcError => {
|
|
if (!isRecord(error) || !isRecord(error.data)) return false;
|
|
return error.data.code === "FORBIDDEN";
|
|
};
|
|
|
|
// RSC-prefetched data must survive the hydration pass without an immediate
|
|
// duplicate fetch. Widget-specific intervals still control live refreshes.
|
|
export const queryCacheDefaultStaleTimeMs = 30_000;
|
|
export const queryCacheDefaultRefetchIntervalMs = 30_000;
|
|
export const queryCacheDefaultGcTimeMs = 1000 * 60 * 5;
|
|
export const queryCacheMetadataStaleTimeMs = 1000 * 60 * 5;
|
|
|
|
const widgetDataQueryPaths = new Set(["app.byId", "app.byIds", "docker.getContainers", "integration.byIds"]);
|
|
// Beszel system stats come from a live subscription rather than ordinary query recovery.
|
|
const excludedWidgetPaths = new Set(["widget.app.ping", "widget.beszel.getSystemStats"]);
|
|
|
|
export const isWidgetDataTrpcPath = (path: string) => {
|
|
if (path.startsWith("widget.")) return !excludedWidgetPaths.has(path);
|
|
return widgetDataQueryPaths.has(path);
|
|
};
|
|
|
|
// These supporting queries can be numerous on a dashboard. Give them a
|
|
// predictable refresh policy instead of refetching all of them after every tab
|
|
// focus/reconnect event. Metadata can refresh after a bounded stale window;
|
|
// Docker state remains live on a single shared timer.
|
|
export const dashboardSupportingQueryPolicies = [
|
|
{
|
|
queryKey: [["app", "byId"]],
|
|
refetchInterval: false,
|
|
staleTime: queryCacheMetadataStaleTimeMs,
|
|
refetchOnWindowFocus: true,
|
|
refetchOnReconnect: true,
|
|
},
|
|
{
|
|
queryKey: [["app", "byIds"]],
|
|
refetchInterval: false,
|
|
staleTime: queryCacheMetadataStaleTimeMs,
|
|
refetchOnWindowFocus: true,
|
|
refetchOnReconnect: true,
|
|
},
|
|
{
|
|
queryKey: [["integration", "byIds"]],
|
|
refetchInterval: false,
|
|
staleTime: queryCacheMetadataStaleTimeMs,
|
|
refetchOnWindowFocus: true,
|
|
refetchOnReconnect: true,
|
|
},
|
|
{
|
|
queryKey: [["docker", "getContainers"]],
|
|
refetchInterval: queryCacheDefaultRefetchIntervalMs,
|
|
staleTime: queryCacheDefaultStaleTimeMs,
|
|
refetchOnWindowFocus: false,
|
|
refetchOnReconnect: false,
|
|
},
|
|
] as const;
|
|
|
|
const getTrpcPathFromQueryKey = (queryKey: QueryKey) => {
|
|
const first = queryKey[0];
|
|
if (!Array.isArray(first)) return null;
|
|
|
|
const parts = first.filter((part): part is string => typeof part === "string");
|
|
return parts.length > 0 ? parts : null;
|
|
};
|
|
|
|
export const isWidgetDataQueryKey = (queryKey: QueryKey) => {
|
|
const path = getTrpcPathFromQueryKey(queryKey);
|
|
if (!path) return false;
|
|
|
|
return isWidgetDataTrpcPath(path.join("."));
|
|
};
|