refactor(Font): consolidate API layer and update type structure
This commit is contained in:
@@ -7,41 +7,64 @@ import {
|
||||
} from '@tanstack/query-core';
|
||||
import type { UnifiedFont } from '../types';
|
||||
|
||||
/** */
|
||||
/**
|
||||
* Base class for font stores using TanStack Query
|
||||
*
|
||||
* Provides reactive font data fetching with caching, automatic refetching,
|
||||
* and parameter binding. Extended by UnifiedFontStore for provider-agnostic
|
||||
* font fetching.
|
||||
*
|
||||
* @template TParams - Type of query parameters
|
||||
*/
|
||||
export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
/**
|
||||
* Cleanup function for effects
|
||||
* Call destroy() to remove effects and prevent memory leaks
|
||||
*/
|
||||
cleanup: () => void;
|
||||
|
||||
/** Reactive parameter bindings from external sources */
|
||||
#bindings = $state<(() => Partial<TParams>)[]>([]);
|
||||
/** Internal parameter state */
|
||||
#internalParams = $state<TParams>({} as TParams);
|
||||
|
||||
/**
|
||||
* Merged params from internal state and all bindings
|
||||
* Automatically updates when bindings or internal params change
|
||||
*/
|
||||
params = $derived.by(() => {
|
||||
let merged = { ...this.#internalParams };
|
||||
|
||||
// Loop through every "Cable" plugged into the store
|
||||
// Loop through every "Cable" plugged into the store
|
||||
// Merge all binding results into params
|
||||
for (const getter of this.#bindings) {
|
||||
const bindingResult = getter();
|
||||
merged = { ...merged, ...bindingResult };
|
||||
}
|
||||
|
||||
return merged as TParams;
|
||||
});
|
||||
|
||||
/** TanStack Query result state */
|
||||
protected result = $state<QueryObserverResult<UnifiedFont[], Error>>({} as any);
|
||||
/** TanStack Query observer instance */
|
||||
protected observer: QueryObserver<UnifiedFont[], Error>;
|
||||
/** Shared query client */
|
||||
protected qc = queryClient;
|
||||
|
||||
/**
|
||||
* Creates a new base font store
|
||||
* @param initialParams - Initial query parameters
|
||||
*/
|
||||
constructor(initialParams: TParams) {
|
||||
this.#internalParams = initialParams;
|
||||
|
||||
this.observer = new QueryObserver(this.qc, this.getOptions());
|
||||
|
||||
// Sync TanStack -> Svelte State
|
||||
// Sync TanStack Query state -> Svelte state
|
||||
this.observer.subscribe(r => {
|
||||
this.result = r;
|
||||
});
|
||||
|
||||
// Sync Svelte State -> TanStack Options
|
||||
// Sync Svelte state changes -> TanStack Query options
|
||||
this.cleanup = $effect.root(() => {
|
||||
$effect(() => {
|
||||
this.observer.setOptions(this.getOptions());
|
||||
@@ -50,11 +73,21 @@ export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mandatory: Child must define how to fetch data and what the key is.
|
||||
* Must be implemented by child class
|
||||
* Returns the query key for TanStack Query caching
|
||||
*/
|
||||
protected abstract getQueryKey(params: TParams): QueryKey;
|
||||
|
||||
/**
|
||||
* Must be implemented by child class
|
||||
* Fetches font data from API
|
||||
*/
|
||||
protected abstract fetchFn(params: TParams): Promise<UnifiedFont[]>;
|
||||
|
||||
/**
|
||||
* Gets TanStack Query options
|
||||
* @param params - Query parameters (defaults to current params)
|
||||
*/
|
||||
protected getOptions(params = this.params): QueryObserverOptions<UnifiedFont[], Error> {
|
||||
return {
|
||||
queryKey: this.getQueryKey(params),
|
||||
@@ -64,25 +97,36 @@ export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
};
|
||||
}
|
||||
|
||||
// --- Common Getters ---
|
||||
/** Array of fonts (empty array if loading/error) */
|
||||
get fonts() {
|
||||
return this.result.data ?? [];
|
||||
}
|
||||
|
||||
/** Whether currently fetching initial data */
|
||||
get isLoading() {
|
||||
return this.result.isLoading;
|
||||
}
|
||||
|
||||
/** Whether any fetch is in progress (including refetches) */
|
||||
get isFetching() {
|
||||
return this.result.isFetching;
|
||||
}
|
||||
|
||||
/** Whether last fetch resulted in an error */
|
||||
get isError() {
|
||||
return this.result.isError;
|
||||
}
|
||||
|
||||
/** Whether no fonts are loaded (not loading and empty array) */
|
||||
get isEmpty() {
|
||||
return !this.isLoading && this.fonts.length === 0;
|
||||
}
|
||||
|
||||
// --- Common Actions ---
|
||||
|
||||
/**
|
||||
* Add a reactive parameter binding
|
||||
* @param getter - Function that returns partial params to merge
|
||||
* @returns Unbind function to remove the binding
|
||||
*/
|
||||
addBinding(getter: () => Partial<TParams>) {
|
||||
this.#bindings.push(getter);
|
||||
|
||||
@@ -91,9 +135,14 @@ export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update query parameters
|
||||
* @param newParams - Partial params to merge with existing
|
||||
*/
|
||||
setParams(newParams: Partial<TParams>) {
|
||||
this.#internalParams = { ...this.params, ...newParams };
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate cache and refetch
|
||||
*/
|
||||
@@ -101,19 +150,22 @@ export abstract class BaseFontStore<TParams extends Record<string, any>> {
|
||||
this.qc.invalidateQueries({ queryKey: this.getQueryKey(this.params) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up effects and observers
|
||||
*/
|
||||
destroy() {
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually refetch
|
||||
* Manually trigger a refetch
|
||||
*/
|
||||
async refetch() {
|
||||
await this.observer.refetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch with different params (for hover states, pagination, etc.)
|
||||
* Prefetch data with different parameters
|
||||
*/
|
||||
async prefetch(params: TParams) {
|
||||
await this.qc.prefetchQuery(this.getOptions(params));
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/**
|
||||
* ============================================================================
|
||||
* UNIFIED FONT STORE TYPES
|
||||
* ============================================================================
|
||||
*
|
||||
* Type definitions for the unified font store infrastructure.
|
||||
* Provides types for filters, sorting, and fetch parameters.
|
||||
*/
|
||||
|
||||
import type {
|
||||
FontshareParams,
|
||||
GoogleFontsParams,
|
||||
} from '$entities/Font/api';
|
||||
import type {
|
||||
FontCategory,
|
||||
FontProvider,
|
||||
FontSubset,
|
||||
} from '$entities/Font/model/types/common';
|
||||
|
||||
/**
|
||||
* Sort configuration
|
||||
*/
|
||||
export interface FontSort {
|
||||
field: 'name' | 'popularity' | 'category' | 'date';
|
||||
direction: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch params for unified API
|
||||
*/
|
||||
export interface FetchFontsParams {
|
||||
providers?: FontProvider[];
|
||||
categories?: FontCategory[];
|
||||
subsets?: FontSubset[];
|
||||
search?: string;
|
||||
sort?: FontSort;
|
||||
forceRefetch?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider-specific params union
|
||||
*/
|
||||
export type ProviderParams = GoogleFontsParams | FontshareParams;
|
||||
@@ -43,7 +43,7 @@ import { BaseFontStore } from './baseFontStore.svelte';
|
||||
* });
|
||||
*
|
||||
* // Update parameters
|
||||
* store.setCategory('serif');
|
||||
* store.setCategories(['serif']);
|
||||
* store.nextPage();
|
||||
* ```
|
||||
*/
|
||||
@@ -108,16 +108,20 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
this.#filterCleanup = $effect.root(() => {
|
||||
$effect(() => {
|
||||
const filterParams = JSON.stringify({
|
||||
provider: this.params.provider,
|
||||
category: this.params.category,
|
||||
subset: this.params.subset,
|
||||
providers: this.params.providers,
|
||||
categories: this.params.categories,
|
||||
subsets: this.params.subsets,
|
||||
q: this.params.q,
|
||||
});
|
||||
|
||||
// If filters changed, reset offset to 0
|
||||
// If filters changed, reset offset and invalidate cache
|
||||
if (filterParams !== this.#previousFilterParams) {
|
||||
if (this.#previousFilterParams && this.params.offset !== 0) {
|
||||
this.setParams({ offset: 0 });
|
||||
if (this.#previousFilterParams) {
|
||||
if (this.params.offset !== 0) {
|
||||
this.setParams({ offset: 0 });
|
||||
}
|
||||
this.#accumulatedFonts = [];
|
||||
this.invalidate();
|
||||
}
|
||||
this.#previousFilterParams = filterParams;
|
||||
}
|
||||
@@ -170,7 +174,7 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
}
|
||||
|
||||
protected getOptions(params = this.params): QueryObserverOptions<UnifiedFont[], Error> {
|
||||
const hasFilters = !!(params.q || params.provider || params.category || params.subset);
|
||||
const hasFilters = !!(params.q || params.providers || params.categories || params.subsets);
|
||||
return {
|
||||
queryKey: this.getQueryKey(params),
|
||||
queryFn: () => this.fetchFn(params),
|
||||
@@ -221,8 +225,6 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
return response.fonts;
|
||||
}
|
||||
|
||||
// --- Getters (proxied from BaseFontStore) ---
|
||||
|
||||
/**
|
||||
* Get all accumulated fonts (for infinite scroll)
|
||||
*/
|
||||
@@ -258,27 +260,25 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
return !this.isLoading && this.fonts.length === 0;
|
||||
}
|
||||
|
||||
// --- Provider-specific shortcuts ---
|
||||
|
||||
/**
|
||||
* Set provider filter
|
||||
* Set providers filter
|
||||
*/
|
||||
setProvider(provider: 'google' | 'fontshare' | undefined) {
|
||||
this.setParams({ provider });
|
||||
setProviders(providers: ProxyFontsParams['providers']) {
|
||||
this.setParams({ providers });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set category filter
|
||||
* Set categories filter
|
||||
*/
|
||||
setCategory(category: ProxyFontsParams['category']) {
|
||||
this.setParams({ category });
|
||||
setCategories(categories: ProxyFontsParams['categories']) {
|
||||
this.setParams({ categories });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set subset filter
|
||||
* Set subsets filter
|
||||
*/
|
||||
setSubset(subset: ProxyFontsParams['subset']) {
|
||||
this.setParams({ subset });
|
||||
setSubsets(subsets: ProxyFontsParams['subsets']) {
|
||||
this.setParams({ subsets });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,8 +295,6 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
this.setParams({ sort });
|
||||
}
|
||||
|
||||
// --- Pagination methods ---
|
||||
|
||||
/**
|
||||
* Go to next page
|
||||
*/
|
||||
@@ -337,8 +335,6 @@ export class UnifiedFontStore extends BaseFontStore<ProxyFontsParams> {
|
||||
this.setParams({ limit });
|
||||
}
|
||||
|
||||
// --- Category shortcuts (for convenience) ---
|
||||
|
||||
get sansSerifFonts() {
|
||||
return this.fonts.filter(f => f.category === 'sans-serif');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user