From ccef3cf7bb7162b5a4f4b86376637836b796caa7 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sun, 24 May 2026 20:33:46 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20extract=20magic=20constants=20?= =?UTF-8?q?=E2=80=94=20wave=202=20(TanStack=20Query=20defaults)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promote the duplicated query lifecycle constants in \$shared/api/queryClient.ts: - staleTime (5 minutes) -> DEFAULT_QUERY_STALE_TIME_MS - gcTime (10 minutes) -> DEFAULT_QUERY_GC_TIME_MS - retry (3) -> QUERY_RETRY_COUNT - retryDelay (1s base, 30s cap) -> QUERY_RETRY_BASE_DELAY_MS + QUERY_RETRY_MAX_DELAY_MS fontCatalogStore and availableFilterStore now import the stale/gc constants instead of re-deriving '5 * 60 * 1000' / '10 * 60 * 1000'. fontCatalogStore.svelte.spec.ts's queryClient mock now passes through the new named exports via importOriginal so the consumer's imports resolve. --- .../fontCatalogStore.svelte.spec.ts | 14 ++++-- .../fontCatalogStore.svelte.ts | 10 ++-- .../availableFilterStore.svelte.ts | 10 ++-- src/shared/api/queryClient.ts | 47 +++++++++++++------ 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.spec.ts b/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.spec.ts index 5cdae3a..f287091 100644 --- a/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.spec.ts +++ b/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.spec.ts @@ -19,11 +19,15 @@ import { import type { UnifiedFont } from '../../types'; import { FontCatalogStore } from './fontCatalogStore.svelte'; -vi.mock('$shared/api/queryClient', () => ({ - queryClient: new QueryClient({ - defaultOptions: { queries: { retry: 0, gcTime: 0 } }, - }), -})); +vi.mock('$shared/api/queryClient', async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + queryClient: new QueryClient({ + defaultOptions: { queries: { retry: 0, gcTime: 0 } }, + }), + }; +}); vi.mock('../../../api', () => ({ fetchProxyFonts: vi.fn() })); import { queryClient } from '$shared/api/queryClient'; diff --git a/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.ts b/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.ts index 165a5ab..aea0ed0 100644 --- a/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.ts +++ b/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.ts @@ -1,4 +1,8 @@ -import { queryClient } from '$shared/api/queryClient'; +import { + DEFAULT_QUERY_GC_TIME_MS, + DEFAULT_QUERY_STALE_TIME_MS, + queryClient, +} from '$shared/api/queryClient'; import { type InfiniteData, InfiniteQueryObserver, @@ -427,8 +431,8 @@ export class FontCatalogStore { const next = lastPage.offset + lastPage.limit; return next < lastPage.total ? { offset: next } : undefined; }, - staleTime: hasFilters ? 0 : 5 * 60 * 1000, - gcTime: 10 * 60 * 1000, + staleTime: hasFilters ? 0 : DEFAULT_QUERY_STALE_TIME_MS, + gcTime: DEFAULT_QUERY_GC_TIME_MS, }; } diff --git a/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.svelte.ts b/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.svelte.ts index 6aa3d6f..7eff164 100644 --- a/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.svelte.ts +++ b/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.svelte.ts @@ -17,7 +17,11 @@ import { fetchProxyFilters } from '$features/FilterAndSortFonts/api/filters/filters'; import type { FilterMetadata } from '$features/FilterAndSortFonts/api/filters/filters'; -import { queryClient } from '$shared/api/queryClient'; +import { + DEFAULT_QUERY_GC_TIME_MS, + DEFAULT_QUERY_STALE_TIME_MS, + queryClient, +} from '$shared/api/queryClient'; import { type QueryKey, QueryObserver, @@ -81,8 +85,8 @@ export class AvailableFilterStore { return { queryKey: this.getQueryKey(), queryFn: () => this.fetchFn(), - staleTime: 5 * 60 * 1000, // 5 minutes - gcTime: 10 * 60 * 1000, // 10 minutes + staleTime: DEFAULT_QUERY_STALE_TIME_MS, + gcTime: DEFAULT_QUERY_GC_TIME_MS, }; } diff --git a/src/shared/api/queryClient.ts b/src/shared/api/queryClient.ts index cf16c92..7f9ddba 100644 --- a/src/shared/api/queryClient.ts +++ b/src/shared/api/queryClient.ts @@ -1,5 +1,31 @@ import { QueryClient } from '@tanstack/query-core'; +/** + * Data remains fresh for this long after fetch. Stores that override + * staleness (e.g. filtered queries) can use 0 to bypass. + */ +export const DEFAULT_QUERY_STALE_TIME_MS = 5 * 60 * 1000; + +/** + * Unused cache entries are garbage collected after this long. + */ +export const DEFAULT_QUERY_GC_TIME_MS = 10 * 60 * 1000; + +/** + * How many times a failed query is retried before surfacing the error. + */ +export const QUERY_RETRY_COUNT = 3; + +/** + * Base delay for exponential retry backoff. + */ +export const QUERY_RETRY_BASE_DELAY_MS = 1000; + +/** + * Upper bound on retry delay regardless of attempt index. + */ +export const QUERY_RETRY_MAX_DELAY_MS = 30000; + /** * TanStack Query client instance * @@ -15,14 +41,8 @@ import { QueryClient } from '@tanstack/query-core'; export const queryClient = new QueryClient({ defaultOptions: { queries: { - /** - * Data remains fresh for 5 minutes after fetch - */ - staleTime: 5 * 60 * 1000, - /** - * Unused cache entries are removed after 10 minutes - */ - gcTime: 10 * 60 * 1000, + staleTime: DEFAULT_QUERY_STALE_TIME_MS, + gcTime: DEFAULT_QUERY_GC_TIME_MS, /** * Don't refetch when window regains focus */ @@ -31,15 +51,12 @@ export const queryClient = new QueryClient({ * Refetch on mount if data is stale */ refetchOnMount: true, + retry: QUERY_RETRY_COUNT, /** - * Retry failed requests up to 3 times + * Exponential backoff: 1s, 2s, 4s, 8s... capped at 30s */ - retry: 3, - /** - * Exponential backoff for retries - * 1s, 2s, 4s, 8s... capped at 30s - */ - retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000), + retryDelay: attemptIndex => + Math.min(QUERY_RETRY_BASE_DELAY_MS * 2 ** attemptIndex, QUERY_RETRY_MAX_DELAY_MS), }, }, });