feat: new version of unifiedFontStore

This commit is contained in:
Ilia Mashkov
2026-01-13 20:10:44 +03:00
parent a9cdd15787
commit 955cc66916

View File

@@ -1,263 +1,29 @@
/**
* ============================================================================
* UNIFIED FONT STORE
* ============================================================================
*
* Single source of truth for font operations across all providers.
* Combines fetching, filtering, caching, and managing fonts from Google Fonts and Fontshare.
*
* NOW INTEGRATED WITH TANSTACK QUERY via specific stores.
*
* OPTIMIZATIONS (P0/P1):
* - Debounced search (300ms) to reduce re-renders
* - Single-pass filter function for O(n) complexity
* - Two derived values: filteredFonts (filtered) + sortedFilteredFonts (final)
* - TanStack Query cancellation for stale requests
*/
import { debounce } from '$shared/lib/utils';
import {
filterFonts,
sortFonts,
} from '../../lib/filterUtils';
import { createFontshareStore } from '../services/fetchFontshareFonts.svelte';
import { createGoogleFontsStore } from '../services/fetchGoogleFonts.svelte';
import type { UnifiedFont } from '../types/normalize';
import type {
FetchFontsParams,
FilterType,
FontFilters,
FontSort,
} from './types';
type Filter,
type FilterModel,
createFilter,
} from '$shared/lib';
import { SvelteMap } from 'svelte/reactivity';
import type { FontProvider } from '../types';
import type { CheckboxFilter } from '../types/common';
import type { BaseFontStore } from './baseFontStore.svelte';
import { createFontshareStore } from './fontshareStore.svelte';
import type { ProviderParams } from './types';
/**
* Creates a unified font store instance.
*/
export function createUnifiedFontStore() {
// Instantiate specific stores
const googleStore = createGoogleFontsStore();
const fontshareStore = createFontshareStore();
export class UnitedFontStore {
private sources: Partial<Record<FontProvider, BaseFontStore<ProviderParams>>>;
// Internal state for local filters (that apply to the combined list)
let _filters = $state<FontFilters>({
providers: [],
categories: [],
subsets: [],
searchQuery: '',
});
filters: SvelteMap<CheckboxFilter, Filter>;
queryValue = $state('');
let _sort = $state<FontSort>({ field: 'name', direction: 'asc' });
// === Debounced Search ===
// Debounce search query to avoid excessive filtering during typing
let _debouncedSearchQuery = $state('');
const setSearchQuery = debounce((query: string) => {
_debouncedSearchQuery = query;
}, 300);
$effect(() => {
setSearchQuery(_filters.searchQuery);
});
// === Computed Values ===
/**
* Combined fonts from all active stores
*/
const allFonts = $derived.by(() => {
let fonts: UnifiedFont[] = [];
// Google Fonts
if (_filters.providers.length === 0 || _filters.providers.includes('google')) {
fonts = [...fonts, ...googleStore.fonts];
}
// Fontshare Fonts
if (_filters.providers.length === 0 || _filters.providers.includes('fontshare')) {
fonts = [...fonts, ...fontshareStore.fonts];
}
return fonts;
});
/**
* Filtered fonts (before sort) - Derived Value #1
* Uses optimized single-pass filter function
*/
const filteredFonts = $derived.by(() => {
const filtersForFiltering: FontFilters = {
providers: _filters.providers,
categories: _filters.categories,
subsets: _filters.subsets,
searchQuery: _debouncedSearchQuery,
constructor(initialConfig: Partial<Record<FontProvider, ProviderParams>> = {}) {
this.sources = {
fontshare: createFontshareStore(initialConfig?.fontshare),
};
return filterFonts(allFonts, filtersForFiltering);
});
/**
* Sorted filtered fonts (final result) - Derived Value #2
* Fast path: skip sorting for default name ascending order
*/
const sortedFilteredFonts = $derived.by(() => {
const filtered = filteredFonts;
// Fast path: default sort (name ascending) - already sorted by filterFonts
if (_sort.field === 'name' && _sort.direction === 'asc') {
return filtered;
this.filters = new SvelteMap();
}
return sortFonts(filtered, _sort);
});
// === Status Derivation ===
const isLoading = $derived(googleStore.isLoading || fontshareStore.isLoading);
const isFetching = $derived(googleStore.isFetching || fontshareStore.isFetching);
const error = $derived(googleStore.error?.message || fontshareStore.error?.message || null);
// === Methods ===
/**
* Fetch fonts from all active providers using TanStack Query
* This now mostly just updates params or triggers refetch if needed.
*
* Includes cancellation of stale requests to improve performance.
*/
async function fetchFonts(params?: FetchFontsParams): Promise<void> {
// Update local filters
if (params) {
if (params.providers) _filters.providers = params.providers;
if (params.categories) _filters.categories = params.categories;
if (params.subsets) _filters.subsets = params.subsets;
if (params.search !== undefined) _filters.searchQuery = params.search;
if (params.sort) _sort = params.sort;
}
// Cancel existing queries before starting new ones (optimization)
googleStore.cancel();
fontshareStore.cancel();
// Trigger fetches in underlying stores
// We pass the filter params down to the stores so they can optimize server-side fetching if supported
// For Google Fonts:
googleStore.setParams({
category: _filters.categories.length === 1 ? _filters.categories[0] : undefined,
subset: _filters.subsets.length === 1 ? _filters.subsets[0] : undefined,
sort: (_sort.field === 'popularity'
? 'popularity'
: _sort.field === 'date'
? 'date'
: 'alpha') as any,
});
// For Fontshare:
// fontshareStore.setCategories(_filters.categories); // If supported
}
/** Update specific filter */
function setFilter(type: FilterType, values: string[]): void {
if (type === 'searchQuery') {
_filters.searchQuery = values[0] ?? '';
} else {
_filters = {
..._filters,
[type]: values as any, // Type cast for loose array matching
};
}
}
/** Clear specific filter */
function clearFilter(type: FilterType): void {
if (type === 'searchQuery') {
_filters.searchQuery = '';
} else {
_filters = {
..._filters,
[type]: [],
};
}
}
/** Clear all filters */
function clearFilters(): void {
_filters = {
providers: [],
categories: [],
subsets: [],
searchQuery: '',
};
}
return {
// Getters
get fonts() {
return allFonts;
},
get filteredFonts() {
return sortedFilteredFonts;
},
get count() {
return allFonts.length;
},
get isLoading() {
return isLoading;
},
get isFetching() {
return isFetching;
},
get error() {
return error;
},
get filters() {
return _filters;
},
get searchQuery() {
return _filters.searchQuery;
},
get sort() {
return _sort;
},
get providers() {
// Expose underlying stores for direct access if needed
return {
google: googleStore,
fontshare: fontshareStore,
};
},
// Setters
set filters(value: FontFilters) {
_filters = value;
},
set searchQuery(value: string) {
_filters.searchQuery = value;
},
set sort(value: FontSort) {
_sort = value;
},
// Methods
fetchFonts,
setFilter,
clearFilter,
clearFilters,
// Legacy support (no-op or adapted)
addFont: () => {}, // Not supported in reactive query model
addFonts: () => {},
removeFont: () => {},
getFontById: (id: string) => allFonts.find(f => f.id === id),
clearCache: () => {
googleStore.clearCache();
fontshareStore.clearCache();
},
};
return Object.values(this.sources).map(store => store.fonts).flat();
}
}
export type UnifiedFontStore = ReturnType<typeof createUnifiedFontStore>;
/**
* Context key for dependency injection
*/
export const UNIFIED_FONT_STORE_KEY = Symbol('UNIFIED_FONT_STORE');