fix(filters): use proxy fetch function

This commit is contained in:
Ilia Mashkov
2026-03-02 15:06:06 +03:00
parent 6d06f9f877
commit ba20d6d264
2 changed files with 218 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
/**
* Proxy API filters
*
* Fetches filter metadata from GlyphDiff proxy API.
* Provides type-safe response handling.
*
* @see https://api.glyphdiff.com/api/v1/filters
*/
import { api } from '$shared/api/api';
/**
* Filter metadata type from backend
*/
export interface FilterMetadata {
/** Filter ID (e.g., "providers", "categories", "subsets") */
id: string;
/** Display name (e.g., "Font Providers", "Categories", "Character Subsets") */
name: string;
/** Filter description */
description: string;
/** Filter type */
type: 'enum' | 'string' | 'array';
/** Available filter options */
options: FilterOption[];
}
/**
* Filter option type
*/
export interface FilterOption {
/** Option ID (e.g., "google", "serif", "latin") */
id: string;
/** Display name (e.g., "Google Fonts", "Serif", "Latin") */
name: string;
/** Option value (e.g., "google", "serif", "latin") */
value: string;
/** Number of fonts with this value */
count: number;
}
/**
* Proxy filters API response
*/
export interface ProxyFiltersResponse {
/** Array of filter metadata */
filters: FilterMetadata[];
}
/**
* Fetch filters from proxy API
*
* @returns Promise resolving to array of filter metadata
* @throws ApiError when request fails
*
* @example
* ```ts
* // Fetch all filters
* const filters = await fetchProxyFilters();
*
* console.log(filters); // [
* // { id: "providers", name: "Font Providers", options: [...] },
* // { id: "categories", name: "Categories", options: [...] },
* // { id: "subsets", name: "Character Subsets", options: [...] }
* // ]
* ```
*/
export async function fetchProxyFilters(): Promise<FilterMetadata[]> {
const response = await api.get<FilterMetadata[]>('/api/v1/filters');
if (!response.data || !Array.isArray(response.data)) {
throw new Error('Proxy API returned invalid response');
}
return response.data;
}

View File

@@ -0,0 +1,135 @@
/**
* Filters store for dynamic filter metadata
*
* Fetches and caches filter metadata from /api/v1/filters endpoint.
* Provides reactive access to filter data for providers, categories, and subsets.
*
* @example
* ```ts
* import { filtersStore } from '$features/GetFonts';
*
* // Access filters (reactive)
* $: filters = filtersStore.filters;
* $: isLoading = filtersStore.isLoading;
* $: error = filtersStore.error;
* ```
*/
import { fetchProxyFilters } from '$entities/Font/api/proxy/filters';
import type {
FilterMetadata,
FilterOption,
} from '$entities/Font/api/proxy/filters';
import { queryClient } from '$shared/api/queryClient';
import {
type QueryKey,
QueryObserver,
type QueryObserverOptions,
type QueryObserverResult,
} from '@tanstack/query-core';
/**
* Filters store wrapping TanStack Query
*
* Fetches and caches filter metadata using fetchProxyFilters()
* Provides reactive access to filter data
*/
class FiltersStore {
/** Cleanup function for effects */
cleanup: () => void;
/** TanStack Query result state */
protected result = $state<QueryObserverResult<FilterMetadata[], Error>>({} as any);
/** TanStack Query observer instance */
protected observer: QueryObserver<FilterMetadata[], Error>;
/** Shared query client */
protected qc = queryClient;
/**
* Creates a new filters store
*/
constructor() {
this.observer = new QueryObserver(this.qc, this.getOptions());
// Sync TanStack Query state -> Svelte state
this.observer.subscribe(r => {
this.result = r;
});
// Sync Svelte state changes -> TanStack Query options
this.cleanup = $effect.root(() => {
$effect(() => {
this.observer.setOptions(this.getOptions());
});
});
}
/**
* Query key for TanStack Query caching
*/
protected getQueryKey(): QueryKey {
return ['filters'] as const;
}
/**
* Fetch function for filter metadata
* Uses fetchProxyFilters() from proxy API
*/
protected async fetchFn(): Promise<FilterMetadata[]> {
return await fetchProxyFilters();
}
/**
* TanStack Query options
*/
protected getOptions(): QueryObserverOptions<FilterMetadata[], Error> {
return {
queryKey: this.getQueryKey(),
queryFn: () => this.fetchFn(),
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes
};
}
/**
* Get all filters
*/
get filters(): FilterMetadata[] {
return this.result.data ?? [];
}
/**
* Get loading state
*/
get isLoading(): boolean {
return this.result.isLoading;
}
/**
* Get error state
*/
get isError(): boolean {
return this.result.isError;
}
/**
* Get error message
*/
get error(): string | null {
return this.result.error?.message ?? null;
}
/**
* Clean up effects and observers
*/
destroy() {
this.cleanup();
}
}
/**
* Singleton instance
*/
export const filtersStore = new FiltersStore();