@sevn/reqcache 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/request-cache.ts"],"sourcesContent":["/**\n * request-cache\n * -------------\n * Cache de requisições HTTP em localStorage, pensado para cenários de\n * alto volume em curto período (ex.: apuração de eleições).\n *\n * Modelo de armazenamento:\n * Tudo fica sob UMA chave no storage, contendo um ARRAY de objetos.\n * Cada objeto é indexado pela `rota` (a URL requisitada):\n * [{ rota, data, createdAt, expiresAt, lastAccess }, ...]\n *\n * Comportamento:\n * 1. Primeira chamada -> faz o fetch e grava a entrada da rota no array.\n * 2. Durante o TTL -> devolve os dados do cache, sem tocar na rede.\n * 3. Após expirar -> SÓ revalida quando o client chamar de novo (lazy).\n * Se houver `monotonicKey`, só aceita o novo dado quando o valor numérico\n * dessa chave for MAIOR que o guardado; senão mantém o antigo.\n *\n * Proteção de espaço (limite de ~5 MB do localStorage):\n * - `maxEntries`: ao gravar, se passar do limite, remove a rota menos\n * recentemente usada (LRU). Roda sozinho, sem timer.\n * - Em erro de quota, remove as entradas mais antigas e tenta de novo.\n * - `cleanup()`: remove rotas expiradas há mais de `graceSeconds`.\n *\n * Extras para o caso de eleição:\n * - Deduplicação: chamadas simultâneas à mesma rota disparam 1 só fetch.\n * - staleOnError: se a rede/API falhar, devolve o último dado válido.\n */\n\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface GetFetchOptions {\n /**\n * Caminho da chave numérica monotônica. Suporta aninhamento com ponto.\n * Ex.: \"total\", \"resultado.votosApurados\", \"data.candidato.votos\".\n * Se o valor novo NÃO for maior que o antigo, mantém o dado em cache.\n */\n monotonicKey?: string;\n\n /** Opções nativas repassadas ao fetch (headers, method, body, signal...). */\n fetchOptions?: RequestInit;\n\n /**\n * Se true (padrão), quando a nova requisição falhar e existir dado antigo,\n * devolve esse dado antigo em vez de lançar erro.\n */\n staleOnError?: boolean;\n\n /** fetch customizado — útil para testes ou ambientes sem fetch global. */\n fetcher?: typeof fetch;\n\n /**\n * URLs alternativas (outros domínios/APIs) tentadas em ordem, caso `url`\n * falhe. A rota do cache continua sendo `url` — as alternativas só entram\n * na requisição, não criam entradas novas no cache.\n */\n fallbackUrls?: string[];\n\n /**\n * Chamado quando uma URL falha e a lib vai tentar a próxima da lista\n * (`url` + `fallbackUrls`). Útil para observabilidade/log.\n */\n onFallback?: (failedUrl: string, error: unknown) => void;\n}\n\n/** Uma entrada do cache. `rota` é a chave identificadora. */\nexport interface CacheEntry<T = unknown> {\n rota: string;\n data: T;\n createdAt: number;\n expiresAt: number;\n lastAccess: number;\n}\n\n/** Uma linha de log do modo debug. */\nexport interface DebugLogFn {\n (message: string, details?: Record<string, unknown>): void;\n}\n\nexport interface RequestCacheConfig {\n /** Storage a usar. Padrão: localStorage (se disponível). */\n storage?: StorageLike;\n /** Chave única onde o array é guardado. Padrão \"reqcache\". */\n storageKey?: string;\n /** Máximo de rotas em cache. Excedeu -> remove a menos usada (LRU). Padrão 100. */\n maxEntries?: number;\n\n /**\n * Se true, imprime logs detalhados de cada operação (fetch de rede,\n * cache hit, deduplicação, fallback, LRU, etc.). Padrão false.\n * Útil para identificar se estão sendo feitas mais chamadas de rede\n * do que o necessário. Pode ser ligado/desligado em runtime com\n * `setDebug()`.\n */\n debug?: boolean;\n\n /** Logger customizado usado quando `debug` está ligado. Padrão: `console.debug`. */\n logger?: DebugLogFn;\n}\n\nexport class RequestCache {\n private storage: StorageLike | null;\n private storageKey: string;\n private maxEntries: number;\n private inflight = new Map<string, Promise<unknown>>();\n /** Relógio de acesso monotônico: sempre cresce, mesmo com acessos no mesmo ms. */\n private tick = 0;\n private debug: boolean;\n private logger: DebugLogFn;\n\n constructor(config: RequestCacheConfig = {}) {\n this.storageKey = config.storageKey ?? \"reqcache\";\n this.maxEntries = config.maxEntries ?? 100;\n this.storage = config.storage ?? getDefaultStorage();\n this.debug = config.debug ?? false;\n this.logger = config.logger ?? defaultLogger;\n }\n\n /** Liga/desliga o modo debug em runtime, sem precisar recriar a instância. */\n setDebug(enabled: boolean): void {\n this.debug = enabled;\n }\n\n /**\n * Busca uma URL usando cache.\n * @param url Rota da requisição (também é a chave do cache).\n * @param ttlMs Tempo de cache em milissegundos (ex.: 10000).\n * @param options Regra monotônica, opções de fetch, etc.\n */\n async getFetch<T = unknown>(\n url: string,\n ttlMs: number,\n options: GetFetchOptions = {},\n ): Promise<T> {\n const now = Date.now();\n const all = this.readAll();\n const cached = all.find((e) => e.rota === url) as CacheEntry<T> | undefined;\n\n // 1. Cache ainda válido -> devolve sem tocar na rede.\n if (cached && now < cached.expiresAt) {\n cached.lastAccess = this.nextAccess(); // marca uso p/ o LRU\n this.writeAll(all);\n this.log(\"cache hit\", { url, expiresAt: new Date(cached.expiresAt).toISOString() });\n return cached.data;\n }\n\n // 2. Deduplicação: se já há uma requisição em andamento p/ essa rota,\n // todas as chamadas concorrentes aguardam a mesma Promise.\n const pending = this.inflight.get(url) as Promise<T> | undefined;\n if (pending) {\n this.log(\"dedup: aguardando requisição em andamento\", { url });\n return pending;\n }\n\n this.log(cached ? \"cache expirado, revalidando\" : \"cache miss\", { url });\n\n const promise = this.revalidate<T>(url, ttlMs, options, cached ?? null)\n .finally(() => this.inflight.delete(url));\n\n this.inflight.set(url, promise);\n return promise;\n }\n\n /** Remove uma rota específica do cache. */\n invalidate(url: string): void {\n const all = this.readAll().filter((e) => e.rota !== url);\n this.writeAll(all);\n }\n\n /**\n * Remove rotas expiradas há mais de `graceSeconds` (padrão 3600 = 1h).\n * Chame na inicialização do app, ou de tempos em tempos.\n * @returns quantidade de rotas removidas.\n */\n cleanup(graceSeconds = 3600): number {\n const limite = Date.now() - graceSeconds * 1000;\n const all = this.readAll();\n const mantidas = all.filter((e) => e.expiresAt > limite);\n this.writeAll(mantidas);\n const removidas = all.length - mantidas.length;\n if (removidas > 0) this.log(\"cleanup: rotas expiradas removidas\", { removidas });\n return removidas;\n }\n\n /** Esvazia todo o cache. */\n clear(): void {\n if (!this.storage) return;\n try {\n this.storage.removeItem(this.storageKey);\n } catch {\n /* storage indisponível */\n }\n }\n\n // --- interno ------------------------------------------------------------\n\n /** Retorna um número de acesso estritamente crescente (para o LRU). */\n private nextAccess(): number {\n this.tick = Math.max(Date.now(), this.tick + 1);\n return this.tick;\n }\n\n /** Emite uma linha de log, só quando o modo debug está ligado. */\n private log(message: string, details?: Record<string, unknown>): void {\n if (!this.debug) return;\n this.logger(message, details);\n }\n\n /**\n * Tenta cada URL da lista em ordem (primário, depois os `fallbackUrls`).\n * Devolve o JSON da primeira que responder OK; se todas falharem, lança o\n * último erro.\n */\n private async fetchComRedundancia<T>(\n candidatos: string[],\n fetcher: typeof fetch,\n options: GetFetchOptions,\n ): Promise<T> {\n let ultimoErro: unknown;\n\n for (const candidato of candidatos) {\n this.log(\"fetch de rede\", { url: candidato });\n try {\n const res = await fetcher(candidato, options.fetchOptions);\n if (!res.ok) throw new Error(`HTTP ${res.status} ao buscar ${candidato}`);\n this.log(\"fetch OK\", { url: candidato, status: res.status });\n return (await res.json()) as T;\n } catch (err) {\n this.log(\"fetch falhou\", { url: candidato, erro: String(err) });\n ultimoErro = err;\n options.onFallback?.(candidato, err);\n }\n }\n\n throw ultimoErro;\n }\n\n private async revalidate<T>(\n url: string,\n ttlMs: number,\n options: GetFetchOptions,\n cached: CacheEntry<T> | null,\n ): Promise<T> {\n const fetcher = options.fetcher ?? fetch;\n const candidatos = [url, ...(options.fallbackUrls ?? [])];\n let fresh: T;\n\n try {\n fresh = await this.fetchComRedundancia<T>(candidatos, fetcher, options);\n } catch (err) {\n // Todos os domínios falharam. Se temos dado antigo e staleOnError, devolve o antigo.\n if (cached && (options.staleOnError ?? true)) {\n this.log(\"staleOnError: devolvendo dado antigo\", { url });\n this.upsert({\n ...cached,\n expiresAt: Date.now() + ttlMs,\n lastAccess: this.nextAccess(),\n });\n return cached.data;\n }\n throw err;\n }\n\n // 3. Regra monotônica: só aceita o novo dado se a chave numérica CRESCEU.\n if (cached && options.monotonicKey) {\n const oldVal = getPath(cached.data, options.monotonicKey);\n const newVal = getPath(fresh, options.monotonicKey);\n const ambosNumeros =\n typeof oldVal === \"number\" && typeof newVal === \"number\";\n\n if (ambosNumeros && (newVal as number) <= (oldVal as number)) {\n // Valor não aumentou -> mantém o dado antigo, só renova a expiração.\n this.log(\"regra monotônica: valor não cresceu, mantendo dado antigo\", {\n url,\n oldVal,\n newVal,\n });\n this.upsert({\n ...cached,\n expiresAt: Date.now() + ttlMs,\n lastAccess: this.nextAccess(),\n });\n return cached.data;\n }\n }\n\n // 4. Grava e devolve o dado novo.\n const now = Date.now();\n this.log(\"cache gravado\", { url, ttlMs });\n this.upsert<T>({\n rota: url,\n data: fresh,\n createdAt: now,\n expiresAt: now + ttlMs,\n lastAccess: this.nextAccess(),\n });\n return fresh;\n }\n\n /** Insere ou atualiza uma rota, aplicando o limite LRU. */\n private upsert<T>(entry: CacheEntry<T>): void {\n const all = this.readAll().filter((e) => e.rota !== entry.rota);\n all.push(entry);\n this.evictIfNeeded(all);\n this.writeAll(all);\n }\n\n /** Enquanto passar de maxEntries, remove a rota menos recentemente usada. */\n private evictIfNeeded(all: CacheEntry[]): void {\n while (all.length > this.maxEntries) {\n let idxMaisAntigo = 0;\n for (let i = 1; i < all.length; i++) {\n if (all[i].lastAccess < all[idxMaisAntigo].lastAccess) idxMaisAntigo = i;\n }\n this.log(\"LRU: removendo rota menos usada\", { url: all[idxMaisAntigo].rota });\n all.splice(idxMaisAntigo, 1);\n }\n }\n\n private readAll(): CacheEntry[] {\n if (!this.storage) return [];\n try {\n const raw = this.storage.getItem(this.storageKey);\n if (!raw) return [];\n const parsed = JSON.parse(raw);\n return Array.isArray(parsed) ? (parsed as CacheEntry[]) : [];\n } catch {\n return []; // JSON corrompido -> trata como cache vazio\n }\n }\n\n private writeAll(all: CacheEntry[]): void {\n if (!this.storage) return;\n try {\n this.storage.setItem(this.storageKey, JSON.stringify(all));\n } catch (err) {\n // Quota estourada: remove os mais antigos e tenta de novo.\n if (isQuotaError(err) && all.length > 0) {\n const reduzido = [...all].sort((a, b) => a.lastAccess - b.lastAccess);\n reduzido.splice(0, Math.ceil(reduzido.length / 2)); // descarta metade\n this.log(\"quota excedida: descartando metade das entradas mais antigas\", {\n totalAntes: all.length,\n totalDepois: reduzido.length,\n });\n try {\n this.storage.setItem(this.storageKey, JSON.stringify(reduzido));\n } catch {\n /* ainda assim falhou -> desiste de persistir; dados já retornam */\n }\n }\n // Falha silenciosa: os dados ainda são devolvidos ao chamador.\n }\n }\n}\n\n/** Lê um caminho aninhado (\"a.b.c\") de um objeto de forma segura. */\nfunction getPath(obj: unknown, path: string): unknown {\n return path.split(\".\").reduce<unknown>((acc, k) => {\n if (acc && typeof acc === \"object\" && k in (acc as object)) {\n return (acc as Record<string, unknown>)[k];\n }\n return undefined;\n }, obj);\n}\n\nfunction isQuotaError(err: unknown): boolean {\n return (\n err instanceof Error &&\n (err.name === \"QuotaExceededError\" ||\n err.name === \"NS_ERROR_DOM_QUOTA_REACHED\")\n );\n}\n\n/** Logger padrão do modo debug: imprime no console com um prefixo fixo. */\nfunction defaultLogger(message: string, details?: Record<string, unknown>): void {\n if (details) {\n console.debug(`[reqcache] ${message}`, details);\n } else {\n console.debug(`[reqcache] ${message}`);\n }\n}\n\n/** Retorna localStorage se existir e funcionar; senão null (SSR, modo privado...). */\nfunction getDefaultStorage(): StorageLike | null {\n try {\n if (typeof localStorage !== \"undefined\") {\n const probe = \"__reqcache_probe__\";\n localStorage.setItem(probe, \"1\");\n localStorage.removeItem(probe);\n return localStorage;\n }\n } catch {\n /* Safari em modo privado antigo lança aqui */\n }\n return null;\n}\n\n/** Instância pronta para uso, caso não queira configurar nada. */\nexport const requestCache = new RequestCache();\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/request-cache.ts"],"sourcesContent":["/**\r\n * request-cache\r\n * -------------\r\n * Cache de requisições HTTP em localStorage, pensado para cenários de\r\n * alto volume em curto período (ex.: apuração de eleições).\r\n *\r\n * Modelo de armazenamento:\r\n * Tudo fica sob UMA chave no storage, contendo um ARRAY de objetos.\r\n * Cada objeto é indexado pela `rota` (a URL requisitada):\r\n * [{ rota, data, createdAt, expiresAt, lastAccess }, ...]\r\n *\r\n * Comportamento:\r\n * 1. Primeira chamada -> faz o fetch e grava a entrada da rota no array.\r\n * 2. Durante o TTL -> devolve os dados do cache, sem tocar na rede.\r\n * 3. Após expirar -> SÓ revalida quando o client chamar de novo (lazy).\r\n * Se houver `monotonicKey`, só aceita o novo dado quando TODAS as chaves\r\n * tiverem AVANÇADO; senão mantém o antigo.\r\n *\r\n * Proteção de espaço (limite de ~5 MB do localStorage):\r\n * - `maxEntries`: ao gravar, se passar do limite, remove a rota menos\r\n * recentemente usada (LRU). Roda sozinho, sem timer.\r\n * - Em erro de quota, remove as entradas mais antigas e tenta de novo.\r\n * - `cleanup()`: remove rotas expiradas há mais de `graceSeconds`.\r\n *\r\n * Extras para o caso de eleição:\r\n * - Deduplicação: chamadas simultâneas à mesma rota disparam 1 só fetch.\r\n * - staleOnError: se a rede/API falhar, devolve o último dado válido.\r\n */\r\n\r\nexport interface StorageLike {\r\n getItem(key: string): string | null;\r\n setItem(key: string, value: string): void;\r\n removeItem(key: string): void;\r\n}\r\n\r\n/**\r\n * Como comparar o valor de uma chave monotônica:\r\n * - `\"number\"` — numérica. Aceita `number` e string numérica (\"2232575810\").\r\n * - `\"date\"` — por timestamp. Aceita o que o `Date.parse` resolver\r\n * (\"2026-10-04T18:23:00Z\") e também epoch em ms.\r\n * - `\"string\"` — lexicográfica.\r\n */\r\nexport type MonotonicType = \"number\" | \"date\" | \"string\";\r\n\r\n/**\r\n * Chave(s) monotônica(s). Três formas:\r\n * - `\"summary.ballots_counted\"` — uma chave, tipo detectado pelo valor.\r\n * - `[\"idg\", \"summary.last_updated\"]` — várias, tipo detectado pelo valor.\r\n * - `{ idg: \"number\", \"summary.last_updated\": \"date\" }` — várias, com o tipo\r\n * declarado por nome de chave (recomendado: não depende de adivinhação).\r\n */\r\nexport type MonotonicKey = string | string[] | Record<string, MonotonicType>;\r\n\r\n/**\r\n * Qualidade de rede estimada pelo browser (Network Information API).\r\n * É uma ESTIMATIVA baseada em latência e throughput recentes, não o rádio do\r\n * aparelho: um 4g congestionado é reportado como \"2g\" — que é exatamente o\r\n * caso que interessa aqui.\r\n */\r\nexport type EffectiveConnectionType = \"slow-2g\" | \"2g\" | \"3g\" | \"4g\";\r\n\r\n/**\r\n * Timeout de UMA tentativa de fetch, em ms, por qualidade de rede.\r\n * `desconhecida` é o que vale em SSR e nos browsers sem a Network Information\r\n * API (Safari e Firefox).\r\n */\r\nexport type TimeoutTiers = Partial<\r\n Record<EffectiveConnectionType | \"desconhecida\", number>\r\n>;\r\n\r\n/**\r\n * Falha de HTTP com o status preservado. É o que permite decidir se vale a\r\n * pena tentar o próximo domínio (503, sim) ou não (404).\r\n */\r\nexport class HttpError extends Error {\r\n constructor(\r\n readonly status: number,\r\n readonly url: string,\r\n ) {\r\n super(`HTTP ${status} ao buscar ${url}`);\r\n this.name = \"HttpError\";\r\n }\r\n}\r\n\r\nexport interface GetFetchOptions {\r\n /**\r\n * Caminho(s) da chave monotônica. Suporta aninhamento com ponto.\r\n *\r\n * monotonicKey: \"resultado.votosApurados\"\r\n * monotonicKey: [\"idg\", \"summary.last_updated\"]\r\n * monotonicKey: { idg: \"number\", \"summary.last_updated\": \"date\" }\r\n *\r\n * Com mais de uma chave, TODAS são comparadas e o dado novo só é aceito\r\n * quando todas concordam que ele é mais recente — se qualquer uma vier\r\n * igual ou menor, a resposta é descartada e o dado em cache é mantido.\r\n * A comparação é sempre estrita: o valor novo precisa ser MAIOR que o\r\n * guardado, nunca igual.\r\n *\r\n * O tipo de cada chave é resolvido nesta ordem:\r\n * 1. declarado na forma de mapa;\r\n * 2. conhecido pelo NOME da chave (`idg` é número, `last_updated` é data —\r\n * veja `TIPOS_POR_NOME`, extensível via `monotonicTypes` na config);\r\n * 3. detectado pelo valor: número, string que converte para número finito,\r\n * string que o `Date.parse` resolve e, por fim, lexicográfica.\r\n *\r\n * TODAS as chaves precisam ter crescido. Uma chave incomparável (ausente de\r\n * um dos lados, ilegível ou fora do tipo esperado) conta como \"não cresceu\"\r\n * e derruba a resposta nova junto com as demais.\r\n */\r\n monotonicKey?: MonotonicKey;\r\n\r\n /** Opções nativas repassadas ao fetch (headers, method, body, signal...). */\r\n fetchOptions?: RequestInit;\r\n\r\n /**\r\n * Se true (padrão), quando a nova requisição falhar e existir dado antigo,\r\n * devolve esse dado antigo em vez de lançar erro.\r\n */\r\n staleOnError?: boolean;\r\n\r\n /** fetch customizado — útil para testes ou ambientes sem fetch global. */\r\n fetcher?: typeof fetch;\r\n\r\n /**\r\n * URLs alternativas (outros domínios/APIs) tentadas em ordem, caso `url`\r\n * falhe. A rota do cache continua sendo `url` — as alternativas só entram\r\n * na requisição, não criam entradas novas no cache.\r\n */\r\n fallbackUrls?: string[];\r\n\r\n /**\r\n * Chamado quando uma URL falha e a lib vai tentar a próxima da lista\r\n * (`url` + `fallbackUrls`). Útil para observabilidade/log.\r\n */\r\n onFallback?: (failedUrl: string, error: unknown) => void;\r\n\r\n /**\r\n * Timeout de CADA tentativa, em ms. Quando omitido, sai da tabela por rede\r\n * (`timeoutTiers` na config). Vale por candidato, não pela fila toda: com\r\n * dois `fallbackUrls`, o pior caso é 3x esse valor.\r\n */\r\n timeoutMs?: number;\r\n\r\n /**\r\n * Status HTTP que NÃO devem acionar o fallback. Padrão: `[404]`.\r\n *\r\n * Um 404 é uma resposta correta do servidor — \"esse recurso não existe\" —,\r\n * não uma indisponibilidade. Repetir o mesmo caminho em outro domínio tende\r\n * a devolver o mesmo 404, só que N vezes mais devagar, então o erro sobe na\r\n * hora. Passe `[]` para voltar ao comportamento antigo (tentar todos).\r\n */\r\n statusSemFallback?: number[];\r\n}\r\n\r\n/** Uma entrada do cache. `rota` é a chave identificadora. */\r\nexport interface CacheEntry<T = unknown> {\r\n rota: string;\r\n data: T;\r\n createdAt: number;\r\n expiresAt: number;\r\n lastAccess: number;\r\n}\r\n\r\n/** Uma linha de log do modo debug. */\r\nexport interface DebugLogFn {\r\n (message: string, details?: Record<string, unknown>): void;\r\n}\r\n\r\nexport interface RequestCacheConfig {\r\n /** Storage a usar. Padrão: localStorage (se disponível). */\r\n storage?: StorageLike;\r\n /** Chave única onde o array é guardado. Padrão \"reqcache\". */\r\n storageKey?: string;\r\n /** Máximo de rotas em cache. Excedeu -> remove a menos usada (LRU). Padrão 100. */\r\n maxEntries?: number;\r\n\r\n /**\r\n * Se true, imprime logs detalhados de cada operação (fetch de rede,\r\n * cache hit, deduplicação, fallback, LRU, etc.). Padrão false.\r\n * Útil para identificar se estão sendo feitas mais chamadas de rede\r\n * do que o necessário. Pode ser ligado/desligado em runtime com\r\n * `setDebug()`.\r\n */\r\n debug?: boolean;\r\n\r\n /** Logger customizado usado quando `debug` está ligado. Padrão: `console.debug`. */\r\n logger?: DebugLogFn;\r\n\r\n /**\r\n * Tipos monotônicos adicionais, por nome de chave. Somado (e com prioridade\r\n * sobre) a tabela embutida `TIPOS_POR_NOME`, para que o chamador possa\r\n * continuar passando `monotonicKey` como lista de caminhos e ainda assim ter\r\n * a comparação certa:\r\n *\r\n * new RequestCache({ monotonicTypes: { data_apuracao: \"date\" } })\r\n * ...\r\n * getFetch(url, 10_000, { monotonicKey: [\"idg\", \"data_apuracao\"] })\r\n *\r\n * Aceita o caminho completo (\"summary.last_updated\") ou só o último trecho\r\n * (\"last_updated\").\r\n */\r\n monotonicTypes?: Record<string, MonotonicType>;\r\n\r\n /**\r\n * Timeout de cada tentativa de fetch, por qualidade de rede do client.\r\n * O que for passado aqui é mesclado sobre `TIMEOUTS_POR_REDE`:\r\n *\r\n * new RequestCache({ timeoutTiers: { \"slow-2g\": 120_000 } })\r\n *\r\n * Um `timeoutMs` na chamada tem prioridade sobre esta tabela.\r\n */\r\n timeoutTiers?: TimeoutTiers;\r\n}\r\n\r\nexport class RequestCache {\r\n private storage: StorageLike | null;\r\n private storageKey: string;\r\n private maxEntries: number;\r\n private inflight = new Map<string, Promise<unknown>>();\r\n /** Relógio de acesso monotônico: sempre cresce, mesmo com acessos no mesmo ms. */\r\n private tick = 0;\r\n private debug: boolean;\r\n private logger: DebugLogFn;\r\n private monotonicTypes: Record<string, MonotonicType>;\r\n private timeoutTiers: Record<EffectiveConnectionType | \"desconhecida\", number>;\r\n\r\n constructor(config: RequestCacheConfig = {}) {\r\n this.storageKey = config.storageKey ?? \"reqcache\";\r\n this.maxEntries = config.maxEntries ?? 100;\r\n this.storage = config.storage ?? getDefaultStorage();\r\n this.debug = config.debug ?? false;\r\n this.logger = config.logger ?? defaultLogger;\r\n this.monotonicTypes = { ...TIPOS_POR_NOME, ...(config.monotonicTypes ?? {}) };\r\n this.timeoutTiers = { ...TIMEOUTS_POR_REDE, ...(config.timeoutTiers ?? {}) };\r\n }\r\n\r\n /** Liga/desliga o modo debug em runtime, sem precisar recriar a instância. */\r\n setDebug(enabled: boolean): void {\r\n this.debug = enabled;\r\n }\r\n\r\n /**\r\n * Busca uma URL usando cache.\r\n * @param url Rota da requisição (também é a chave do cache).\r\n * @param ttlMs Tempo de cache em milissegundos (ex.: 10000).\r\n * @param options Regra monotônica, opções de fetch, etc.\r\n */\r\n async getFetch<T = unknown>(\r\n url: string,\r\n ttlMs: number,\r\n options: GetFetchOptions = {},\r\n ): Promise<T> {\r\n const now = Date.now();\r\n const all = this.readAll();\r\n const cached = all.find((e) => e.rota === url) as CacheEntry<T> | undefined;\r\n\r\n // 1. Cache ainda válido -> devolve sem tocar na rede.\r\n if (cached && now < cached.expiresAt) {\r\n cached.lastAccess = this.nextAccess(); // marca uso p/ o LRU\r\n this.writeAll(all);\r\n this.log(\"cache hit\", { url, expiresAt: new Date(cached.expiresAt).toISOString() });\r\n return cached.data;\r\n }\r\n\r\n // 2. Deduplicação: se já há uma requisição em andamento p/ essa rota,\r\n // todas as chamadas concorrentes aguardam a mesma Promise.\r\n const pending = this.inflight.get(url) as Promise<T> | undefined;\r\n if (pending) {\r\n this.log(\"dedup: aguardando requisição em andamento\", { url });\r\n return pending;\r\n }\r\n\r\n this.log(cached ? \"cache expirado, revalidando\" : \"cache miss\", { url });\r\n\r\n const promise = this.revalidate<T>(url, ttlMs, options, cached ?? null)\r\n .finally(() => this.inflight.delete(url));\r\n\r\n this.inflight.set(url, promise);\r\n return promise;\r\n }\r\n\r\n /** Remove uma rota específica do cache. */\r\n invalidate(url: string): void {\r\n const all = this.readAll().filter((e) => e.rota !== url);\r\n this.writeAll(all);\r\n }\r\n\r\n /**\r\n * Remove rotas expiradas há mais de `graceSeconds` (padrão 3600 = 1h).\r\n * Chame na inicialização do app, ou de tempos em tempos.\r\n * @returns quantidade de rotas removidas.\r\n */\r\n cleanup(graceSeconds = 3600): number {\r\n const limite = Date.now() - graceSeconds * 1000;\r\n const all = this.readAll();\r\n const mantidas = all.filter((e) => e.expiresAt > limite);\r\n this.writeAll(mantidas);\r\n const removidas = all.length - mantidas.length;\r\n if (removidas > 0) this.log(\"cleanup: rotas expiradas removidas\", { removidas });\r\n return removidas;\r\n }\r\n\r\n /** Esvazia todo o cache. */\r\n clear(): void {\r\n if (!this.storage) return;\r\n try {\r\n this.storage.removeItem(this.storageKey);\r\n } catch {\r\n /* storage indisponível */\r\n }\r\n }\r\n\r\n // --- interno ------------------------------------------------------------\r\n\r\n /** Retorna um número de acesso estritamente crescente (para o LRU). */\r\n private nextAccess(): number {\r\n this.tick = Math.max(Date.now(), this.tick + 1);\r\n return this.tick;\r\n }\r\n\r\n /** Emite uma linha de log, só quando o modo debug está ligado. */\r\n private log(message: string, details?: Record<string, unknown>): void {\r\n if (!this.debug) return;\r\n this.logger(message, details);\r\n }\r\n\r\n /**\r\n * Timeout de UMA tentativa, em ms. A ordem de prioridade é:\r\n * 1. `timeoutMs` passado na chamada;\r\n * 2. o tier da rede atual do client (`navigator.connection`);\r\n * 3. o tier `desconhecida`, usado em SSR, Safari e Firefox.\r\n *\r\n * A rede é relida a cada tentativa de propósito: numa apuração o client pode\r\n * sair do wi-fi e cair no 3g no meio da fila de fallback.\r\n */\r\n private resolverTimeout(options: GetFetchOptions): number {\r\n if (typeof options.timeoutMs === \"number\") return options.timeoutMs;\r\n return this.timeoutTiers[detectarRede() ?? \"desconhecida\"];\r\n }\r\n\r\n /**\r\n * Tenta cada URL da lista em ordem (primário, depois os `fallbackUrls`).\r\n * Devolve o JSON da primeira que responder OK; se todas falharem, lança o\r\n * último erro.\r\n *\r\n * Duas situações encerram a fila antes do fim, em vez de seguir para o\r\n * próximo candidato:\r\n * - um status listado em `statusSemFallback` (padrão: 404). É resposta do\r\n * servidor, não indisponibilidade — o outro domínio diria o mesmo.\r\n * - o `signal` do chamador abortado. Foi cancelamento de quem pediu;\r\n * insistir só trocaria o erro real pelo AbortError do último candidato.\r\n */\r\n private async fetchComRedundancia<T>(\r\n candidatos: string[],\r\n fetcher: typeof fetch,\r\n options: GetFetchOptions,\r\n ): Promise<T> {\r\n const semFallback = new Set(\r\n options.statusSemFallback ?? STATUS_SEM_FALLBACK_PADRAO,\r\n );\r\n const externo = options.fetchOptions?.signal ?? null;\r\n let ultimoErro: unknown;\r\n\r\n for (const candidato of candidatos) {\r\n if (externo?.aborted) {\r\n this.log(\"cancelado pelo chamador: fila interrompida\", { url: candidato });\r\n throw externo.reason ?? new Error(\"Requisição cancelada pelo chamador\");\r\n }\r\n\r\n const timeoutMs = this.resolverTimeout(options);\r\n const tentativa = criarSinal(timeoutMs, externo);\r\n this.log(\"fetch de rede\", { url: candidato, timeoutMs, rede: detectarRede() });\r\n\r\n try {\r\n const res = await fetcher(candidato, {\r\n ...options.fetchOptions,\r\n signal: tentativa.signal,\r\n });\r\n if (!res.ok) throw new HttpError(res.status, candidato);\r\n const json = (await res.json()) as T;\r\n this.log(\"fetch OK\", { url: candidato, status: res.status });\r\n return json;\r\n } catch (bruto) {\r\n // Um abort disparado pelo nosso timer chega aqui como AbortError\r\n // genérico; troca por um erro que diz o que de fato aconteceu.\r\n const err = tentativa.estourou()\r\n ? erroDeTimeout(candidato, timeoutMs)\r\n : bruto;\r\n\r\n this.log(\"fetch falhou\", { url: candidato, erro: String(err) });\r\n ultimoErro = err;\r\n options.onFallback?.(candidato, err);\r\n\r\n if (err instanceof HttpError && semFallback.has(err.status)) {\r\n this.log(\"status sem fallback: fila interrompida\", {\r\n url: candidato,\r\n status: err.status,\r\n });\r\n throw err;\r\n }\r\n } finally {\r\n // Obrigatório: sem isso sobra um timer de minutos pendurado por\r\n // tentativa, segurando o event loop no Node e vazando no browser.\r\n tentativa.cancelar();\r\n }\r\n }\r\n\r\n throw ultimoErro;\r\n }\r\n\r\n private async revalidate<T>(\r\n url: string,\r\n ttlMs: number,\r\n options: GetFetchOptions,\r\n cached: CacheEntry<T> | null,\r\n ): Promise<T> {\r\n const fetcher = options.fetcher ?? fetch;\r\n const candidatos = [url, ...(options.fallbackUrls ?? [])];\r\n let fresh: T;\r\n\r\n try {\r\n fresh = await this.fetchComRedundancia<T>(candidatos, fetcher, options);\r\n } catch (err) {\r\n // Todos os domínios falharam. Se temos dado antigo e staleOnError, devolve o antigo.\r\n if (cached && (options.staleOnError ?? true)) {\r\n this.log(\"staleOnError: devolvendo dado antigo\", { url });\r\n this.upsert({\r\n ...cached,\r\n expiresAt: Date.now() + ttlMs,\r\n lastAccess: this.nextAccess(),\r\n });\r\n return cached.data;\r\n }\r\n throw err;\r\n }\r\n\r\n // 3. Regra monotônica: só aceita o novo dado se TODAS as chaves que\r\n // conseguiram votar tiverem avançado.\r\n if (cached && options.monotonicKey) {\r\n const veredito = avaliarMonotonicidade(\r\n cached.data,\r\n fresh,\r\n options.monotonicKey,\r\n this.monotonicTypes,\r\n );\r\n\r\n if (!veredito.avancou) {\r\n // Alguma chave não avançou -> mantém o dado antigo, só renova a expiração.\r\n this.log(\"regra monotônica: valor não cresceu, mantendo dado antigo\", {\r\n url,\r\n chaves: veredito.detalhes,\r\n });\r\n this.upsert({\r\n ...cached,\r\n expiresAt: Date.now() + ttlMs,\r\n lastAccess: this.nextAccess(),\r\n });\r\n return cached.data;\r\n }\r\n }\r\n\r\n // 4. Grava e devolve o dado novo.\r\n const now = Date.now();\r\n this.log(\"cache gravado\", { url, ttlMs });\r\n this.upsert<T>({\r\n rota: url,\r\n data: fresh,\r\n createdAt: now,\r\n expiresAt: now + ttlMs,\r\n lastAccess: this.nextAccess(),\r\n });\r\n return fresh;\r\n }\r\n\r\n /** Insere ou atualiza uma rota, aplicando o limite LRU. */\r\n private upsert<T>(entry: CacheEntry<T>): void {\r\n const all = this.readAll().filter((e) => e.rota !== entry.rota);\r\n all.push(entry);\r\n this.evictIfNeeded(all);\r\n this.writeAll(all);\r\n }\r\n\r\n /** Enquanto passar de maxEntries, remove a rota menos recentemente usada. */\r\n private evictIfNeeded(all: CacheEntry[]): void {\r\n while (all.length > this.maxEntries) {\r\n let idxMaisAntigo = 0;\r\n for (let i = 1; i < all.length; i++) {\r\n if (all[i].lastAccess < all[idxMaisAntigo].lastAccess) idxMaisAntigo = i;\r\n }\r\n this.log(\"LRU: removendo rota menos usada\", { url: all[idxMaisAntigo].rota });\r\n all.splice(idxMaisAntigo, 1);\r\n }\r\n }\r\n\r\n private readAll(): CacheEntry[] {\r\n if (!this.storage) return [];\r\n try {\r\n const raw = this.storage.getItem(this.storageKey);\r\n if (!raw) return [];\r\n const parsed = JSON.parse(raw);\r\n return Array.isArray(parsed) ? (parsed as CacheEntry[]) : [];\r\n } catch {\r\n return []; // JSON corrompido -> trata como cache vazio\r\n }\r\n }\r\n\r\n private writeAll(all: CacheEntry[]): void {\r\n if (!this.storage) return;\r\n try {\r\n this.storage.setItem(this.storageKey, JSON.stringify(all));\r\n } catch (err) {\r\n // Quota estourada: remove os mais antigos e tenta de novo.\r\n if (isQuotaError(err) && all.length > 0) {\r\n const reduzido = [...all].sort((a, b) => a.lastAccess - b.lastAccess);\r\n reduzido.splice(0, Math.ceil(reduzido.length / 2)); // descarta metade\r\n this.log(\"quota excedida: descartando metade das entradas mais antigas\", {\r\n totalAntes: all.length,\r\n totalDepois: reduzido.length,\r\n });\r\n try {\r\n this.storage.setItem(this.storageKey, JSON.stringify(reduzido));\r\n } catch {\r\n /* ainda assim falhou -> desiste de persistir; dados já retornam */\r\n }\r\n }\r\n // Falha silenciosa: os dados ainda são devolvidos ao chamador.\r\n }\r\n }\r\n}\r\n\r\n/** Lê um caminho aninhado (\"a.b.c\") de um objeto de forma segura. */\r\nfunction getPath(obj: unknown, path: string): unknown {\r\n return path.split(\".\").reduce<unknown>((acc, k) => {\r\n if (acc && typeof acc === \"object\" && k in (acc as object)) {\r\n return (acc as Record<string, unknown>)[k];\r\n }\r\n return undefined;\r\n }, obj);\r\n}\r\n\r\n/** Como um valor deve ser comparado, decidido pelo próprio valor. */\r\ntype TipoComparacao = \"number\" | \"date\" | \"string\";\r\n\r\n/** Um valor já classificado, pronto para ser comparado com outro do mesmo tipo. */\r\ninterface ValorComparavel {\r\n tipo: TipoComparacao;\r\n ordem: number | string;\r\n}\r\n\r\n/**\r\n * Descobre como comparar um valor. A ordem das checagens NÃO pode ser trocada:\r\n * `Date.parse` aceita strings numéricas curtas como data (`Date.parse(\"9\")` cai\r\n * em setembro, `Date.parse(\"10\")` em outubro), então um `idg` curto viraria\r\n * data se a checagem de data viesse antes da numérica. No sentido inverso não\r\n * há risco: `Number(\"2026-10-04T18:23:00Z\")` é NaN.\r\n *\r\n * Devolve `null` quando o valor é incomparável.\r\n */\r\nfunction classificar(valor: unknown): ValorComparavel | null {\r\n if (typeof valor === \"number\") {\r\n return Number.isFinite(valor) ? { tipo: \"number\", ordem: valor } : null;\r\n }\r\n if (typeof valor !== \"string\") return null;\r\n\r\n // String vazia ou em branco é incomparável: `Number(\"\")` e `Number(\" \")`\r\n // devolvem 0, e um campo vazio viraria um \"zero\" comparável.\r\n if (valor.trim() === \"\") return null;\r\n\r\n const numero = Number(valor);\r\n if (Number.isFinite(numero)) return { tipo: \"number\", ordem: numero };\r\n\r\n const data = Date.parse(valor);\r\n if (!Number.isNaN(data)) return { tipo: \"date\", ordem: data };\r\n\r\n return { tipo: \"string\", ordem: valor };\r\n}\r\n\r\n/**\r\n * Classifica um valor segundo um tipo DECLARADO pelo chamador (forma de mapa).\r\n * Como o tipo veio declarado, não há adivinhação: o valor ou serve, ou a chave\r\n * é incomparável (e derruba a resposta nova). É também mais tolerante que a\r\n * detecção por valor — com\r\n * `\"number\"`, `100` e `\"101\"` comparam entre si sem problema, já que o\r\n * chamador afirmou que aquele campo é numérico.\r\n */\r\nfunction classificarComTipo(\r\n valor: unknown,\r\n tipo: TipoComparacao,\r\n): ValorComparavel | null {\r\n // String vazia ou em branco nunca é comparável: `Number(\"\")` e `Number(\" \")`\r\n // devolvem 0, e um campo vazio viraria um \"zero\" comparável.\r\n const texto = typeof valor === \"string\" ? valor.trim() : null;\r\n if (texto === \"\") return null;\r\n\r\n if (tipo === \"number\") {\r\n if (typeof valor === \"number\") {\r\n return Number.isFinite(valor) ? { tipo, ordem: valor } : null;\r\n }\r\n if (texto === null) return null;\r\n const numero = Number(texto);\r\n return Number.isFinite(numero) ? { tipo, ordem: numero } : null;\r\n }\r\n\r\n if (tipo === \"date\") {\r\n // Um número aqui é lido como epoch em ms.\r\n if (typeof valor === \"number\") {\r\n return Number.isFinite(valor) ? { tipo, ordem: valor } : null;\r\n }\r\n if (texto === null) return null;\r\n const data = Date.parse(texto);\r\n return Number.isNaN(data) ? null : { tipo, ordem: data };\r\n }\r\n\r\n return texto === null ? null : { tipo, ordem: texto };\r\n}\r\n\r\n/**\r\n * Tipos conhecidos por NOME de chave. É o que permite ao front continuar\r\n * passando só os caminhos — `[\"idg\", \"summary.last_updated\"]` — e ainda assim\r\n * ter `idg` comparado como número e `last_updated` como data, sem depender de\r\n * adivinhação pelo valor.\r\n *\r\n * A busca é feita pelo caminho completo e, se não achar, pelo último trecho\r\n * dele: \"summary.last_updated\" cai em \"last_updated\". Nomes fora desta tabela\r\n * (e de `monotonicTypes`) continuam sendo detectados pelo valor.\r\n */\r\nconst TIPOS_POR_NOME: Record<string, MonotonicType> = {\r\n idg: \"number\",\r\n versao: \"number\",\r\n ballots_counted: \"number\",\r\n last_updated: \"date\",\r\n};\r\n\r\n/** De onde veio o tipo usado para comparar uma chave. */\r\ntype OrigemDoTipo = \"declarado\" | \"conhecido\" | \"detectado\";\r\n\r\n/** Uma chave já normalizada: o caminho, o tipo dela e de onde o tipo veio. */\r\ninterface ChaveMonotonica {\r\n chave: string;\r\n /** null -> detectar pelo valor. */\r\n tipo: TipoComparacao | null;\r\n origem: OrigemDoTipo;\r\n}\r\n\r\n/**\r\n * Procura o tipo de uma chave na tabela de nomes: primeiro pelo caminho\r\n * completo (\"summary.last_updated\"), depois só pelo último trecho\r\n * (\"last_updated\").\r\n */\r\nfunction tipoConhecido(\r\n chave: string,\r\n tabela: Record<string, MonotonicType>,\r\n): MonotonicType | null {\r\n if (chave in tabela) return tabela[chave];\r\n const ultimo = chave.slice(chave.lastIndexOf(\".\") + 1);\r\n return ultimo in tabela ? tabela[ultimo] : null;\r\n}\r\n\r\n/**\r\n * Reduz as três formas aceitas de `monotonicKey` a uma lista única, já com o\r\n * tipo de cada chave resolvido: o declarado no mapa vence; senão vale o que a\r\n * tabela de nomes souber; senão fica `null` e o tipo é detectado pelo valor.\r\n */\r\nfunction normalizarChaves(\r\n monotonicKey: MonotonicKey,\r\n tabela: Record<string, MonotonicType>,\r\n): ChaveMonotonica[] {\r\n const semTipo = (chave: string): ChaveMonotonica => {\r\n const conhecido = tipoConhecido(chave, tabela);\r\n return conhecido\r\n ? { chave, tipo: conhecido, origem: \"conhecido\" }\r\n : { chave, tipo: null, origem: \"detectado\" };\r\n };\r\n\r\n if (typeof monotonicKey === \"string\") return [semTipo(monotonicKey)];\r\n if (Array.isArray(monotonicKey)) return monotonicKey.map(semTipo);\r\n\r\n return Object.entries(monotonicKey).map(([chave, tipo]) => ({\r\n chave,\r\n tipo,\r\n origem: \"declarado\" as const,\r\n }));\r\n}\r\n\r\n/** `a > b`, já sabendo que os dois lados são do mesmo tipo. */\r\nfunction maior(a: number | string, b: number | string): boolean {\r\n return typeof a === \"string\" ? a > String(b) : a > Number(b);\r\n}\r\n\r\n/** O que uma chave decidiu ao comparar a versão antiga com a nova. */\r\ninterface VotoMonotonico {\r\n chave: string;\r\n /** Tipo usado na comparação; `null` quando a chave é incomparável. */\r\n tipo: TipoComparacao | null;\r\n /** Se o tipo veio do mapa, da tabela de nomes ou da detecção pelo valor. */\r\n origem: OrigemDoTipo;\r\n oldVal: unknown;\r\n newVal: unknown;\r\n /** Qualquer resultado diferente de \"avançou\" derruba a resposta nova. */\r\n voto: \"avançou\" | \"não avançou\" | \"incomparável\";\r\n}\r\n\r\ninterface VereditoMonotonico {\r\n /** true -> aceitar o dado novo; false -> manter o que está em cache. */\r\n avancou: boolean;\r\n detalhes: VotoMonotonico[];\r\n}\r\n\r\n/**\r\n * Compara todas as chaves monotônicas entre o dado em cache e o dado novo.\r\n *\r\n * Verificação estrita: o dado novo só entra quando TODAS as chaves cresceram.\r\n * Basta uma que não cresça para a resposta ser descartada — e uma chave\r\n * incomparável (ausente de um dos lados, ilegível, fora do tipo esperado ou\r\n * com tipos divergentes entre as versões) também não cresceu, então também\r\n * derruba a resposta. Só entra o que comprovadamente cresceu em todas.\r\n */\r\nfunction avaliarMonotonicidade(\r\n oldData: unknown,\r\n newData: unknown,\r\n monotonicKey: MonotonicKey,\r\n tabela: Record<string, MonotonicType>,\r\n): VereditoMonotonico {\r\n const detalhes: VotoMonotonico[] = [];\r\n // Sem nenhuma chave configurada não há regra a aplicar, e o dado novo passa.\r\n let todasCresceram = true;\r\n\r\n // Avalia TODAS as chaves: nenhuma decide sozinha e nenhuma interrompe o laço,\r\n // para que o log de debug mostre o estado de cada uma.\r\n for (const { chave, tipo, origem } of normalizarChaves(monotonicKey, tabela)) {\r\n const oldVal = getPath(oldData, chave);\r\n const newVal = getPath(newData, chave);\r\n\r\n const oldCmp = tipo ? classificarComTipo(oldVal, tipo) : classificar(oldVal);\r\n const newCmp = tipo ? classificarComTipo(newVal, tipo) : classificar(newVal);\r\n\r\n // Quando o tipo é conhecido, ele é quem manda — `100` e `\"101\"` sob\r\n // \"number\" são o mesmo campo. Quando foi detectado pelo valor, tipos\r\n // divergentes entre as versões (número de um lado, string do outro; string\r\n // numérica vs. string de data) são sinal de que o campo mudou de forma.\r\n const divergente =\r\n tipo === null &&\r\n (typeof oldVal !== typeof newVal || oldCmp?.tipo !== newCmp?.tipo);\r\n\r\n // Incomparável não é \"neutro\": se a lib não consegue afirmar que a chave\r\n // cresceu, ela não cresceu, e a resposta nova cai.\r\n if (oldCmp === null || newCmp === null || divergente) {\r\n todasCresceram = false;\r\n detalhes.push({ chave, tipo: null, origem, oldVal, newVal, voto: \"incomparável\" });\r\n continue;\r\n }\r\n\r\n const avancou = maior(newCmp.ordem, oldCmp.ordem);\r\n if (!avancou) todasCresceram = false;\r\n detalhes.push({\r\n chave,\r\n tipo: newCmp.tipo,\r\n origem,\r\n oldVal,\r\n newVal,\r\n voto: avancou ? \"avançou\" : \"não avançou\",\r\n });\r\n }\r\n\r\n return { avancou: todasCresceram, detalhes };\r\n}\r\n\r\n/** Status que, por padrão, NÃO acionam o fallback: o servidor já respondeu. */\r\nconst STATUS_SEM_FALLBACK_PADRAO = [404];\r\n\r\n/**\r\n * Tier list de timeout por qualidade de rede, em ms, aplicado a CADA tentativa.\r\n *\r\n * 4g / desconhecida 30 s — base\r\n * 3g 45 s — 1,5x\r\n * 2g 60 s — 2x\r\n * slow-2g 90 s — 3x; borda de cobertura, interior\r\n *\r\n * A folga cresce com a rede ruim para não matar uma resposta que ainda viria\r\n * num client lento. O custo aparece em `fallbackUrls`, porque a fila é\r\n * sequencial: o pior caso é N x o tier — veja `timeoutMs` em GetFetchOptions.\r\n */\r\nexport const TIMEOUTS_POR_REDE: Record<EffectiveConnectionType | \"desconhecida\", number> = {\r\n \"slow-2g\": 90_000,\r\n \"2g\": 60_000,\r\n \"3g\": 45_000,\r\n \"4g\": 30_000,\r\n desconhecida: 30_000,\r\n};\r\n\r\nconst REDES_CONHECIDAS: EffectiveConnectionType[] = [\"slow-2g\", \"2g\", \"3g\", \"4g\"];\r\n\r\n/**\r\n * Lê `navigator.connection.effectiveType` (Network Information API).\r\n * Só Chromium e Android implementam; Safari, Firefox e SSR devolvem `null` e\r\n * caem no tier `desconhecida`.\r\n */\r\nfunction detectarRede(): EffectiveConnectionType | null {\r\n try {\r\n if (typeof navigator === \"undefined\") return null;\r\n const nav = navigator as Navigator & {\r\n connection?: { effectiveType?: string };\r\n mozConnection?: { effectiveType?: string };\r\n webkitConnection?: { effectiveType?: string };\r\n };\r\n const conexao = nav.connection ?? nav.mozConnection ?? nav.webkitConnection;\r\n const tipo = conexao?.effectiveType;\r\n if (!tipo) return null;\r\n return REDES_CONHECIDAS.indexOf(tipo as EffectiveConnectionType) >= 0\r\n ? (tipo as EffectiveConnectionType)\r\n : null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/** Erro dedicado do timeout, para não subir como um `AbortError` sem contexto. */\r\nfunction erroDeTimeout(url: string, timeoutMs: number): Error {\r\n const err = new Error(`Timeout de ${timeoutMs}ms ao buscar ${url}`);\r\n err.name = \"TimeoutError\";\r\n return err;\r\n}\r\n\r\n/** O sinal de uma tentativa, junto com o que é preciso para limpá-lo depois. */\r\ninterface SinalDaTentativa {\r\n signal: AbortSignal | undefined;\r\n /** true quando quem abortou foi o timeout, e não o chamador. */\r\n estourou(): boolean;\r\n /** Limpa timer e listener. Sempre chamar no `finally`. */\r\n cancelar(): void;\r\n}\r\n\r\n/**\r\n * Monta o `signal` de UMA tentativa: o timeout da vez encadeado com o `signal`\r\n * que o chamador passou em `fetchOptions`, para que cancelar por fora continue\r\n * funcionando.\r\n *\r\n * Feito na mão com `AbortController` em vez de `AbortSignal.any` +\r\n * `AbortSignal.timeout`, que só existem em Safari 17.4+ e Node 20+. Também\r\n * evita o problema de um `AbortSignal.timeout` passado pelo chamador: o\r\n * relógio dele começa na criação, então viraria um orçamento da fila inteira,\r\n * deixando os fallbacks sem tempo nenhum.\r\n */\r\nfunction criarSinal(\r\n timeoutMs: number,\r\n externo: AbortSignal | null,\r\n): SinalDaTentativa {\r\n if (!(timeoutMs > 0) || typeof AbortController === \"undefined\") {\r\n return {\r\n signal: externo ?? undefined,\r\n estourou: () => false,\r\n cancelar: () => {},\r\n };\r\n }\r\n\r\n const ctrl = new AbortController();\r\n let porTimeout = false;\r\n\r\n const timer = setTimeout(() => {\r\n porTimeout = true;\r\n ctrl.abort();\r\n }, timeoutMs);\r\n\r\n const repassar = () => ctrl.abort();\r\n if (externo) {\r\n if (externo.aborted) ctrl.abort();\r\n else externo.addEventListener(\"abort\", repassar);\r\n }\r\n\r\n return {\r\n signal: ctrl.signal,\r\n estourou: () => porTimeout,\r\n cancelar: () => {\r\n clearTimeout(timer);\r\n externo?.removeEventListener(\"abort\", repassar);\r\n },\r\n };\r\n}\r\n\r\nfunction isQuotaError(err: unknown): boolean {\r\n return (\r\n err instanceof Error &&\r\n (err.name === \"QuotaExceededError\" ||\r\n err.name === \"NS_ERROR_DOM_QUOTA_REACHED\")\r\n );\r\n}\r\n\r\n/** Logger padrão do modo debug: imprime no console com um prefixo fixo. */\r\nfunction defaultLogger(message: string, details?: Record<string, unknown>): void {\r\n if (details) {\r\n console.debug(`[reqcache] ${message}`, details);\r\n } else {\r\n console.debug(`[reqcache] ${message}`);\r\n }\r\n}\r\n\r\n/** Retorna localStorage se existir e funcionar; senão null (SSR, modo privado...). */\r\nfunction getDefaultStorage(): StorageLike | null {\r\n try {\r\n if (typeof localStorage !== \"undefined\") {\r\n const probe = \"__reqcache_probe__\";\r\n localStorage.setItem(probe, \"1\");\r\n localStorage.removeItem(probe);\r\n return localStorage;\r\n }\r\n } catch {\r\n /* Safari em modo privado antigo lança aqui */\r\n }\r\n return null;\r\n}\r\n\r\n/** Instância pronta para uso, caso não queira configurar nada. */\r\nexport const requestCache = new RequestCache();\r\n"],"names":[],"mappings":";;;;;;IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2BG;IA2CH;;;IAGG;IACG,MAAO,SAAU,SAAQ,KAAK,CAAA;QAClC,WAAA,CACW,MAAc,EACd,GAAW,EAAA;IAEpB,QAAA,KAAK,CAAC,CAAA,KAAA,EAAQ,MAAM,cAAc,GAAG,CAAA,CAAE,CAAC;YAH/B,IAAA,CAAA,MAAM,GAAN,MAAM;YACN,IAAA,CAAA,GAAG,GAAH,GAAG;IAGZ,QAAA,IAAI,CAAC,IAAI,GAAG,WAAW;QACzB;IACD;UAoIY,YAAY,CAAA;IAYvB,IAAA,WAAA,CAAY,SAA6B,EAAE,EAAA;;IARnC,QAAA,IAAA,CAAA,QAAQ,GAAG,IAAI,GAAG,EAA4B;;YAE9C,IAAA,CAAA,IAAI,GAAG,CAAC;YAOd,IAAI,CAAC,UAAU,GAAG,CAAA,EAAA,GAAA,MAAM,CAAC,UAAU,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,UAAU;YACjD,IAAI,CAAC,UAAU,GAAG,CAAA,EAAA,GAAA,MAAM,CAAC,UAAU,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,GAAG;YAC1C,IAAI,CAAC,OAAO,GAAG,CAAA,EAAA,GAAA,MAAM,CAAC,OAAO,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,iBAAiB,EAAE;YACpD,IAAI,CAAC,KAAK,GAAG,CAAA,EAAA,GAAA,MAAM,CAAC,KAAK,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,KAAK;YAClC,IAAI,CAAC,MAAM,GAAG,CAAA,EAAA,GAAA,MAAM,CAAC,MAAM,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,aAAa;IAC5C,QAAA,IAAI,CAAC,cAAc,GAAG,EAAE,GAAG,cAAc,EAAE,IAAI,CAAA,EAAA,GAAA,MAAM,CAAC,cAAc,mCAAI,EAAE,CAAC,EAAE;IAC7E,QAAA,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,iBAAiB,EAAE,IAAI,CAAA,EAAA,GAAA,MAAM,CAAC,YAAY,mCAAI,EAAE,CAAC,EAAE;QAC9E;;IAGA,IAAA,QAAQ,CAAC,OAAgB,EAAA;IACvB,QAAA,IAAI,CAAC,KAAK,GAAG,OAAO;QACtB;IAEA;;;;;IAKG;QACH,MAAM,QAAQ,CACZ,GAAW,EACX,KAAa,EACb,UAA2B,EAAE,EAAA;IAE7B,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACtB,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE;IAC1B,QAAA,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,GAAG,CAA8B;;YAG3E,IAAI,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,SAAS,EAAE;gBACpC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;IACtC,YAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAClB,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBACnF,OAAO,MAAM,CAAC,IAAI;YACpB;;;YAIA,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAA2B;YAChE,IAAI,OAAO,EAAE;gBACX,IAAI,CAAC,GAAG,CAAC,2CAA2C,EAAE,EAAE,GAAG,EAAE,CAAC;IAC9D,YAAA,OAAO,OAAO;YAChB;IAEA,QAAA,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,6BAA6B,GAAG,YAAY,EAAE,EAAE,GAAG,EAAE,CAAC;IAExE,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAI,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAA,IAAA,IAAN,MAAM,cAAN,MAAM,GAAI,IAAI;IACnE,aAAA,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAE3C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC;IAC/B,QAAA,OAAO,OAAO;QAChB;;IAGA,IAAA,UAAU,CAAC,GAAW,EAAA;YACpB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC;IACxD,QAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QACpB;IAEA;;;;IAIG;QACH,OAAO,CAAC,YAAY,GAAG,IAAI,EAAA;YACzB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,GAAG,IAAI;IAC/C,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE;IAC1B,QAAA,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,MAAM,CAAC;IACxD,QAAA,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACvB,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM;YAC9C,IAAI,SAAS,GAAG,CAAC;gBAAE,IAAI,CAAC,GAAG,CAAC,oCAAoC,EAAE,EAAE,SAAS,EAAE,CAAC;IAChF,QAAA,OAAO,SAAS;QAClB;;QAGA,KAAK,GAAA;YACH,IAAI,CAAC,IAAI,CAAC,OAAO;gBAAE;IACnB,QAAA,IAAI;gBACF,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC;YAC1C;IAAE,QAAA,OAAA,EAAA,EAAM;;YAER;QACF;;;QAKQ,UAAU,GAAA;IAChB,QAAA,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC,IAAI;QAClB;;QAGQ,GAAG,CAAC,OAAe,EAAE,OAAiC,EAAA;YAC5D,IAAI,CAAC,IAAI,CAAC,KAAK;gBAAE;IACjB,QAAA,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;QAC/B;IAEA;;;;;;;;IAQG;IACK,IAAA,eAAe,CAAC,OAAwB,EAAA;;IAC9C,QAAA,IAAI,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ;gBAAE,OAAO,OAAO,CAAC,SAAS;YACnE,OAAO,IAAI,CAAC,YAAY,CAAC,CAAA,EAAA,GAAA,YAAY,EAAE,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,cAAc,CAAC;QAC5D;IAEA;;;;;;;;;;;IAWG;IACK,IAAA,MAAM,mBAAmB,CAC/B,UAAoB,EACpB,OAAqB,EACrB,OAAwB,EAAA;;IAExB,QAAA,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,CAAA,EAAA,GAAA,OAAO,CAAC,iBAAiB,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,0BAA0B,CACxD;YACD,MAAM,OAAO,GAAG,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,OAAO,CAAC,YAAY,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAE,MAAM,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,IAAI;IACpD,QAAA,IAAI,UAAmB;IAEvB,QAAA,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;gBAClC,IAAI,OAAO,aAAP,OAAO,KAAA,MAAA,GAAA,MAAA,GAAP,OAAO,CAAE,OAAO,EAAE;oBACpB,IAAI,CAAC,GAAG,CAAC,4CAA4C,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;oBAC1E,MAAM,CAAA,EAAA,GAAA,OAAO,CAAC,MAAM,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,IAAI,KAAK,CAAC,oCAAoC,CAAC;gBACzE;gBAEA,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC;gBAC/C,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC;IAChD,YAAA,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,CAAC;IAE9E,YAAA,IAAI;IACF,gBAAA,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE;wBACnC,GAAG,OAAO,CAAC,YAAY;wBACvB,MAAM,EAAE,SAAS,CAAC,MAAM;IACzB,iBAAA,CAAC;oBACF,IAAI,CAAC,GAAG,CAAC,EAAE;wBAAE,MAAM,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC;oBACvD,MAAM,IAAI,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM;IACpC,gBAAA,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAC5D,gBAAA,OAAO,IAAI;gBACb;gBAAE,OAAO,KAAK,EAAE;;;IAGd,gBAAA,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ;IAC5B,sBAAE,aAAa,CAAC,SAAS,EAAE,SAAS;0BAClC,KAAK;IAET,gBAAA,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC/D,UAAU,GAAG,GAAG;oBAChB,CAAA,EAAA,GAAA,OAAO,CAAC,UAAU,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,MAAA,GAAA,EAAA,CAAA,IAAA,CAAA,OAAA,EAAG,SAAS,EAAE,GAAG,CAAC;IAEpC,gBAAA,IAAI,GAAG,YAAY,SAAS,IAAI,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;IAC3D,oBAAA,IAAI,CAAC,GAAG,CAAC,wCAAwC,EAAE;IACjD,wBAAA,GAAG,EAAE,SAAS;4BACd,MAAM,EAAE,GAAG,CAAC,MAAM;IACnB,qBAAA,CAAC;IACF,oBAAA,MAAM,GAAG;oBACX;gBACF;wBAAU;;;oBAGR,SAAS,CAAC,QAAQ,EAAE;gBACtB;YACF;IAEA,QAAA,MAAM,UAAU;QAClB;QAEQ,MAAM,UAAU,CACtB,GAAW,EACX,KAAa,EACb,OAAwB,EACxB,MAA4B,EAAA;;YAE5B,MAAM,OAAO,GAAG,CAAA,EAAA,GAAA,OAAO,CAAC,OAAO,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,KAAK;IACxC,QAAA,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,IAAI,CAAA,EAAA,GAAA,OAAO,CAAC,YAAY,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,EAAE,CAAC,CAAC;IACzD,QAAA,IAAI,KAAQ;IAEZ,QAAA,IAAI;IACF,YAAA,KAAK,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAI,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC;YACzE;YAAE,OAAO,GAAG,EAAE;;gBAEZ,IAAI,MAAM,KAAK,CAAA,EAAA,GAAA,OAAO,CAAC,YAAY,MAAA,IAAA,IAAA,EAAA,KAAA,MAAA,GAAA,EAAA,GAAI,IAAI,CAAC,EAAE;oBAC5C,IAAI,CAAC,GAAG,CAAC,sCAAsC,EAAE,EAAE,GAAG,EAAE,CAAC;oBACzD,IAAI,CAAC,MAAM,CAAC;IACV,oBAAA,GAAG,MAAM;IACT,oBAAA,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;IAC7B,oBAAA,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;IAC9B,iBAAA,CAAC;oBACF,OAAO,MAAM,CAAC,IAAI;gBACpB;IACA,YAAA,MAAM,GAAG;YACX;;;IAIA,QAAA,IAAI,MAAM,IAAI,OAAO,CAAC,YAAY,EAAE;IAClC,YAAA,MAAM,QAAQ,GAAG,qBAAqB,CACpC,MAAM,CAAC,IAAI,EACX,KAAK,EACL,OAAO,CAAC,YAAY,EACpB,IAAI,CAAC,cAAc,CACpB;IAED,YAAA,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE;;IAErB,gBAAA,IAAI,CAAC,GAAG,CAAC,2DAA2D,EAAE;wBACpE,GAAG;wBACH,MAAM,EAAE,QAAQ,CAAC,QAAQ;IAC1B,iBAAA,CAAC;oBACF,IAAI,CAAC,MAAM,CAAC;IACV,oBAAA,GAAG,MAAM;IACT,oBAAA,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;IAC7B,oBAAA,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;IAC9B,iBAAA,CAAC;oBACF,OAAO,MAAM,CAAC,IAAI;gBACpB;YACF;;IAGA,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;YACtB,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;YACzC,IAAI,CAAC,MAAM,CAAI;IACb,YAAA,IAAI,EAAE,GAAG;IACT,YAAA,IAAI,EAAE,KAAK;IACX,YAAA,SAAS,EAAE,GAAG;gBACd,SAAS,EAAE,GAAG,GAAG,KAAK;IACtB,YAAA,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE;IAC9B,SAAA,CAAC;IACF,QAAA,OAAO,KAAK;QACd;;IAGQ,IAAA,MAAM,CAAI,KAAoB,EAAA;YACpC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC;IAC/D,QAAA,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;IACf,QAAA,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;IACvB,QAAA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QACpB;;IAGQ,IAAA,aAAa,CAAC,GAAiB,EAAA;YACrC,OAAO,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE;gBACnC,IAAI,aAAa,GAAG,CAAC;IACrB,YAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;IACnC,gBAAA,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,UAAU;wBAAE,aAAa,GAAG,CAAC;gBAC1E;IACA,YAAA,IAAI,CAAC,GAAG,CAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7E,YAAA,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC;YAC9B;QACF;QAEQ,OAAO,GAAA;YACb,IAAI,CAAC,IAAI,CAAC,OAAO;IAAE,YAAA,OAAO,EAAE;IAC5B,QAAA,IAAI;IACF,YAAA,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;IACjD,YAAA,IAAI,CAAC,GAAG;IAAE,gBAAA,OAAO,EAAE;gBACnB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;IAC9B,YAAA,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAI,MAAuB,GAAG,EAAE;YAC9D;IAAE,QAAA,OAAA,EAAA,EAAM;gBACN,OAAO,EAAE,CAAC;YACZ;QACF;IAEQ,IAAA,QAAQ,CAAC,GAAiB,EAAA;YAChC,IAAI,CAAC,IAAI,CAAC,OAAO;gBAAE;IACnB,QAAA,IAAI;IACF,YAAA,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAC5D;YAAE,OAAO,GAAG,EAAE;;gBAEZ,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE;oBACvC,MAAM,QAAQ,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC;IACrE,gBAAA,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACnD,gBAAA,IAAI,CAAC,GAAG,CAAC,8DAA8D,EAAE;wBACvE,UAAU,EAAE,GAAG,CAAC,MAAM;wBACtB,WAAW,EAAE,QAAQ,CAAC,MAAM;IAC7B,iBAAA,CAAC;IACF,gBAAA,IAAI;IACF,oBAAA,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;oBACjE;IAAE,gBAAA,OAAA,EAAA,EAAM;;oBAER;gBACF;;YAEF;QACF;IACD;IAED;IACA,SAAS,OAAO,CAAC,GAAY,EAAE,IAAY,EAAA;IACzC,IAAA,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAU,CAAC,GAAG,EAAE,CAAC,KAAI;YAChD,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,IAAK,GAAc,EAAE;IAC1D,YAAA,OAAQ,GAA+B,CAAC,CAAC,CAAC;YAC5C;IACA,QAAA,OAAO,SAAS;QAClB,CAAC,EAAE,GAAG,CAAC;IACT;IAWA;;;;;;;;IAQG;IACH,SAAS,WAAW,CAAC,KAAc,EAAA;IACjC,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;YAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI;QACzE;QACA,IAAI,OAAO,KAAK,KAAK,QAAQ;IAAE,QAAA,OAAO,IAAI;;;IAI1C,IAAA,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;IAAE,QAAA,OAAO,IAAI;IAEpC,IAAA,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,IAAA,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE;QAErE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;IAC9B,IAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE;QAE7D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE;IACzC;IAEA;;;;;;;IAOG;IACH,SAAS,kBAAkB,CACzB,KAAc,EACd,IAAoB,EAAA;;;IAIpB,IAAA,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,GAAG,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI;QAC7D,IAAI,KAAK,KAAK,EAAE;IAAE,QAAA,OAAO,IAAI;IAE7B,IAAA,IAAI,IAAI,KAAK,QAAQ,EAAE;IACrB,QAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;gBAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI;YAC/D;YACA,IAAI,KAAK,KAAK,IAAI;IAAE,YAAA,OAAO,IAAI;IAC/B,QAAA,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC;YAC5B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;QACjE;IAEA,IAAA,IAAI,IAAI,KAAK,MAAM,EAAE;;IAEnB,QAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;gBAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI;YAC/D;YACA,IAAI,KAAK,KAAK,IAAI;IAAE,YAAA,OAAO,IAAI;YAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;YAC9B,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QAC1D;IAEA,IAAA,OAAO,KAAK,KAAK,IAAI,GAAG,IAAI,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;IACvD;IAEA;;;;;;;;;IASG;IACH,MAAM,cAAc,GAAkC;IACpD,IAAA,GAAG,EAAE,QAAQ;IACb,IAAA,MAAM,EAAE,QAAQ;IAChB,IAAA,eAAe,EAAE,QAAQ;IACzB,IAAA,YAAY,EAAE,MAAM;KACrB;IAaD;;;;IAIG;IACH,SAAS,aAAa,CACpB,KAAa,EACb,MAAqC,EAAA;QAErC,IAAI,KAAK,IAAI,MAAM;IAAE,QAAA,OAAO,MAAM,CAAC,KAAK,CAAC;IACzC,IAAA,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtD,IAAA,OAAO,MAAM,IAAI,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI;IACjD;IAEA;;;;IAIG;IACH,SAAS,gBAAgB,CACvB,YAA0B,EAC1B,MAAqC,EAAA;IAErC,IAAA,MAAM,OAAO,GAAG,CAAC,KAAa,KAAqB;YACjD,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC;IAC9C,QAAA,OAAO;kBACH,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW;IAC/C,cAAE,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE;IAChD,IAAA,CAAC;QAED,IAAI,OAAO,YAAY,KAAK,QAAQ;IAAE,QAAA,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACpE,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;IAAE,QAAA,OAAO,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC;IAEjE,IAAA,OAAO,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM;YAC1D,KAAK;YACL,IAAI;IACJ,QAAA,MAAM,EAAE,WAAoB;IAC7B,KAAA,CAAC,CAAC;IACL;IAEA;IACA,SAAS,KAAK,CAAC,CAAkB,EAAE,CAAkB,EAAA;QACnD,OAAO,OAAO,CAAC,KAAK,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IAC9D;IAqBA;;;;;;;;IAQG;IACH,SAAS,qBAAqB,CAC5B,OAAgB,EAChB,OAAgB,EAChB,YAA0B,EAC1B,MAAqC,EAAA;QAErC,MAAM,QAAQ,GAAqB,EAAE;;QAErC,IAAI,cAAc,GAAG,IAAI;;;IAIzB,IAAA,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,gBAAgB,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE;YAC5E,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;YACtC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;IAEtC,QAAA,MAAM,MAAM,GAAG,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC;IAC5E,QAAA,MAAM,MAAM,GAAG,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC;;;;;IAM5E,QAAA,MAAM,UAAU,GACd,IAAI,KAAK,IAAI;iBACZ,OAAO,MAAM,KAAK,OAAO,MAAM,IAAI,CAAA,MAAM,KAAA,IAAA,IAAN,MAAM,KAAA,MAAA,GAAA,MAAA,GAAN,MAAM,CAAE,IAAI,OAAK,MAAM,KAAA,IAAA,IAAN,MAAM,KAAA,MAAA,GAAA,MAAA,GAAN,MAAM,CAAE,IAAI,CAAA,CAAC;;;YAIpE,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,IAAI,UAAU,EAAE;gBACpD,cAAc,GAAG,KAAK;gBACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;gBAClF;YACF;IAEA,QAAA,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC;IACjD,QAAA,IAAI,CAAC,OAAO;gBAAE,cAAc,GAAG,KAAK;YACpC,QAAQ,CAAC,IAAI,CAAC;gBACZ,KAAK;gBACL,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM;gBACN,MAAM;gBACN,MAAM;gBACN,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,aAAa;IAC1C,SAAA,CAAC;QACJ;IAEA,IAAA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE;IAC9C;IAEA;IACA,MAAM,0BAA0B,GAAG,CAAC,GAAG,CAAC;IAExC;;;;;;;;;;;IAWG;AACI,UAAM,iBAAiB,GAA6D;IACzF,IAAA,SAAS,EAAE,KAAM;IACjB,IAAA,IAAI,EAAE,KAAM;IACZ,IAAA,IAAI,EAAE,KAAM;IACZ,IAAA,IAAI,EAAE,KAAM;IACZ,IAAA,YAAY,EAAE,KAAM;;IAGtB,MAAM,gBAAgB,GAA8B,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC;IAEjF;;;;IAIG;IACH,SAAS,YAAY,GAAA;;IACnB,IAAA,IAAI;YACF,IAAI,OAAO,SAAS,KAAK,WAAW;IAAE,YAAA,OAAO,IAAI;YACjD,MAAM,GAAG,GAAG,SAIX;IACD,QAAA,MAAM,OAAO,GAAG,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,GAAG,CAAC,UAAU,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,EAAA,GAAI,GAAG,CAAC,aAAa,MAAA,IAAA,IAAA,EAAA,KAAA,KAAA,CAAA,GAAA,EAAA,GAAI,GAAG,CAAC,gBAAgB;YAC3E,MAAM,IAAI,GAAG,OAAO,KAAA,IAAA,IAAP,OAAO,KAAA,KAAA,CAAA,GAAA,KAAA,CAAA,GAAP,OAAO,CAAE,aAAa;IACnC,QAAA,IAAI,CAAC,IAAI;IAAE,YAAA,OAAO,IAAI;IACtB,QAAA,OAAO,gBAAgB,CAAC,OAAO,CAAC,IAA+B,CAAC,IAAI;IAClE,cAAG;kBACD,IAAI;QACV;IAAE,IAAA,OAAA,EAAA,EAAM;IACN,QAAA,OAAO,IAAI;QACb;IACF;IAEA;IACA,SAAS,aAAa,CAAC,GAAW,EAAE,SAAiB,EAAA;QACnD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,CAAA,WAAA,EAAc,SAAS,CAAA,aAAA,EAAgB,GAAG,CAAA,CAAE,CAAC;IACnE,IAAA,GAAG,CAAC,IAAI,GAAG,cAAc;IACzB,IAAA,OAAO,GAAG;IACZ;IAWA;;;;;;;;;;IAUG;IACH,SAAS,UAAU,CACjB,SAAiB,EACjB,OAA2B,EAAA;IAE3B,IAAA,IAAI,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,OAAO,eAAe,KAAK,WAAW,EAAE;YAC9D,OAAO;IACL,YAAA,MAAM,EAAE,OAAO,KAAA,IAAA,IAAP,OAAO,KAAA,MAAA,GAAP,OAAO,GAAI,SAAS;IAC5B,YAAA,QAAQ,EAAE,MAAM,KAAK;IACrB,YAAA,QAAQ,EAAE,MAAK,EAAE,CAAC;aACnB;QACH;IAEA,IAAA,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE;QAClC,IAAI,UAAU,GAAG,KAAK;IAEtB,IAAA,MAAM,KAAK,GAAG,UAAU,CAAC,MAAK;YAC5B,UAAU,GAAG,IAAI;YACjB,IAAI,CAAC,KAAK,EAAE;QACd,CAAC,EAAE,SAAS,CAAC;QAEb,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE;QACnC,IAAI,OAAO,EAAE;YACX,IAAI,OAAO,CAAC,OAAO;gBAAE,IAAI,CAAC,KAAK,EAAE;;IAC5B,YAAA,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC;QAClD;QAEA,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,MAAM;IACnB,QAAA,QAAQ,EAAE,MAAM,UAAU;YAC1B,QAAQ,EAAE,MAAK;gBACb,YAAY,CAAC,KAAK,CAAC;gBACnB,OAAO,KAAA,IAAA,IAAP,OAAO,KAAA,MAAA,GAAA,MAAA,GAAP,OAAO,CAAE,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC;YACjD,CAAC;SACF;IACH;IAEA,SAAS,YAAY,CAAC,GAAY,EAAA;QAChC,QACE,GAAG,YAAY,KAAK;IACpB,SAAC,GAAG,CAAC,IAAI,KAAK,oBAAoB;IAChC,YAAA,GAAG,CAAC,IAAI,KAAK,4BAA4B,CAAC;IAEhD;IAEA;IACA,SAAS,aAAa,CAAC,OAAe,EAAE,OAAiC,EAAA;QACvE,IAAI,OAAO,EAAE;YACX,OAAO,CAAC,KAAK,CAAC,CAAA,WAAA,EAAc,OAAO,CAAA,CAAE,EAAE,OAAO,CAAC;QACjD;aAAO;IACL,QAAA,OAAO,CAAC,KAAK,CAAC,cAAc,OAAO,CAAA,CAAE,CAAC;QACxC;IACF;IAEA;IACA,SAAS,iBAAiB,GAAA;IACxB,IAAA,IAAI;IACF,QAAA,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE;gBACvC,MAAM,KAAK,GAAG,oBAAoB;IAClC,YAAA,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;IAChC,YAAA,YAAY,CAAC,UAAU,CAAC,KAAK,CAAC;IAC9B,YAAA,OAAO,YAAY;YACrB;QACF;IAAE,IAAA,OAAA,EAAA,EAAM;;QAER;IACA,IAAA,OAAO,IAAI;IACb;IAEA;AACO,UAAM,YAAY,GAAG,IAAI,YAAY;;;;;;;;;;;"}
@@ -13,8 +13,8 @@
13
13
  * 1. Primeira chamada -> faz o fetch e grava a entrada da rota no array.
