/** * Proxy API client * * Handles API requests to GlyphDiff proxy API for fetching font metadata. * Provides error handling, pagination support, and type-safe responses. * * Proxy API normalizes font data from Google Fonts and Fontshare into a single * unified format, eliminating the need for client-side normalization. * * @see https://api.glyphdiff.com/api/v1/fonts */ import { api } from '$shared/api/api'; import { queryClient } from '$shared/api/queryClient'; import { fontKeys } from '$shared/api/queryKeys'; import { buildQueryString } from '$shared/lib/utils'; import type { QueryParams } from '$shared/lib/utils'; import type { UnifiedFont } from '../../model/types'; /** * Normalizes cache by seeding individual font entries from collection responses. * This ensures that a font fetched in a list or batch is available via its detail key. * * @param fonts - Array of fonts to cache */ export function seedFontCache(fonts: UnifiedFont[]): void { fonts.forEach(font => { queryClient.setQueryData(fontKeys.detail(font.id), font); }); } /** * Proxy API base URL */ const PROXY_API_URL = 'https://api.glyphdiff.com/api/v1/fonts' as const; /** * Proxy API parameters * * Maps directly to the proxy API query parameters * * UPDATED: Now supports array values for filters */ export interface ProxyFontsParams extends QueryParams { /** * Font provider filter * * NEW: Supports array of providers (e.g., ["google", "fontshare"]) * Backward compatible: Single value still works */ providers?: string[] | string; /** * Font category filter * * NEW: Supports array of categories (e.g., ["serif", "sans-serif"]) * Backward compatible: Single value still works */ categories?: string[] | string; /** * Character subset filter * * NEW: Supports array of subsets (e.g., ["latin", "cyrillic"]) * Backward compatible: Single value still works */ subsets?: string[] | string; /** * Search query (e.g., "roboto", "satoshi") */ q?: string; /** * Sort order for results * "name" - Alphabetical by font name * "popularity" - Most popular first * "lastModified" - Recently updated first */ sort?: 'name' | 'popularity' | 'lastModified'; /** * Number of items to return (pagination) */ limit?: number; /** * Number of items to skip (pagination) * Use for pagination: offset = (page - 1) * limit */ offset?: number; } /** * Proxy API response * * Includes pagination metadata alongside font data */ export interface ProxyFontsResponse { /** * List of font objects returned by the proxy */ fonts: UnifiedFont[]; /** * Total number of matching fonts (ignoring limit/offset) */ total: number; /** * Page size used for the request */ limit: number; /** * Start index for the result set */ offset: number; } /** * Fetch fonts from proxy API * * @param params - Query parameters for filtering and pagination * @returns Promise resolving to proxy API response * @throws ApiError when request fails * * @example * ```ts * // Fetch all sans-serif fonts from Google * const response = await fetchProxyFonts({ * provider: 'google', * category: 'sans-serif', * limit: 50, * offset: 0 * }); * * // Search fonts across all providers * const searchResponse = await fetchProxyFonts({ * q: 'roboto', * limit: 20 * }); * * // Fetch fonts with pagination * const page1 = await fetchProxyFonts({ limit: 50, offset: 0 }); * const page2 = await fetchProxyFonts({ limit: 50, offset: 50 }); * ``` */ export async function fetchProxyFonts( params: ProxyFontsParams = {}, ): Promise { const queryString = buildQueryString(params); const url = `${PROXY_API_URL}${queryString}`; const response = await api.get(url); if (!response.data || !Array.isArray(response.data.fonts)) { throw new Error('Proxy API returned invalid response'); } return response.data; } /** * Fetch font by ID * * Convenience function for fetching a single font by ID * Note: This fetches a page and filters client-side, which is not ideal * For production, consider adding a dedicated endpoint to the proxy API * * @param id - Font ID (family name for Google, slug for Fontshare) * @returns Promise resolving to font or undefined * * @example * ```ts * const roboto = await fetchProxyFontById('Roboto'); * const satoshi = await fetchProxyFontById('satoshi'); * ``` */ export async function fetchProxyFontById( id: string, ): Promise { const response = await fetchProxyFonts({ limit: 1000, q: id }); if (!response || !response.fonts) { console.error('[fetchProxyFontById] No fonts in response', { response }); return undefined; } return response.fonts.find(font => font.id === id); } /** * Fetch multiple fonts by their IDs * * @param ids - Array of font IDs to fetch * @returns Promise resolving to an array of fonts */ export async function fetchFontsByIds(ids: string[]): Promise { if (ids.length === 0) { return []; } const queryString = ids.join(','); const url = `${PROXY_API_URL}/batch?ids=${queryString}`; const response = await api.get(url); return response.data ?? []; }