diff --git a/src/app/providers/QueryProvider.svelte b/src/app/providers/QueryProvider.svelte
index 8806e54..eeb42b7 100644
--- a/src/app/providers/QueryProvider.svelte
+++ b/src/app/providers/QueryProvider.svelte
@@ -6,7 +6,7 @@
descendants of this provider.
-->
diff --git a/src/entities/Font/api/proxy/proxyFonts.test.ts b/src/entities/Font/api/proxy/proxyFonts.test.ts
index 10bbaa8..a6ccc67 100644
--- a/src/entities/Font/api/proxy/proxyFonts.test.ts
+++ b/src/entities/Font/api/proxy/proxyFonts.test.ts
@@ -19,7 +19,9 @@ vi.mock('$shared/api/api', () => ({
}));
import { api } from '$shared/api/api';
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
+
+const queryClient = getQueryClient();
import { fontKeys } from '$shared/api/queryKeys';
import { FontResponseError } from '../../lib/errors/errors';
import {
diff --git a/src/entities/Font/api/proxy/proxyFonts.ts b/src/entities/Font/api/proxy/proxyFonts.ts
index 5b953de..515c4c8 100644
--- a/src/entities/Font/api/proxy/proxyFonts.ts
+++ b/src/entities/Font/api/proxy/proxyFonts.ts
@@ -11,7 +11,7 @@
*/
import { api } from '$shared/api/api';
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
import { fontKeys } from '$shared/api/queryKeys';
import { buildQueryString } from '$shared/lib/utils';
import type { QueryParams } from '$shared/lib/utils';
@@ -26,7 +26,7 @@ import type { UnifiedFont } from '../../model/types';
*/
export function seedFontCache(fonts: UnifiedFont[]): void {
fonts.forEach(font => {
- queryClient.setQueryData(fontKeys.detail(font.id), font);
+ getQueryClient().setQueryData(fontKeys.detail(font.id), font);
});
}
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 230512e..278e8e0 100644
--- a/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.spec.ts
+++ b/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.spec.ts
@@ -27,18 +27,21 @@ vi.mock('$shared/api/queryClient', async importOriginal => {
*/
const { QueryClient } = await import('@tanstack/query-core');
const actual = await importOriginal();
+ const mockClient = new QueryClient({
+ defaultOptions: { queries: { retry: 0, gcTime: 0 } },
+ });
return {
...actual,
- queryClient: new QueryClient({
- defaultOptions: { queries: { retry: 0, gcTime: 0 } },
- }),
+ getQueryClient: () => mockClient,
};
});
vi.mock('../../../api', () => ({ fetchProxyFonts: vi.fn() }));
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
import { fetchProxyFonts } from '../../../api';
+const queryClient = getQueryClient();
+
const fetch = fetchProxyFonts as ReturnType;
type FontPage = { fonts: UnifiedFont[]; total: number; limit: number; offset: number };
diff --git a/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.ts b/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.ts
index 72b3d56..ceff630 100644
--- a/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.ts
+++ b/src/entities/Font/model/store/fontCatalogStore/fontCatalogStore.svelte.ts
@@ -1,7 +1,7 @@
import {
DEFAULT_QUERY_GC_TIME_MS,
DEFAULT_QUERY_STALE_TIME_MS,
- queryClient,
+ getQueryClient,
} from '$shared/api/queryClient';
import {
type InfiniteData,
@@ -46,7 +46,7 @@ export class FontCatalogStore {
readonly unknown[],
PageParam
>;
- #qc = queryClient;
+ #qc = getQueryClient();
#unsubscribe: () => void;
constructor(params: FontStoreParams = {}) {
diff --git a/src/entities/Font/model/store/fontsByIdsStore/fontsByIdsStore.test.ts b/src/entities/Font/model/store/fontsByIdsStore/fontsByIdsStore.test.ts
index 0acf5a9..16ce2fe 100644
--- a/src/entities/Font/model/store/fontsByIdsStore/fontsByIdsStore.test.ts
+++ b/src/entities/Font/model/store/fontsByIdsStore/fontsByIdsStore.test.ts
@@ -1,4 +1,6 @@
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
+
+const queryClient = getQueryClient();
import { fontKeys } from '$shared/api/queryKeys';
import {
beforeEach,
diff --git a/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.svelte.ts b/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.svelte.ts
index 265b153..02d2c3f 100644
--- a/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.svelte.ts
+++ b/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.svelte.ts
@@ -20,7 +20,7 @@ import type { FilterMetadata } from '$features/FilterAndSortFonts/api/filters/fi
import {
DEFAULT_QUERY_GC_TIME_MS,
DEFAULT_QUERY_STALE_TIME_MS,
- queryClient,
+ getQueryClient,
} from '$shared/api/queryClient';
import {
type QueryKey,
@@ -49,7 +49,7 @@ export class AvailableFilterStore {
/**
* Shared query client
*/
- protected qc = queryClient;
+ protected qc = getQueryClient();
/**
* Creates a new filters store
diff --git a/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.test.ts b/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.test.ts
index 8f489d4..e36fe76 100644
--- a/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.test.ts
+++ b/src/features/FilterAndSortFonts/model/store/availableFilterStore/availableFilterStore.test.ts
@@ -1,4 +1,6 @@
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
+
+const queryClient = getQueryClient();
import {
afterEach,
beforeEach,
diff --git a/src/shared/api/queryClient.ts b/src/shared/api/queryClient.ts
index 1e921e5..95ba830 100644
--- a/src/shared/api/queryClient.ts
+++ b/src/shared/api/queryClient.ts
@@ -27,11 +27,16 @@ export const QUERY_RETRY_BASE_DELAY_MS = 1000;
*/
export const QUERY_RETRY_MAX_DELAY_MS = 30000;
+let queryClientInstance: QueryClient | undefined;
+
/**
- * TanStack Query client instance
+ * Shared TanStack Query client (lazy singleton).
*
- * Configured for optimal caching and refetching behavior.
- * Used by all font stores for data fetching and caching.
+ * Construction is deferred to the first call so importing this module is inert:
+ * module eval runs no `new QueryClient()`, so the module is genuinely
+ * side-effect-free and needs no `sideEffects` allowlist exception. The
+ * app-layer `QueryProvider` is the first caller; every store reuses the same
+ * instance. Matches the lazy-accessor pattern used by the font stores.
*
* Cache behavior:
* - Data stays fresh for 5 minutes (staleTime)
@@ -39,30 +44,32 @@ export const QUERY_RETRY_MAX_DELAY_MS = 30000;
* - No refetch on window focus (reduces unnecessary network requests)
* - 3 retries with exponential backoff on failure
*/
-export const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: DEFAULT_QUERY_STALE_TIME_MS,
- gcTime: DEFAULT_QUERY_GC_TIME_MS,
- /**
- * Don't refetch when window regains focus
- */
- refetchOnWindowFocus: false,
- /**
- * Refetch on mount if data is stale
- */
- refetchOnMount: true,
- retry: (failureCount, error) => {
- if (error instanceof NonRetryableError) {
- return false;
- }
- return failureCount < QUERY_RETRY_COUNT;
+export function getQueryClient(): QueryClient {
+ return (queryClientInstance ??= new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: DEFAULT_QUERY_STALE_TIME_MS,
+ gcTime: DEFAULT_QUERY_GC_TIME_MS,
+ /**
+ * Don't refetch when window regains focus
+ */
+ refetchOnWindowFocus: false,
+ /**
+ * Refetch on mount if data is stale
+ */
+ refetchOnMount: true,
+ retry: (failureCount, error) => {
+ if (error instanceof NonRetryableError) {
+ return false;
+ }
+ return failureCount < QUERY_RETRY_COUNT;
+ },
+ /**
+ * Exponential backoff: 1s, 2s, 4s, 8s... capped at 30s
+ */
+ retryDelay: attemptIndex =>
+ Math.min(QUERY_RETRY_BASE_DELAY_MS * 2 ** attemptIndex, QUERY_RETRY_MAX_DELAY_MS),
},
- /**
- * Exponential backoff: 1s, 2s, 4s, 8s... capped at 30s
- */
- retryDelay: attemptIndex =>
- Math.min(QUERY_RETRY_BASE_DELAY_MS * 2 ** attemptIndex, QUERY_RETRY_MAX_DELAY_MS),
},
- },
-});
+ }));
+}
diff --git a/src/shared/lib/helpers/BaseQueryStore/BaseQueryStore.svelte.ts b/src/shared/lib/helpers/BaseQueryStore/BaseQueryStore.svelte.ts
index c50cb8a..ee0a3ea 100644
--- a/src/shared/lib/helpers/BaseQueryStore/BaseQueryStore.svelte.ts
+++ b/src/shared/lib/helpers/BaseQueryStore/BaseQueryStore.svelte.ts
@@ -1,4 +1,4 @@
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
import {
QueryObserver,
type QueryObserverOptions,
@@ -20,7 +20,7 @@ export abstract class BaseQueryStore {
#unsubscribe: () => void;
constructor(options: QueryObserverOptions) {
- this.#observer = new QueryObserver(queryClient, options);
+ this.#observer = new QueryObserver(getQueryClient(), options);
this.#unsubscribe = this.#observer.subscribe(result => {
this.#result = result;
});
diff --git a/src/shared/lib/helpers/BaseQueryStore/BaseQueryStore.test.ts b/src/shared/lib/helpers/BaseQueryStore/BaseQueryStore.test.ts
index 00b8ea0..31654c7 100644
--- a/src/shared/lib/helpers/BaseQueryStore/BaseQueryStore.test.ts
+++ b/src/shared/lib/helpers/BaseQueryStore/BaseQueryStore.test.ts
@@ -1,4 +1,6 @@
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
+
+const queryClient = getQueryClient();
import {
beforeEach,
describe,
diff --git a/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts b/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts
index 323e0b3..b151733 100644
--- a/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts
+++ b/src/widgets/ComparisonView/model/stores/comparisonStore.test.ts
@@ -11,7 +11,9 @@
import type { UnifiedFont } from '$entities/Font';
import { UNIFIED_FONTS } from '$entities/Font/testing';
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
+
+const queryClient = getQueryClient();
import {
beforeEach,
describe,
diff --git a/vitest.setup.component.ts b/vitest.setup.component.ts
index 32236e5..001a965 100644
--- a/vitest.setup.component.ts
+++ b/vitest.setup.component.ts
@@ -1,4 +1,4 @@
-import { queryClient } from '$shared/api/queryClient';
+import { getQueryClient } from '$shared/api/queryClient';
import * as matchers from '@testing-library/jest-dom/matchers';
import { cleanup } from '@testing-library/svelte';
import {
@@ -14,7 +14,7 @@ expect.extend(matchers);
afterEach(() => {
cleanup();
- queryClient.clear();
+ getQueryClient().clear();
});
// Mock window.matchMedia for components that use it