14
14
  * 2. Durante o TTL -> devolve os dados do cache, sem tocar na rede.
15
15
  * 3. Após expirar -> SÓ revalida quando o client chamar de novo (lazy).
16
- * Se houver `monotonicKey`, só aceita o novo dado quando o valor numérico
17
- * dessa chave for MAIOR que o guardado; senão mantém o antigo.
16
+ * Se houver `monotonicKey`, só aceita o novo dado quando TODAS as chaves
17
+ * tiverem AVANÇADO; senão mantém o antigo.
18
18
  *
19
19
  * Proteção de espaço (limite de ~5 MB do localStorage):
20
20
  * - `maxEntries`: ao gravar, se passar do limite, remove a rota menos
@@ -31,13 +31,70 @@ export interface StorageLike {
31
31
  setItem(key: string, value: string): void;
32
32
  removeItem(key: string): void;
33
33
  }
34
+ /**
35
+ * Como comparar o valor de uma chave monotônica:
36
+ * - `"number"` — numérica. Aceita `number` e string numérica ("2232575810").
37
+ * - `"date"` — por timestamp. Aceita o que o `Date.parse` resolver
38
+ * ("2026-10-04T18:23:00Z") e também epoch em ms.
39
+ * - `"string"` — lexicográfica.
40
+ */
41
+ export type MonotonicType = "number" | "date" | "string";
42
+ /**
43
+ * Chave(s) monotônica(s). Três formas:
44
+ * - `"summary.ballots_counted"` — uma chave, tipo detectado pelo valor.
45
+ * - `["idg", "summary.last_updated"]` — várias, tipo detectado pelo valor.
46
+ * - `{ idg: "number", "summary.last_updated": "date" }` — várias, com o tipo
47
+ * declarado por nome de chave (recomendado: não depende de adivinhação).
48
+ */
49
+ export type MonotonicKey = string | string[] | Record<string, MonotonicType>;
50
+ /**
51
+ * Qualidade de rede estimada pelo browser (Network Information API).
52
+ * É uma ESTIMATIVA baseada em latência e throughput recentes, não o rádio do
53
+ * aparelho: um 4g congestionado é reportado como "2g" — que é exatamente o
54
+ * caso que interessa aqui.
55
+ */
56
+ export type EffectiveConnectionType = "slow-2g" | "2g" | "3g" | "4g";
57
+ /**
58
+ * Timeout de UMA tentativa de fetch, em ms, por qualidade de rede.
59
+ * `desconhecida` é o que vale em SSR e nos browsers sem a Network Information
60
+ * API (Safari e Firefox).
61
+ */
62
+ export type TimeoutTiers = Partial<Record<EffectiveConnectionType | "desconhecida", number>>;
63
+ /**
64
+ * Falha de HTTP com o status preservado. É o que permite decidir se vale a
65
+ * pena tentar o próximo domínio (503, sim) ou não (404).
66
+ */
67
+ export declare class HttpError extends Error {
68
+ readonly status: number;
69
+ readonly url: string;
70
+ constructor(status: number, url: string);
71
+ }
34
72
  export interface GetFetchOptions {
35
73
  /**
36
- * Caminho da chave numérica monotônica. Suporta aninhamento com ponto.
37
- * Ex.: "total", "resultado.votosApurados", "data.candidato.votos".
38
- * Se o valor novo NÃO for maior que o antigo, mantém o dado em cache.
74
+ * Caminho(s) da chave monotônica. Suporta aninhamento com ponto.
75
+ *
76
+ * monotonicKey: "resultado.votosApurados"
77
+ * monotonicKey: ["idg", "summary.last_updated"]
78
+ * monotonicKey: { idg: "number", "summary.last_updated": "date" }
79
+ *
80
+ * Com mais de uma chave, TODAS são comparadas e o dado novo só é aceito
81
+ * quando todas concordam que ele é mais recente — se qualquer uma vier
82
+ * igual ou menor, a resposta é descartada e o dado em cache é mantido.
83
+ * A comparação é sempre estrita: o valor novo precisa ser MAIOR que o
84
+ * guardado, nunca igual.
85
+ *
86
+ * O tipo de cada chave é resolvido nesta ordem:
87
+ * 1. declarado na forma de mapa;
88
+ * 2. conhecido pelo NOME da chave (`idg` é número, `last_updated` é data —
89
+ * veja `TIPOS_POR_NOME`, extensível via `monotonicTypes` na config);
90
+ * 3. detectado pelo valor: número, string que converte para número finito,
91
+ * string que o `Date.parse` resolve e, por fim, lexicográfica.
92
+ *
93
+ * TODAS as chaves precisam ter crescido. Uma chave incomparável (ausente de
94
+ * um dos lados, ilegível ou fora do tipo esperado) conta como "não cresceu"
95
+ * e derruba a resposta nova junto com as demais.
39
96
  */
40
- monotonicKey?: string;
97
+ monotonicKey?: MonotonicKey;
41
98
  /** Opções nativas repassadas ao fetch (headers, method, body, signal...). */
42
99
  fetchOptions?: RequestInit;
43
100
  /**
@@ -58,6 +115,21 @@ export interface GetFetchOptions {
58
115
  * (`url` + `fallbackUrls`). Útil para observabilidade/log.
59
116
  */
60
117
  onFallback?: (failedUrl: string, error: unknown) => void;
118
+ /**
119
+ * Timeout de CADA tentativa, em ms. Quando omitido, sai da tabela por rede
120
+ * (`timeoutTiers` na config). Vale por candidato, não pela fila toda: com
121
+ * dois `fallbackUrls`, o pior caso é 3x esse valor.
122
+ */
123
+ timeoutMs?: number;
124
+ /**
125
+ * Status HTTP que NÃO devem acionar o fallback. Padrão: `[404]`.
126
+ *
127
+ * Um 404 é uma resposta correta do servidor — "esse recurso não existe" —,
128
+ * não uma indisponibilidade. Repetir o mesmo caminho em outro domínio tende
129
+ * a devolver o mesmo 404, só que N vezes mais devagar, então o erro sobe na
130
+ * hora. Passe `[]` para voltar ao comportamento antigo (tentar todos).
131
+ */
132
+ statusSemFallback?: number[];
61
133
  }
