import { queryClient } from '$shared/api/queryClient'; import { type QueryKey, QueryObserver, type QueryObserverOptions, type QueryObserverResult, } from '@tanstack/query-core'; import type { UnifiedFont } from '../types'; /** */ export abstract class BaseFontStore> { // params = $state({} as TParams); cleanup: () => void; #bindings = $state<(() => Partial)[]>([]); #internalParams = $state({} as TParams); params = $derived.by(() => { let merged = { ...this.#internalParams }; // Loop through every "Cable" plugged into the store for (const getter of this.#bindings) { merged = { ...merged, ...getter() }; } return merged as TParams; }); protected result = $state>({} as any); protected observer: QueryObserver; protected qc = queryClient; constructor(initialParams: TParams) { this.#internalParams = initialParams; this.observer = new QueryObserver(this.qc, this.getOptions()); // Sync TanStack -> Svelte State this.observer.subscribe(r => { this.result = r; }); // Sync Svelte State -> TanStack Options this.cleanup = $effect.root(() => { $effect(() => { this.observer.setOptions(this.getOptions()); }); }); } /** * Mandatory: Child must define how to fetch data and what the key is. */ protected abstract getQueryKey(params: TParams): QueryKey; protected abstract fetchFn(params: TParams): Promise; private getOptions(params = this.params): QueryObserverOptions { return { queryKey: this.getQueryKey(params), queryFn: () => this.fetchFn(params), staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, }; } // --- Common Getters --- get fonts() { return this.result.data ?? []; } get isLoading() { return this.result.isLoading; } get isFetching() { return this.result.isFetching; } get isError() { return this.result.isError; } get isEmpty() { return !this.isLoading && this.fonts.length === 0; } // --- Common Actions --- addBinding(getter: () => Partial) { this.#bindings.push(getter); return () => { this.#bindings = this.#bindings.filter(b => b !== getter); }; } setParams(newParams: Partial) { this.#internalParams = { ...this.params, ...newParams }; } /** * Invalidate cache and refetch */ invalidate() { this.qc.invalidateQueries({ queryKey: this.getQueryKey(this.params) }); } destroy() { this.cleanup(); } /** * Manually refetch */ async refetch() { await this.observer.refetch(); } /** * Prefetch with different params (for hover states, pagination, etc.) */ 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( this.getQueryKey(this.params), ); } /** * Set data manually (optimistic updates) */ setQueryData(updater: (old: UnifiedFont[] | undefined) => UnifiedFont[]) { this.qc.setQueryData( this.getQueryKey(this.params), updater, ); } }