feature: Create BaseFontStore class with Tanstack query logic and FontshareStore, GoogleFontsStore based on it
This commit is contained in:
156
src/entities/Font/model/store/baseFontStore.svelte.ts
Normal file
156
src/entities/Font/model/store/baseFontStore.svelte.ts
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/entities/Font/model/store/fontshareStore.svelte.ts
Normal file
32
src/entities/Font/model/store/fontshareStore.svelte.ts
Normal 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();
|
||||||
27
src/entities/Font/model/store/googleFontsStore.svelte.ts
Normal file
27
src/entities/Font/model/store/googleFontsStore.svelte.ts
Normal 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();
|
||||||
Reference in New Issue
Block a user