62
134
  /** Uma entrada do cache. `rota` é a chave identificadora. */
63
135
  export interface CacheEntry<T = unknown> {
@@ -88,6 +160,29 @@ export interface RequestCacheConfig {
88
160
  debug?: boolean;
89
161
  /** Logger customizado usado quando `debug` está ligado. Padrão: `console.debug`. */
90
162
  logger?: DebugLogFn;
163
+ /**
164
+ * Tipos monotônicos adicionais, por nome de chave. Somado (e com prioridade
165
+ * sobre) a tabela embutida `TIPOS_POR_NOME`, para que o chamador possa
166
+ * continuar passando `monotonicKey` como lista de caminhos e ainda assim ter
167
+ * a comparação certa:
168
+ *
169
+ * new RequestCache({ monotonicTypes: { data_apuracao: "date" } })
170
+ * ...
171
+ * getFetch(url, 10_000, { monotonicKey: ["idg", "data_apuracao"] })
172
+ *
173
+ * Aceita o caminho completo ("summary.last_updated") ou só o último trecho
174
+ * ("last_updated").
175
+ */
176
+ monotonicTypes?: Record<string, MonotonicType>;
177
+ /**
178
+ * Timeout de cada tentativa de fetch, por qualidade de rede do client.
179
+ * O que for passado aqui é mesclado sobre `TIMEOUTS_POR_REDE`:
180
+ *
181
+ * new RequestCache({ timeoutTiers: { "slow-2g": 120_000 } })
182
+ *
183
+ * Um `timeoutMs` na chamada tem prioridade sobre esta tabela.
184
+ */
185
+ timeoutTiers?: TimeoutTiers;
91
186
  }
92
187
  export declare class RequestCache {
93
188
  private storage;
@@ -98,6 +193,8 @@ export declare class RequestCache {
98
193
  private tick;
99
194
  private debug;
100
195
  private logger;
196
+ private monotonicTypes;
197
+ private timeoutTiers;
101
198
  constructor(config?: RequestCacheConfig);
102
199
  /** Liga/desliga o modo debug em runtime, sem precisar recriar a instância. */
103
200
  setDebug(enabled: boolean): void;
@@ -122,10 +219,27 @@ export declare class RequestCache {
122
219
  private nextAccess;
123
220
  /** Emite uma linha de log, só quando o modo debug está ligado. */
124
221
  private log;
222
+ /**
223
+ * Timeout de UMA tentativa, em ms. A ordem de prioridade é:
224
+ * 1. `timeoutMs` passado na chamada;
225
+ * 2. o tier da rede atual do client (`navigator.connection`);
226
+ * 3. o tier `desconhecida`, usado em SSR, Safari e Firefox.
227
+ *
228
+ * A rede é relida a cada tentativa de propósito: numa apuração o client pode
229
+ * sair do wi-fi e cair no 3g no meio da fila de fallback.
230
+ */
231
+ private resolverTimeout;
125
232
  /**
126
233
  * Tenta cada URL da lista em ordem (primário, depois os `fallbackUrls`).
127
234
  * Devolve o JSON da primeira que responder OK; se todas falharem, lança o
128
235
  * último erro.
236
+ *
237
+ * Duas situações encerram a fila antes do fim, em vez de seguir para o
238
+ * próximo candidato:
239
+ * - um status listado em `statusSemFallback` (padrão: 404). É resposta do
240
+ * servidor, não indisponibilidade — o outro domínio diria o mesmo.
241
+ * - o `signal` do chamador abortado. Foi cancelamento de quem pediu;
242
+ * insistir só trocaria o erro real pelo AbortError do último candidato.
129
243
  */
130
244
  private fetchComRedundancia;
131
245
  private revalidate;
@@ -136,5 +250,18 @@ export declare class RequestCache {
136
250
  private readAll;
137
251
  private writeAll;
138
252
  }
253
+ /**
254
+ * Tier list de timeout por qualidade de rede, em ms, aplicado a CADA tentativa.
255
+ *
256
+ * 4g / desconhecida 30 s — base
257
+ * 3g 45 s — 1,5x
258
+ * 2g 60 s — 2x
259
+ * slow-2g 90 s — 3x; borda de cobertura, interior
260
+ *
261
+ * A folga cresce com a rede ruim para não matar uma resposta que ainda viria
262
+ * num client lento. O custo aparece em `fallbackUrls`, porque a fila é
263
+ * sequencial: o pior caso é N x o tier — veja `timeoutMs` em GetFetchOptions.
264
+ */
265
+ export declare const TIMEOUTS_POR_REDE: Record<EffectiveConnectionType | "desconhecida", number>;
139
266
  /** Instância pronta para uso, caso não queira configurar nada. */
140
267
  export declare const requestCache: RequestCache;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sevn/reqcache",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Cache de requisicoes HTTP em localStorage, com regra monotonica, deduplicacao e limite LRU. Pensado para cenarios de alto volume (ex.: apuracao de eleicoes).",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",