refactor(shared): rename fontCache to collectionCache
- Rename fontCache.ts to collectionCache.ts - Rename FontCacheManager interface to CollectionCacheManager - Make implementation fully generic (already was, just renamed interface) - Update exports in shared/fetch/index.ts - Fix getStats() to return derived store value for accurate statistics - Add comprehensive test coverage for collection cache manager - 41 test cases covering all functionality - Tests for caching, deduplication, state tracking - Tests for statistics, reactivity, and edge cases Closes task-1 of Phase 1 refactoring
This commit is contained in:
187
src/entities/Font/api/fontshare.ts
Normal file
187
src/entities/Font/api/fontshare.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Fontshare API client
|
||||
*
|
||||
* Handles API requests to Fontshare API for fetching font metadata.
|
||||
* Provides error handling, pagination support, and type-safe responses.
|
||||
*
|
||||
* @see https://fontshare.com
|
||||
*/
|
||||
|
||||
import type {
|
||||
FontshareApiModel,
|
||||
FontshareFont,
|
||||
} from '$entities/Font';
|
||||
import { api } from '$shared/api/api';
|
||||
|
||||
/**
|
||||
* Fontshare API parameters
|
||||
*/
|
||||
export interface FontshareParams {
|
||||
/**
|
||||
* Filter by categories (e.g., ["Sans", "Serif", "Display"])
|
||||
*/
|
||||
categories?: string[];
|
||||
/**
|
||||
* Filter by tags (e.g., ["Magazines", "Branding", "Logos"])
|
||||
*/
|
||||
tags?: string[];
|
||||
/**
|
||||
* Page number for pagination (1-indexed)
|
||||
*/
|
||||
page?: number;
|
||||
/**
|
||||
* Number of items per page
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* Search query to filter fonts
|
||||
*/
|
||||
search?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fontshare API response wrapper
|
||||
* Extends collection model with additional metadata
|
||||
*/
|
||||
export interface FontshareResponse extends FontshareApiModel {
|
||||
// Response structure matches FontshareApiModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query string from parameters
|
||||
*/
|
||||
function buildQueryString(params: FontshareParams): string {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (params.categories?.length) {
|
||||
searchParams.append('categories', params.categories.join(','));
|
||||
}
|
||||
|
||||
if (params.tags?.length) {
|
||||
searchParams.append('tags', params.tags.join(','));
|
||||
}
|
||||
|
||||
if (params.page) {
|
||||
searchParams.append('page', String(params.page));
|
||||
}
|
||||
|
||||
if (params.limit) {
|
||||
searchParams.append('limit', String(params.limit));
|
||||
}
|
||||
|
||||
if (params.search) {
|
||||
searchParams.append('search', params.search);
|
||||
}
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
return queryString ? `?${queryString}` : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch fonts from Fontshare API
|
||||
*
|
||||
* @param params - Query parameters for filtering fonts
|
||||
* @returns Promise resolving to Fontshare API response
|
||||
* @throws ApiError when request fails
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Fetch all Sans category fonts
|
||||
* const response = await fetchFontshareFonts({
|
||||
* categories: ['Sans'],
|
||||
* limit: 50
|
||||
* });
|
||||
*
|
||||
* // Fetch fonts with specific tags
|
||||
* const response = await fetchFontshareFonts({
|
||||
* tags: ['Branding', 'Logos']
|
||||
* });
|
||||
*
|
||||
* // Search fonts
|
||||
* const response = await fetchFontshareFonts({
|
||||
* search: 'Satoshi'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function fetchFontshareFonts(
|
||||
params: FontshareParams = {},
|
||||
): Promise<FontshareResponse> {
|
||||
const queryString = buildQueryString(params);
|
||||
const url = `https://api.fontshare.com/v2${queryString}`;
|
||||
|
||||
try {
|
||||
const response = await api.get<FontshareResponse>(url);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// Re-throw ApiError with context
|
||||
if (error instanceof Error) {
|
||||
throw error;
|
||||
}
|
||||
throw new Error(`Failed to fetch Fontshare fonts: ${String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch font by slug
|
||||
* Convenience function for fetching a single font
|
||||
*
|
||||
* @param slug - Font slug (e.g., "satoshi", "general-sans")
|
||||
* @returns Promise resolving to Fontshare font item
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const satoshi = await fetchFontshareFontBySlug('satoshi');
|
||||
* ```
|
||||
*/
|
||||
export async function fetchFontshareFontBySlug(
|
||||
slug: string,
|
||||
): Promise<FontshareFont | undefined> {
|
||||
const response = await fetchFontshareFonts();
|
||||
return response.items.find(font => font.slug === slug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all fonts from Fontshare
|
||||
* Convenience function for fetching all available fonts
|
||||
* Uses pagination to get all items
|
||||
*
|
||||
* @returns Promise resolving to all Fontshare fonts
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const allFonts = await fetchAllFontshareFonts();
|
||||
* console.log(`Found ${allFonts.items.length} fonts`);
|
||||
* ```
|
||||
*/
|
||||
export async function fetchAllFontshareFonts(
|
||||
params: FontshareParams = {},
|
||||
): Promise<FontshareResponse> {
|
||||
const allFonts: FontshareFont[] = [];
|
||||
let page = 1;
|
||||
const limit = 100; // Max items per page
|
||||
|
||||
while (true) {
|
||||
const response = await fetchFontshareFonts({
|
||||
...params,
|
||||
page,
|
||||
limit,
|
||||
});
|
||||
|
||||
allFonts.push(...response.items);
|
||||
|
||||
// Check if we've fetched all items
|
||||
if (response.items.length < limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
|
||||
// Return first response with all items combined
|
||||
const firstResponse = await fetchFontshareFonts({ ...params, page: 1, limit });
|
||||
|
||||
return {
|
||||
...firstResponse,
|
||||
items: allFonts,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user