/** * Lazy detail fetches. Only detail pages call these; the list, search and * game switching all run off the bundled snapshot. * * In-flight requests are de-duplicated in memory. Durable caching (across * reloads and offline) is the service worker's job — see src/sw.js, which * runs stale-while-revalidate over this origin. */ const BASE = 'https://pokeapi.co/api/v2'; const inflight = new Map(); export function getJSON(path) { if (inflight.has(path)) return inflight.get(path); const promise = fetch(`${BASE}/${path}`) .then((res) => { if (!res.ok) throw new Error(`${path} → ${res.status}`); return res.json(); }) .finally(() => inflight.delete(path)); inflight.set(path, promise); return promise; } export const getPokemon = (idOrName) => getJSON(`pokemon/${idOrName}`); export const getSpecies = (idOrName) => getJSON(`pokemon-species/${idOrName}`); export const getEvolutionChain = (id) => getJSON(`evolution-chain/${id}`); export const getMove = (idOrName) => getJSON(`move/${idOrName}`); export const getItem = (idOrName) => getJSON(`item/${idOrName}`); export const getEncounters = (id) => getJSON(`pokemon/${id}/encounters`); // Full name indexes for the lookup tabs — one request each, then the // service worker keeps them (stale-while-revalidate). export const getMoveIndex = () => getJSON('move?limit=2000'); export const getItemIndex = () => getJSON('item?limit=3000'); /** Run async `fn` over `items` with a bounded number of parallel workers. */ export async function mapLimit(items, limit, fn) { const out = new Array(items.length); let i = 0; await Promise.all( Array.from({ length: Math.min(limit, items.length) }, async () => { while (i < items.length) { const idx = i++; out[idx] = await fn(items[idx], idx); } }), ); return out; }