feature: Create BaseFontStore class with Tanstack query logic and FontshareStore, GoogleFontsStore based on it

This commit is contained in:
Ilia Mashkov
2026-01-13 20:02:20 +03:00
parent 91451f7886
commit a29b80efbb
3 changed files with 215 additions and 0 deletions

View File

@@ -0,0 +1,156 @@
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<TParams extends Record<string, any>> {
// params = $state<TParams>({} as TParams);
cleanup: () => void;
#bindings = $state<(() => Partial<TParams>)[]>([]);
#internalParams = $state<TParams>({} 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<QueryObserverResult<UnifiedFont[], Error>>({} as any);
protected observer: QueryObserver<UnifiedFont[], Error>;
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<UnifiedFont[]>;
private getOptions(params = this.params): QueryObserverOptions<UnifiedFont[], Error> {
return {
queryKey: this.getQueryKey(params),
queryFn: () => this.fetchFn(params),
staleTime: 5 * 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<TParams>) {
this.#bindings.push(getter);
return () => {
this.#bindings = this.#bindings.filter(b => b !== getter);
};
}
setParams(newParams: Partial<TParams>) {
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<UnifiedFont[]>(
this.getQueryKey(this.params),
);
}
/**
* Set data manually (optimistic updates)
*/
setQueryData(updater: (old: UnifiedFont[] | undefined) => UnifiedFont[]) {
this.qc.setQueryData(
this.getQueryKey(this.params),
updater,
);
}
}

View File

@@ -0,0 +1,32 @@
import type { FontshareParams } from '../../api';
import { fetchFontshareFontsQuery } from '../services';
import type { UnifiedFont } from '../types';
import { BaseFontStore } from './baseFontStore.svelte';
/**
* Fontshare store wrapping TanStack Query with runes
*/
export class FontshareStore extends BaseFontStore<FontshareParams> {
constructor(initialParams: FontshareParams = {}) {
super(initialParams);
}
protected getQueryKey(params: FontshareParams) {
return ['fontshare', params] as const;
}
protected async fetchFn(params: FontshareParams): Promise<UnifiedFont[]> {
return fetchFontshareFontsQuery(params);
}
// Provider-specific methods (shortcuts)
setSearch(search: string) {
this.setParams({ q: search } as any);
}
}
export function createFontshareStore(params: FontshareParams = {}) {
return new FontshareStore(params);
}
export const fontshareStore = new FontshareStore();

View File

@@ -0,0 +1,27 @@
import type { GoogleFontsParams } from '../../api';
import { fetchGoogleFontsQuery } from '../services';
import type { UnifiedFont } from '../types';
import { BaseFontStore } from './baseFontStore.svelte';
/**
* Google Fonts store wrapping TanStack Query with runes
*/
export class GoogleFontsStore extends BaseFontStore<GoogleFontsParams> {
constructor(initialParams: GoogleFontsParams = {}) {
super(initialParams);
}
protected getQueryKey(params: GoogleFontsParams) {
return ['googleFonts', params] as const;
}
protected async fetchFn(params: GoogleFontsParams): Promise<UnifiedFont[]> {
return fetchGoogleFontsQuery(params);
}
}
export function createFontshareStore(params: GoogleFontsParams = {}) {
return new GoogleFontsStore(params);
}
export const googleFontsStore = new GoogleFontsStore();