211 lines
5.6 KiB
TypeScript
211 lines
5.6 KiB
TypeScript
import { queryClient } from '$shared/api/queryClient';
|
|
import {
|
|
type QueryKey,
|
|
QueryObserver,
|
|
type QueryObserverOptions,
|
|
type QueryObserverResult,
|
|
} 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 };
|
|
|
|
// 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 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());
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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),
|
|
queryFn: () => this.fetchFn(params),
|
|
staleTime: 5 * 60 * 1000,
|
|
gcTime: 10 * 60 * 1000,
|
|
};
|
|
}
|
|
|
|
/** 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;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
return () => {
|
|
this.#bindings = this.#bindings.filter(b => b !== getter);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update query parameters
|
|
* @param newParams - Partial params to merge with existing
|
|
*/
|
|
setParams(newParams: Partial<TParams>) {
|
|
this.#internalParams = { ...this.params, ...newParams };
|
|
}
|
|
|
|
/**
|
|
* Invalidate cache and refetch
|
|
*/
|
|
invalidate() {
|
|
this.qc.invalidateQueries({ queryKey: this.getQueryKey(this.params) });
|
|
}
|
|
|
|
/**
|
|
* Clean up effects and observers
|
|
*/
|
|
destroy() {
|
|
this.cleanup();
|
|
}
|
|
|
|
/**
|
|
* Manually trigger a refetch
|
|
*/
|
|
async refetch() {
|
|
await this.observer.refetch();
|
|
}
|
|
|
|
/**
|
|
* Prefetch data with different parameters
|
|
*/
|
|
async prefetch(params: TParams) {
|
|
await this.qc.prefetchQuery(this.getOptions(params));
|
|
}
|
|
|
|
/**
|
|
* Cancel ongoing queries
|
|
*/
|
|
cancel() {
|
|
this.qc.cancelQueries({
|
|
queryKey: this.getQueryKey(this.params),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Clear cache for current params
|
|
*/
|
|
clearCache() {
|
|
this.qc.removeQueries({
|
|
queryKey: this.getQueryKey(this.params),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get cached data without triggering fetch
|
|
*/
|
|
getCachedData() {
|
|
return this.qc.getQueryData<UnifiedFont[]>(
|
|
this.getQueryKey(this.params),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Set data manually (optimistic updates)
|
|
*/
|
|
setQueryData(updater: (old: UnifiedFont[] | undefined) => UnifiedFont[]) {
|
|
this.qc.setQueryData(
|
|
this.getQueryKey(this.params),
|
|
updater,
|
|
);
|
|
}
|
|
}
|