👁️
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 258 lines 6.4 kB view raw
1/** 2 * Shared TanStack Query definitions 3 */ 4 5import { queryOptions } from "@tanstack/react-query"; 6import { getCardDataProvider } from "./card-data-provider"; 7import type { OracleId, ScryfallId, VolatileData } from "./scryfall-types"; 8import type { 9 Card, 10 PaginatedSearchResult, 11 SearchRestrictions, 12 SortOption, 13 UnifiedSearchResult, 14} from "./search-types"; 15 16/** 17 * Combine function for useQueries - converts query results into a Map. 18 * Returns undefined until all cards are loaded. 19 */ 20export function combineCardQueries( 21 results: Array<{ data?: Card | undefined }>, 22): Map<ScryfallId, Card> | undefined { 23 const map = new Map<ScryfallId, Card>(); 24 for (const result of results) { 25 if (result.data) { 26 map.set(result.data.id, result.data); 27 } 28 } 29 return results.every((r) => r.data) ? map : undefined; 30} 31 32/** 33 * Search cards by name with optional restrictions 34 */ 35export const searchCardsQueryOptions = ( 36 query: string, 37 restrictions?: SearchRestrictions, 38 maxResults = 50, 39) => 40 queryOptions({ 41 queryKey: ["cards", "search", query, restrictions, maxResults] as const, 42 queryFn: async (): Promise<{ cards: Card[]; totalCount: number }> => { 43 const provider = await getCardDataProvider(); 44 45 if (!query.trim()) { 46 // No search query - return empty results 47 return { cards: [], totalCount: 0 }; 48 } 49 50 // Search may not be available on all providers (e.g., server-side) 51 if (!provider.searchCards) { 52 return { cards: [], totalCount: 0 }; 53 } 54 55 const cards = await provider.searchCards(query, restrictions, maxResults); 56 const metadata = await provider.getMetadata(); 57 58 return { 59 cards, 60 totalCount: metadata.cardCount, 61 }; 62 }, 63 staleTime: 5 * 60 * 1000, // 5 minutes 64 }); 65 66/** 67 * Get a single card by ID 68 */ 69export const getCardByIdQueryOptions = (id: ScryfallId) => 70 queryOptions({ 71 queryKey: ["cards", "byId", id] as const, 72 queryFn: async () => { 73 const provider = await getCardDataProvider(); 74 return provider.getCardById(id); 75 }, 76 staleTime: Number.POSITIVE_INFINITY, 77 }); 78 79/** 80 * Get all printings for a card's oracle ID 81 */ 82export const getCardPrintingsQueryOptions = (oracleId: OracleId) => 83 queryOptions({ 84 queryKey: ["cards", "printings", oracleId] as const, 85 queryFn: async (): Promise<ScryfallId[]> => { 86 const provider = await getCardDataProvider(); 87 return provider.getPrintingsByOracleId(oracleId); 88 }, 89 staleTime: Number.POSITIVE_INFINITY, 90 }); 91 92/** 93 * Get cards metadata (version, count) 94 */ 95export const getCardsMetadataQueryOptions = () => 96 queryOptions({ 97 queryKey: ["cards", "metadata"] as const, 98 queryFn: async () => { 99 const provider = await getCardDataProvider(); 100 return provider.getMetadata(); 101 }, 102 staleTime: Number.POSITIVE_INFINITY, 103 }); 104 105/** 106 * Get canonical printing ID for an oracle ID 107 */ 108export const getCanonicalPrintingQueryOptions = (oracleId: OracleId) => 109 queryOptions({ 110 queryKey: ["cards", "canonical", oracleId] as const, 111 queryFn: async (): Promise<ScryfallId | null> => { 112 const provider = await getCardDataProvider(); 113 const result = await provider.getCanonicalPrinting(oracleId); 114 return result ?? null; 115 }, 116 staleTime: Number.POSITIVE_INFINITY, 117 }); 118 119export type SyntaxSearchResult = 120 | { ok: true; cards: Card[] } 121 | { ok: false; error: { message: string; start: number; end: number } }; 122 123/** 124 * Search cards using Scryfall-like syntax (e.g., "t:creature cmc<=3 s:lea") 125 */ 126export const syntaxSearchQueryOptions = (query: string, maxResults = 100) => 127 queryOptions({ 128 queryKey: ["cards", "syntaxSearch", query, maxResults] as const, 129 queryFn: async (): Promise<SyntaxSearchResult> => { 130 const provider = await getCardDataProvider(); 131 132 if (!query.trim()) { 133 return { ok: true, cards: [] }; 134 } 135 136 if (!provider.syntaxSearch) { 137 return { 138 ok: false, 139 error: { message: "Syntax search not available", start: 0, end: 0 }, 140 }; 141 } 142 143 return provider.syntaxSearch(query, maxResults); 144 }, 145 staleTime: 5 * 60 * 1000, 146 }); 147 148/** 149 * Get volatile data (prices, EDHREC rank) for a card 150 */ 151export const getVolatileDataQueryOptions = (id: ScryfallId) => 152 queryOptions({ 153 queryKey: ["cards", "volatile", id] as const, 154 queryFn: async (): Promise<VolatileData | null> => { 155 const provider = await getCardDataProvider(); 156 return provider.getVolatileData(id); 157 }, 158 staleTime: 5 * 60 * 1000, // 5 minutes - prices change frequently 159 }); 160 161export type { UnifiedSearchResult }; 162 163/** 164 * Unified search that automatically routes to fuzzy or syntax search 165 * based on query complexity. Returns mode indicator and description for syntax queries. 166 */ 167export const unifiedSearchQueryOptions = ( 168 query: string, 169 restrictions?: SearchRestrictions, 170 maxResults = 50, 171) => 172 queryOptions({ 173 queryKey: [ 174 "cards", 175 "unifiedSearch", 176 query, 177 restrictions, 178 maxResults, 179 ] as const, 180 queryFn: async (): Promise<UnifiedSearchResult> => { 181 const provider = await getCardDataProvider(); 182 183 if (!query.trim()) { 184 return { mode: "fuzzy", cards: [], description: null, error: null }; 185 } 186 187 if (!provider.unifiedSearch) { 188 return { 189 mode: "fuzzy", 190 cards: [], 191 description: null, 192 error: { message: "Unified search not available", start: 0, end: 0 }, 193 }; 194 } 195 196 return provider.unifiedSearch(query, restrictions, maxResults); 197 }, 198 staleTime: 5 * 60 * 1000, 199 }); 200 201export const PAGE_SIZE = 50; 202 203/** 204 * Single page query for visibility-based fetching. 205 * Used with useQueries to load pages based on scroll position. 206 */ 207export const searchPageQueryOptions = ( 208 query: string, 209 offset: number, 210 restrictions?: SearchRestrictions, 211 sort: SortOption[] = [{ field: "name", direction: "auto" }], 212) => 213 queryOptions({ 214 queryKey: [ 215 "cards", 216 "searchPage", 217 query, 218 offset, 219 restrictions, 220 sort, 221 ] as const, 222 queryFn: async (): Promise<PaginatedSearchResult> => { 223 const provider = await getCardDataProvider(); 224 225 if (!query.trim()) { 226 return { 227 mode: "fuzzy", 228 cards: [], 229 totalCount: 0, 230 description: null, 231 error: null, 232 }; 233 } 234 235 if (!provider.paginatedUnifiedSearch) { 236 return { 237 mode: "fuzzy", 238 cards: [], 239 totalCount: 0, 240 description: null, 241 error: { 242 message: "Paginated search not available", 243 start: 0, 244 end: 0, 245 }, 246 }; 247 } 248 249 return provider.paginatedUnifiedSearch( 250 query, 251 restrictions, 252 sort, 253 offset, 254 PAGE_SIZE, 255 ); 256 }, 257 staleTime: 5 * 60 * 1000, 258 });