refactor(Font): consolidate API layer and update type structure

This commit is contained in:
Ilia Mashkov
2026-03-02 22:18:21 +03:00
parent ba186d00a1
commit af4137f47f
17 changed files with 325 additions and 558 deletions

View File

@@ -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));