/** * Service for fetching Google Fonts with Svelte 5 runes + TanStack Query */ import { type CreateQueryResult, createQuery, useQueryClient, } from '@tanstack/svelte-query'; import { type GoogleFontsParams, fetchGoogleFonts, } from '../../api'; import { normalizeGoogleFonts } from '../../lib'; import type { FontCategory, FontSubset, } from '../types'; import type { UnifiedFont } from '../types/normalize'; /** * Query key factory */ function getGoogleFontsQueryKey(params: GoogleFontsParams) { return ['googleFonts', params] as const; } /** * Query function */ export async function fetchGoogleFontsQuery(params: GoogleFontsParams): Promise { try { const response = await fetchGoogleFonts({ category: params.category, subset: params.subset, sort: params.sort, }); return normalizeGoogleFonts(response.items); } catch (error) { if (error instanceof Error) { if (error.message.includes('Failed to fetch')) { throw new Error( 'Unable to connect to Google Fonts. Please check your internet connection.', ); } if (error.message.includes('404')) { throw new Error('Font not found in Google Fonts catalog.'); } } throw new Error('Failed to load fonts from Google Fonts.'); } } /** * Google Fonts store wrapping TanStack Query with runes */ export class GoogleFontsStore { params = $state({}); private query: CreateQueryResult; private queryClient = useQueryClient(); constructor(initialParams: GoogleFontsParams = {}) { this.params = initialParams; // Create the query - automatically reactive this.query = createQuery(() => ({ queryKey: getGoogleFontsQueryKey(this.params), queryFn: () => fetchGoogleFontsQuery(this.params), staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 10 * 60 * 1000, // 10 minutes })); } // Proxy TanStack Query's reactive state get fonts() { return this.query.data ?? []; } get isLoading() { return this.query.isLoading; } get isFetching() { return this.query.isFetching; } get isRefetching() { return this.query.isRefetching; } get error() { return this.query.error; } get isError() { return this.query.isError; } get isSuccess() { return this.query.isSuccess; } get status() { return this.query.status; } // Derived helpers get hasData() { return this.fonts.length > 0; } get isEmpty() { return !this.isLoading && this.fonts.length === 0; } get fontCount() { return this.fonts.length; } // Filtered fonts by category (if you need additional client-side filtering) get sansSerifFonts() { return this.fonts.filter(f => f.category === 'sans-serif'); } get serifFonts() { return this.fonts.filter(f => f.category === 'serif'); } get displayFonts() { return this.fonts.filter(f => f.category === 'display'); } get handwritingFonts() { return this.fonts.filter(f => f.category === 'handwriting'); } get monospaceFonts() { return this.fonts.filter(f => f.category === 'monospace'); } /** * Update parameters - TanStack Query will automatically refetch */ setParams(newParams: Partial) { this.params = { ...this.params, ...newParams }; } setCategory(category: FontCategory | undefined) { this.setParams({ category }); } setSubset(subset: FontSubset | undefined) { this.setParams({ subset }); } setSort(sort: 'popularity' | 'alpha' | 'date' | undefined) { this.setParams({ sort }); } setSearch(search: string) { this.setParams({ search }); } clearSearch() { this.setParams({ search: undefined }); } clearFilters() { this.params = {}; } /** * Manually refetch */ async refetch() { await this.query.refetch(); } /** * Invalidate cache and refetch */ invalidate() { this.queryClient.invalidateQueries({ queryKey: getGoogleFontsQueryKey(this.params), }); } /** * Invalidate all Google Fonts queries */ invalidateAll() { this.queryClient.invalidateQueries({ queryKey: ['googleFonts'], }); } /** * Prefetch with different params (for hover states, pagination, etc.) */ async prefetch(params: GoogleFontsParams) { await this.queryClient.prefetchQuery({ queryKey: getGoogleFontsQueryKey(params), queryFn: () => fetchGoogleFontsQuery(params), staleTime: 5 * 60 * 1000, }); } /** * Prefetch next category (useful for tab switching) */ async prefetchCategory(category: FontCategory) { await this.prefetch({ ...this.params, category }); } /** * Cancel ongoing queries */ cancel() { this.queryClient.cancelQueries({ queryKey: getGoogleFontsQueryKey(this.params), }); } /** * Clear cache for current params */ clearCache() { this.queryClient.removeQueries({ queryKey: getGoogleFontsQueryKey(this.params), }); } /** * Get cached data without triggering fetch */ getCachedData() { return this.queryClient.getQueryData( getGoogleFontsQueryKey(this.params), ); } /** * Check if data exists in cache */ hasCache(params?: GoogleFontsParams) { const key = params ? getGoogleFontsQueryKey(params) : getGoogleFontsQueryKey(this.params); return this.queryClient.getQueryData(key) !== undefined; } /** * Set data manually (optimistic updates) */ setQueryData(updater: (old: UnifiedFont[] | undefined) => UnifiedFont[]) { this.queryClient.setQueryData( getGoogleFontsQueryKey(this.params), updater, ); } /** * Get query state for debugging */ getQueryState() { return this.queryClient.getQueryState( getGoogleFontsQueryKey(this.params), ); } } /** * Factory function to create Google Fonts store */ export function createGoogleFontsStore(params: GoogleFontsParams = {}) { return new GoogleFontsStore(params); }