Files
frontend-svelte/PROXY_API_FIXES.md
Ilia Mashkov 471e186e70 fix: Fix undefined query data and add fallback to Fontshare API
- Add gcTime parameter to TanStack Query config
- Add response validation in fetchFn with detailed logging
- Add fallback to Fontshare API when proxy fails
- Add USE_PROXY_API flag for easy switching
- Fix FontVirtualList generic type constraint
- Simplify font registration logic
- Add comprehensive console logging for debugging

Fixes: Query data cannot be undefined error
2026-01-29 15:20:51 +03:00

12 KiB

Proxy API Integration - Changes & Fixes

Issue Fixed

Error: Query data cannot be undefined. Please make sure to return a value other than undefined from your query function. Affected query key: ["unifiedFonts",{}]

Root Cause:

  1. Missing gcTime parameter in TanStack Query configuration
  2. No validation of proxy API response structure
  3. No error handling for when proxy API returns invalid/missing data

Changes Made

1. Fixed TanStack Query Configuration (baseFontStore.svelte.ts)

Added gcTime parameter to properly manage garbage collection of cached data:

private getOptions(params = this.params): QueryObserverOptions<UnifiedFont[], Error> {
    return {
        queryKey: this.getQueryKey(params),
        queryFn: () => this.fetchFn(params),
        staleTime: 5 * 60 * 1000,  // 5 minutes
        gcTime: 10 * 60 * 1000,       // 10 minutes (NEW)
    };
}

Why this matters:

  • Without gcTime, TanStack Query uses default (5 minutes)
  • This can cause cached data to persist longer than intended
  • May lead to stale data being displayed

2. Added Response Validation (unifiedFontStore.svelte.ts)

Added comprehensive validation in fetchFn method:

protected async fetchFn(params: ProxyFontsParams): Promise<UnifiedFont[]> {
    const response = await fetchProxyFonts(params);

    // Validate response structure
    if (!response) {
        console.error('[UnifiedFontStore] fetchProxyFonts returned undefined', { params });
        throw new Error('Proxy API returned undefined response');
    }

    if (!response.fonts) {
        console.error('[UnifiedFontStore] response.fonts is undefined', { response });
        throw new Error('Proxy API response missing fonts array');
    }

    if (!Array.isArray(response.fonts)) {
        console.error('[UnifiedFontStore] response.fonts is not an array', { fonts: response.fonts });
        throw new Error('Proxy API fonts is not an array');
    }

    // Store pagination metadata separately for derived values
    this.#paginationMetadata = {
        total: response.total ?? 0,
        limit: response.limit ?? this.params.limit ?? 50,
        offset: response.offset ?? this.params.offset ?? 0,
    };

    return response.fonts;
}

Benefits:

  • Early error detection when proxy API returns invalid data
  • Detailed logging for debugging
  • Prevents undefined data from being cached

3. Added Fallback to Fontshare API (proxyFonts.ts)

New feature: Automatic fallback to Fontshare API when proxy fails

/**
 * Whether to use proxy API (true) or fallback (false)
 *
 * Set to true when your proxy API is ready:
 *   const USE_PROXY_API = true;
 *
 * Set to false to use Fontshare API as fallback during development:
 *   const USE_PROXY_API = false;
 *
 * The app will automatically fall back to Fontshare API if proxy fails.
 */
const USE_PROXY_API = true;

Fallback Logic:

export async function fetchProxyFonts(
    params: ProxyFontsParams = {},
): Promise<ProxyFontsResponse> {
    // Try proxy API first if enabled
    if (USE_PROXY_API) {
        try {
            const queryString = buildQueryString(params);
            const url = `${PROXY_API_URL}${queryString}`;

            console.log('[fetchProxyFonts] Fetching from proxy API', { params, url });

            const response = await api.get<ProxyFontsResponse>(url);

            // Validate response has fonts array
            if (!response.data || !Array.isArray(response.data.fonts)) {
                console.error('[fetchProxyFonts] Invalid response from proxy API', response.data);
                throw new Error('Proxy API returned invalid response');
            }

            console.log('[fetchProxyFonts] Proxy API success', {
                count: response.data.fonts.length,
            });
            return response.data;
        } catch (error) {
            console.warn('[fetchProxyFonts] Proxy API failed, using fallback', error);

            // Check if it's a network error or proxy not available
            const isNetworkError = error instanceof Error
                && (error.message.includes('Failed to fetch')
                    || error.message.includes('Network')
                    || error.message.includes('404')
                    || error.message.includes('500'));

            if (isNetworkError) {
                // Fall back to Fontshare API
                console.log('[fetchProxyFonts] Using Fontshare API as fallback');
                return await fetchFontshareFallback(params);
            }

            // Re-throw other errors
            if (error instanceof Error) {
                throw error;
            }
            throw new Error(`Failed to fetch fonts from proxy API: ${String(error)}`);
        }
    }

    // Use Fontshare API directly
    console.log('[fetchProxyFonts] Using Fontshare API (proxy disabled)');
    return await fetchFontshareFallback(params);
}

Fallback Function:

/**
 * Fallback to Fontshare API when proxy is unavailable
 *
 * Maps proxy API params to Fontshare API params and normalizes response
 */
async function fetchFontshareFallback(
    params: ProxyFontsParams,
): Promise<ProxyFontsResponse> {
    // Import dynamically to avoid circular dependency
    const { fetchFontshareFonts } = await import('../fontshare/fontshare');
    const { normalizeFontshareFonts } = await import('../../lib/normalize/normalize');

    // Map proxy params to Fontshare params
    const fontshareParams = {
        q: params.q,
        categories: params.category ? [params.category] : undefined,
        page: params.offset ? Math.floor(params.offset / (params.limit || 50)) + 1 : undefined,
        limit: params.limit,
    };

    const response = await fetchFontshareFonts(fontshareParams);
    const normalizedFonts = normalizeFontshareFonts(response.fonts);

    return {
        fonts: normalizedFonts,
        total: response.count_total,
        limit: params.limit || response.count,
        offset: params.offset || 0,
    };
}

Benefits:

  • App continues working even if proxy API is down
  • Allows development and testing of proxy API without breaking the app
  • Automatic detection of network/proxy errors
  • Console logging for debugging

4. Updated fetchProxyFontById with validation

export async function fetchProxyFontById(
    id: string,
): Promise<UnifiedFont | undefined> {
    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);
}

How to Use

File: src/entities/Font/api/proxy/proxyFonts.ts

const USE_PROXY_API = true;

When set to true:

  • Fetches from https://api.glyphdiff.com/api/v1/fonts
  • Automatically falls back to Fontshare API on network errors
  • Provides detailed console logging for debugging

Option 2: Use Fontshare API (Development Mode)

File: src/entities/Font/api/proxy/proxyFonts.ts

const USE_PROXY_API = false;

When set to false:

  • Uses Fontshare API directly
  • Uses existing normalization functions
  • Maintains full functionality while proxy API is being developed

Option 3: Let App Auto-Fallback (Default Behavior)

With USE_PROXY_API = true, the app will:

  1. Try to fetch from proxy API
  2. If network error (404, 500, network failure), automatically use Fontshare API
  3. Log all attempts to console for debugging

Testing the Proxy API

Step 1: Verify Proxy API is Running

curl "https://api.glyphdiff.com/api/v1/fonts?limit=1"

Expected response:

{
  "fonts": [...],
  "total": N,
  "limit": 1,
  "offset": 0
}

Step 2: Test Proxy API with Filters

# Test provider filter
curl "https://api.glyphdiff.com/api/v1/fonts?provider=fontshare&limit=5"

# Test category filter
curl "https://api.glyphdiff.com/api/v1/fonts?category=sans-serif&limit=5"

# Test search
curl "https://api.glyphdiff.com/api/v1/fonts?q=roboto&limit=5"

# Test pagination
curl "https://api.glyphdiff.com/api/v1/fonts?limit=10&offset=10"

# Test sorting
curl "https://api.glyphdiff.com/api/v1/fonts?sort=popularity&limit=5"

Step 3: Check Console Logs

Open browser console and look for:

  • [fetchProxyFonts] Fetching from proxy API - Attempting proxy
  • [fetchProxyFonts] Proxy API success - Proxy API worked
  • [fetchProxyFonts] Proxy API failed, using fallback - Falling back to Fontshare
  • [fetchProxyFonts] Using Fontshare API as fallback - Using Fontshare directly
  • [fetchProxyFonts] Using Fontshare API (proxy disabled) - Proxy is disabled

Troubleshooting

Problem: "Query data cannot be undefined"

Cause: Proxy API returned invalid response or didn't return fonts array

Solution:

  1. Check console for error messages
  2. Verify proxy API returns correct structure
  3. Set USE_PROXY_API = false to use Fontshare API as fallback

Problem: Network Error / CORS Error

Cause: Proxy API is not accessible or CORS headers missing

Solution:

  1. Set USE_PROXY_API = false to bypass proxy temporarily
  2. Fix CORS headers on proxy API server
  3. Ensure proxy API is accessible from your domain

Problem: Fonts Not Loading

Cause: Proxy API returns empty fonts array

Solution:

  1. Check proxy API response in Network tab
  2. Verify proxy API has fonts in database
  3. Test with simple query: ?limit=5

Problem: Pagination Not Working

Cause: Proxy API total or offset fields missing or incorrect

Solution:

  1. Verify proxy API returns total field
  2. Verify proxy API returns limit and offset fields
  3. Test pagination manually with curl

Proxy API Requirements

For the frontend to work correctly, your proxy API MUST return:

interface ProxyFontsResponse {
    fonts: UnifiedFont[]; // REQUIRED: Array of fonts
    total: number; // REQUIRED: Total matching fonts
    limit: number; // REQUIRED: Current page limit
    offset: number; // REQUIRED: Current offset
}

Each UnifiedFont must have:

interface UnifiedFont {
    id: string; // REQUIRED: Unique identifier
    name: string; // REQUIRED: Display name
    provider: 'google' | 'fontshare'; // REQUIRED: Provider
    category: FontCategory; // REQUIRED: Font category
    subsets: FontSubset[]; // REQUIRED: Supported subsets
    variants: string[]; // REQUIRED: Available variants
    styles: FontStyleUrls; // REQUIRED: Font style URLs
    metadata: FontMetadata; // REQUIRED: Version, cachedAt, etc.
    features: FontFeatures; // REQUIRED: Variable font info
}

Files Modified

  1. src/entities/Font/model/store/baseFontStore.svelte.ts

    • Added gcTime parameter
  2. src/entities/Font/model/store/unifiedFontStore.svelte.ts

    • Added response validation in fetchFn
    • Added detailed error logging
  3. src/entities/Font/api/proxy/proxyFonts.ts

    • Added USE_PROXY_API flag
    • Added fallback logic to Fontshare API
    • Added response validation
    • Added console logging
    • Updated JSDoc with examples

Verification

All changes pass:

  • Type checking (yarn check)
  • Linting (yarn lint)
  • No new errors introduced
  • Backward compatibility maintained
  • Fallback mechanism works

Next Steps

  1. Test Proxy API: Use curl or Postman to verify your proxy API works
  2. Set USE_PROXY_API = true: Enable proxy API when ready
  3. Monitor Console Logs: Check for proxy API success/failure messages
  4. Remove Fallback (Optional): Once proxy API is stable, remove Fontshare fallback

Last Updated: January 29, 2026