@sevn/reqcache 1.3.0 → 1.5.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 CHANGED
@@ -32,9 +32,21 @@
32
32
  * - Deduplicação: chamadas simultâneas à mesma rota disparam 1 só fetch.
33
33
  * - staleOnError: se a rede/API falhar, devolve o último dado válido.
34
34
  */
35
+ /**
36
+ * Falha de HTTP com o status preservado. É o que permite decidir se vale a
37
+ * pena tentar o próximo domínio (503, sim) ou não (404).
38
+ */
39
+ class HttpError extends Error {
40
+ constructor(status, url) {
41
+ super(`HTTP ${status} ao buscar ${url}`);
42
+ this.status = status;
43
+ this.url = url;
44
+ this.name = "HttpError";
45
+ }
46
+ }
35
47
  class RequestCache {
36
48
  constructor(config = {}) {
37
- var _a, _b, _c, _d, _e, _f;
49
+ var _a, _b, _c, _d, _e, _f, _g;
38
50
  this.inflight = new Map();
39
51
  /** Relógio de acesso monotônico: sempre cresce, mesmo com acessos no mesmo ms. */
40
52
  this.tick = 0;
@@ -44,6 +56,7 @@
44
56
  this.debug = (_d = config.debug) !== null && _d !== void 0 ? _d : false;
45
57
  this.logger = (_e = config.logger) !== null && _e !== void 0 ? _e : defaultLogger;
46
58
  this.monotonicTypes = { ...TIPOS_POR_NOME, ...((_f = config.monotonicTypes) !== null && _f !== void 0 ? _f : {}) };
59
+ this.timeoutTiers = { ...TIMEOUTS_POR_REDE, ...((_g = config.timeoutTiers) !== null && _g !== void 0 ? _g : {}) };
47
60
  }
48
61
  /** Liga/desliga o modo debug em runtime, sem precisar recriar a instância. */
49
62
  setDebug(enabled) {
@@ -122,27 +135,78 @@
122
135
  return;
123
136
  this.logger(message, details);
124
137
  }
138
+ /**
139
+ * Timeout de UMA tentativa, em ms. A ordem de prioridade é:
140
+ * 1. `timeoutMs` passado na chamada;
141
+ * 2. o tier da rede atual do client (`navigator.connection`);
142
+ * 3. o tier `desconhecida`, usado em SSR, Safari e Firefox.
143
+ *
144
+ * A rede é relida a cada tentativa de propósito: numa apuração o client pode
145
+ * sair do wi-fi e cair no 3g no meio da fila de fallback.
146
+ */
147
+ resolverTimeout(options) {
148
+ var _a;
149
+ if (typeof options.timeoutMs === "number")
150
+ return options.timeoutMs;
151
+ return this.timeoutTiers[(_a = detectarRede()) !== null && _a !== void 0 ? _a : "desconhecida"];
152
+ }
125
153
  /**
126
154
  * Tenta cada URL da lista em ordem (primário, depois os `fallbackUrls`).
127
155
  * Devolve o JSON da primeira que responder OK; se todas falharem, lança o
128
156
  * último erro.
157
+ *
158
+ * Duas situações encerram a fila antes do fim, em vez de seguir para o
159
+ * próximo candidato:
160
+ * - um status listado em `statusSemFallback` (padrão: 404). É resposta do
161
+ * servidor, não indisponibilidade — o outro domínio diria o mesmo.
162
+ * - o `signal` do chamador abortado. Foi cancelamento de quem pediu;
163
+ * insistir só trocaria o erro real pelo AbortError do último candidato.
129
164
  */
130
165
  async fetchComRedundancia(candidatos, fetcher, options) {
131
- var _a;
166
+ var _a, _b, _c, _d, _e;
167
+ const semFallback = new Set((_a = options.statusSemFallback) !== null && _a !== void 0 ? _a : STATUS_SEM_FALLBACK_PADRAO);
168
+ const externo = (_c = (_b = options.fetchOptions) === null || _b === void 0 ? void 0 : _b.signal) !== null && _c !== void 0 ? _c : null;
132
169
  let ultimoErro;
133
170
  for (const candidato of candidatos) {
134
- this.log("fetch de rede", { url: candidato });
171
+ if (externo === null || externo === void 0 ? void 0 : externo.aborted) {
172
+ this.log("cancelado pelo chamador: fila interrompida", { url: candidato });
173
+ throw (_d = externo.reason) !== null && _d !== void 0 ? _d : new Error("Requisição cancelada pelo chamador");
174
+ }
175
+ const timeoutMs = this.resolverTimeout(options);
176
+ const tentativa = criarSinal(timeoutMs, externo);
177
+ this.log("fetch de rede", { url: candidato, timeoutMs, rede: detectarRede() });
135
178
  try {
136
- const res = await fetcher(candidato, options.fetchOptions);
179
+ const res = await fetcher(candidato, {
180
+ ...options.fetchOptions,
181
+ signal: tentativa.signal,
182
+ });
137
183
  if (!res.ok)
138
- throw new Error(`HTTP ${res.status} ao buscar ${candidato}`);
184
+ throw new HttpError(res.status, candidato);
185
+ const json = (await res.json());
139
186
  this.log("fetch OK", { url: candidato, status: res.status });
140
- return (await res.json());
187
+ return json;
141
188
  }
142
- catch (err) {
189
+ catch (bruto) {
190
+ // Um abort disparado pelo nosso timer chega aqui como AbortError
191
+ // genérico; troca por um erro que diz o que de fato aconteceu.
192
+ const err = tentativa.estourou()
193
+ ? erroDeTimeout(candidato, timeoutMs)
194
+ : bruto;
143
195
  this.log("fetch falhou", { url: candidato, erro: String(err) });
144
196
  ultimoErro = err;
145
- (_a = options.onFallback) === null || _a === void 0 ? void 0 : _a.call(options, candidato, err);
197
+ (_e = options.onFallback) === null || _e === void 0 ? void 0 : _e.call(options, candidato, err);
198
+ if (err instanceof HttpError && semFallback.has(err.status)) {
199
+ this.log("status sem fallback: fila interrompida", {
200
+ url: candidato,
201
+ status: err.status,
202
+ });
203
+ throw err;
204
+ }
205
+ }
206
+ finally {
207
+ // Obrigatório: sem isso sobra um timer de minutos pendurado por
208
+ // tentativa, segurando o event loop no Node e vazando no browser.
209
+ tentativa.cancelar();
146
210
  }
147
211
  }
148
212
  throw ultimoErro;
@@ -428,6 +492,107 @@
428
492
  }
429
493
  return { avancou: todasCresceram, detalhes };
430
494
  }
495
+ /** Status que, por padrão, NÃO acionam o fallback: o servidor já respondeu. */
496
+ const STATUS_SEM_FALLBACK_PADRAO = [404];
497
+ /**
498
+ * Tier list de timeout por qualidade de rede, em ms, aplicado a CADA tentativa.
499
+ *
500
+ * 4g / desconhecida 8 s — base
501
+ * 3g 12 s — 1,5x
502
+ * 2g 16 s — 2x
503
+ * slow-2g 24 s — 3x; borda de cobertura, interior
504
+ *
505
+ * A base fica logo abaixo de um ciclo de polling de 10 s: numa tela que
506
+ * repete a chamada, a resposta que chega depois do próximo tick já nasce
507
+ * velha — o tick novo traz dado mais fresco. Abortar a tempo deixa o ciclo
508
+ * seguinte começar limpo, e o `staleOnError` cobre o buraco com o dado
509
+ * anterior em vez de travar a UI.
510
+ *
511
+ * A folga ainda cresce com a rede ruim, e os tiers piores passam de um ciclo
512
+ * de propósito: lá a deduplicação por rota faz o polling desacelerar sozinho,
513
+ * o que é melhor do que martelar um client que não dá conta.
514
+ *
515
+ * O custo aparece em `fallbackUrls`, porque a fila é sequencial: o pior caso
516
+ * é N x o tier — veja `timeoutMs` em GetFetchOptions.
517
+ */
518
+ const TIMEOUTS_POR_REDE = {
519
+ "slow-2g": 24000,
520
+ "2g": 16000,
521
+ "3g": 12000,
522
+ "4g": 8000,
523
+ desconhecida: 8000,
524
+ };
525
+ const REDES_CONHECIDAS = ["slow-2g", "2g", "3g", "4g"];
526
+ /**
527
+ * Lê `navigator.connection.effectiveType` (Network Information API).
528
+ * Só Chromium e Android implementam; Safari, Firefox e SSR devolvem `null` e
529
+ * caem no tier `desconhecida`.
530
+ */
531
+ function detectarRede() {
532
+ var _a, _b;
533
+ try {
534
+ if (typeof navigator === "undefined")
535
+ return null;
536
+ const nav = navigator;
537
+ const conexao = (_b = (_a = nav.connection) !== null && _a !== void 0 ? _a : nav.mozConnection) !== null && _b !== void 0 ? _b : nav.webkitConnection;
538
+ const tipo = conexao === null || conexao === void 0 ? void 0 : conexao.effectiveType;
539
+ if (!tipo)
540
+ return null;
541
+ return REDES_CONHECIDAS.indexOf(tipo) >= 0
542
+ ? tipo
543
+ : null;
544
+ }
545
+ catch (_c) {
546
+ return null;
547
+ }
548
+ }
549
+ /** Erro dedicado do timeout, para não subir como um `AbortError` sem contexto. */
550
+ function erroDeTimeout(url, timeoutMs) {
551
+ const err = new Error(`Timeout de ${timeoutMs}ms ao buscar ${url}`);
552
+ err.name = "TimeoutError";
553
+ return err;
554
+ }
555
+ /**
556
+ * Monta o `signal` de UMA tentativa: o timeout da vez encadeado com o `signal`
557
+ * que o chamador passou em `fetchOptions`, para que cancelar por fora continue
558
+ * funcionando.
559
+ *
560
+ * Feito na mão com `AbortController` em vez de `AbortSignal.any` +
561
+ * `AbortSignal.timeout`, que só existem em Safari 17.4+ e Node 20+. Também
562
+ * evita o problema de um `AbortSignal.timeout` passado pelo chamador: o
563
+ * relógio dele começa na criação, então viraria um orçamento da fila inteira,
564
+ * deixando os fallbacks sem tempo nenhum.
565
+ */
566
+ function criarSinal(timeoutMs, externo) {
567
+ if (!(timeoutMs > 0) || typeof AbortController === "undefined") {
568
+ return {
569
+ signal: externo !== null && externo !== void 0 ? externo : undefined,
570
+ estourou: () => false,
571
+ cancelar: () => { },
572
+ };
573
+ }
574
+ const ctrl = new AbortController();
575
+ let porTimeout = false;
576
+ const timer = setTimeout(() => {
577
+ porTimeout = true;
578
+ ctrl.abort();
579
+ }, timeoutMs);
580
+ const repassar = () => ctrl.abort();
581
+ if (externo) {
582
+ if (externo.aborted)
583
+ ctrl.abort();
584
+ else
585
+ externo.addEventListener("abort", repassar);
586
+ }
587
+ return {
588
+ signal: ctrl.signal,
589
+ estourou: () => porTimeout,
590
+ cancelar: () => {
591
+ clearTimeout(timer);
592
+ externo === null || externo === void 0 ? void 0 : externo.removeEventListener("abort", repassar);
593
+ },
594
+ };
595
+ }
431
596
  function isQuotaError(err) {
432
597
  return (err instanceof Error &&
433
598
  (err.name === "QuotaExceededError" ||
@@ -460,7 +625,9 @@
460
625
  /** Instância pronta para uso, caso não queira configurar nada. */
461
626
  const requestCache = new RequestCache();
462
627
 
628
+ exports.HttpError = HttpError;
463
629
  exports.RequestCache = RequestCache;
630
+ exports.TIMEOUTS_POR_REDE = TIMEOUTS_POR_REDE;
464
631
  exports.requestCache = requestCache;
465
632
 
466
633
  }));
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 TODAS as chaves\n * tiverem AVANÇADO; 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\n/**\n * Como comparar o valor de uma chave monotônica:\n * - `\"number\"` — numérica. Aceita `number` e string numérica (\"2232575810\").\n * - `\"date\"` — por timestamp. Aceita o que o `Date.parse` resolver\n * (\"2026-10-04T18:23:00Z\") e também epoch em ms.\n * - `\"string\"` — lexicográfica.\n */\nexport type MonotonicType = \"number\" | \"date\" | \"string\";\n\n/**\n * Chave(s) monotônica(s). Três formas:\n * - `\"summary.ballots_counted\"` — uma chave, tipo detectado pelo valor.\n * - `[\"idg\", \"summary.last_updated\"]` — várias, tipo detectado pelo valor.\n * - `{ idg: \"number\", \"summary.last_updated\": \"date\" }` — várias, com o tipo\n * declarado por nome de chave (recomendado: não depende de adivinhação).\n */\nexport type MonotonicKey = string | string[] | Record<string, MonotonicType>;\n\nexport interface GetFetchOptions {\n /**\n * Caminho(s) da chave monotônica. Suporta aninhamento com ponto.\n *\n * monotonicKey: \"resultado.votosApurados\"\n * monotonicKey: [\"idg\", \"summary.last_updated\"]\n * monotonicKey: { idg: \"number\", \"summary.last_updated\": \"date\" }\n *\n * Com mais de uma chave, TODAS são comparadas e o dado novo só é aceito\n * quando todas concordam que ele é mais recente — se qualquer uma vier\n * igual ou menor, a resposta é descartada e o dado em cache é mantido.\n * A comparação é sempre estrita: o valor novo precisa ser MAIOR que o\n * guardado, nunca igual.\n *\n * O tipo de cada chave é resolvido nesta ordem:\n * 1. declarado na forma de mapa;\n * 2. conhecido pelo NOME da chave (`idg` é número, `last_updated` é data —\n * veja `TIPOS_POR_NOME`, extensível via `monotonicTypes` na config);\n * 3. detectado pelo valor: número, string que converte para número finito,\n * string que o `Date.parse` resolve e, por fim, lexicográfica.\n *\n * TODAS as chaves precisam ter crescido. Uma chave incomparável (ausente de\n * um dos lados, ilegível ou fora do tipo esperado) conta como \"não cresceu\"\n * e derruba a resposta nova junto com as demais.\n */\n monotonicKey?: MonotonicKey;\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 /**\n * Tipos monotônicos adicionais, por nome de chave. Somado (e com prioridade\n * sobre) a tabela embutida `TIPOS_POR_NOME`, para que o chamador possa\n * continuar passando `monotonicKey` como lista de caminhos e ainda assim ter\n * a comparação certa:\n *\n * new RequestCache({ monotonicTypes: { data_apuracao: \"date\" } })\n * ...\n * getFetch(url, 10_000, { monotonicKey: [\"idg\", \"data_apuracao\"] })\n *\n * Aceita o caminho completo (\"summary.last_updated\") ou só o último trecho\n * (\"last_updated\").\n */\n monotonicTypes?: Record<string, MonotonicType>;\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 private monotonicTypes: Record<string, MonotonicType>;\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 this.monotonicTypes = { ...TIPOS_POR_NOME, ...(config.monotonicTypes ?? {}) };\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 TODAS as chaves que\n // conseguiram votar tiverem avançado.\n if (cached && options.monotonicKey) {\n const veredito = avaliarMonotonicidade(\n cached.data,\n fresh,\n options.monotonicKey,\n this.monotonicTypes,\n );\n\n if (!veredito.avancou) {\n // Alguma chave não avançou -> 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 chaves: veredito.detalhes,\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\n/** Como um valor deve ser comparado, decidido pelo próprio valor. */\ntype TipoComparacao = \"number\" | \"date\" | \"string\";\n\n/** Um valor já classificado, pronto para ser comparado com outro do mesmo tipo. */\ninterface ValorComparavel {\n tipo: TipoComparacao;\n ordem: number | string;\n}\n\n/**\n * Descobre como comparar um valor. A ordem das checagens NÃO pode ser trocada:\n * `Date.parse` aceita strings numéricas curtas como data (`Date.parse(\"9\")` cai\n * em setembro, `Date.parse(\"10\")` em outubro), então um `idg` curto viraria\n * data se a checagem de data viesse antes da numérica. No sentido inverso não\n * há risco: `Number(\"2026-10-04T18:23:00Z\")` é NaN.\n *\n * Devolve `null` quando o valor é incomparável.\n */\nfunction classificar(valor: unknown): ValorComparavel | null {\n if (typeof valor === \"number\") {\n return Number.isFinite(valor) ? { tipo: \"number\", ordem: valor } : null;\n }\n if (typeof valor !== \"string\") return null;\n\n // String vazia ou em branco é incomparável: `Number(\"\")` e `Number(\" \")`\n // devolvem 0, e um campo vazio viraria um \"zero\" comparável.\n if (valor.trim() === \"\") return null;\n\n const numero = Number(valor);\n if (Number.isFinite(numero)) return { tipo: \"number\", ordem: numero };\n\n const data = Date.parse(valor);\n if (!Number.isNaN(data)) return { tipo: \"date\", ordem: data };\n\n return { tipo: \"string\", ordem: valor };\n}\n\n/**\n * Classifica um valor segundo um tipo DECLARADO pelo chamador (forma de mapa).\n * Como o tipo veio declarado, não há adivinhação: o valor ou serve, ou a chave\n * é incomparável (e derruba a resposta nova). É também mais tolerante que a\n * detecção por valor — com\n * `\"number\"`, `100` e `\"101\"` comparam entre si sem problema, já que o\n * chamador afirmou que aquele campo é numérico.\n */\nfunction classificarComTipo(\n valor: unknown,\n tipo: TipoComparacao,\n): ValorComparavel | null {\n // String vazia ou em branco nunca é comparável: `Number(\"\")` e `Number(\" \")`\n // devolvem 0, e um campo vazio viraria um \"zero\" comparável.\n const texto = typeof valor === \"string\" ? valor.trim() : null;\n if (texto === \"\") return null;\n\n if (tipo === \"number\") {\n if (typeof valor === \"number\") {\n return Number.isFinite(valor) ? { tipo, ordem: valor } : null;\n }\n if (texto === null) return null;\n const numero = Number(texto);\n return Number.isFinite(numero) ? { tipo, ordem: numero } : null;\n }\n\n if (tipo === \"date\") {\n // Um número aqui é lido como epoch em ms.\n if (typeof valor === \"number\") {\n return Number.isFinite(valor) ? { tipo, ordem: valor } : null;\n }\n if (texto === null) return null;\n const data = Date.parse(texto);\n return Number.isNaN(data) ? null : { tipo, ordem: data };\n }\n\n return texto === null ? null : { tipo, ordem: texto };\n}\n\n/**\n * Tipos conhecidos por NOME de chave. É o que permite ao front continuar\n * passando só os caminhos — `[\"idg\", \"summary.last_updated\"]` — e ainda assim\n * ter `idg` comparado como número e `last_updated` como data, sem depender de\n * adivinhação pelo valor.\n *\n * A busca é feita pelo caminho completo e, se não achar, pelo último trecho\n * dele: \"summary.last_updated\" cai em \"last_updated\". Nomes fora desta tabela\n * (e de `monotonicTypes`) continuam sendo detectados pelo valor.\n */\nconst TIPOS_POR_NOME: Record<string, MonotonicType> = {\n idg: \"number\",\n versao: \"number\",\n ballots_counted: \"number\",\n last_updated: \"date\",\n};\n\n/** De onde veio o tipo usado para comparar uma chave. */\ntype OrigemDoTipo = \"declarado\" | \"conhecido\" | \"detectado\";\n\n/** Uma chave já normalizada: o caminho, o tipo dela e de onde o tipo veio. */\ninterface ChaveMonotonica {\n chave: string;\n /** null -> detectar pelo valor. */\n tipo: TipoComparacao | null;\n origem: OrigemDoTipo;\n}\n\n/**\n * Procura o tipo de uma chave na tabela de nomes: primeiro pelo caminho\n * completo (\"summary.last_updated\"), depois só pelo último trecho\n * (\"last_updated\").\n */\nfunction tipoConhecido(\n chave: string,\n tabela: Record<string, MonotonicType>,\n): MonotonicType | null {\n if (chave in tabela) return tabela[chave];\n const ultimo = chave.slice(chave.lastIndexOf(\".\") + 1);\n return ultimo in tabela ? tabela[ultimo] : null;\n}\n\n/**\n * Reduz as três formas aceitas de `monotonicKey` a uma lista única, já com o\n * tipo de cada chave resolvido: o declarado no mapa vence; senão vale o que a\n * tabela de nomes souber; senão fica `null` e o tipo é detectado pelo valor.\n */\nfunction normalizarChaves(\n monotonicKey: MonotonicKey,\n tabela: Record<string, MonotonicType>,\n): ChaveMonotonica[] {\n const semTipo = (chave: string): ChaveMonotonica => {\n const conhecido = tipoConhecido(chave, tabela);\n return conhecido\n ? { chave, tipo: conhecido, origem: \"conhecido\" }\n : { chave, tipo: null, origem: \"detectado\" };\n };\n\n if (typeof monotonicKey === \"string\") return [semTipo(monotonicKey)];\n if (Array.isArray(monotonicKey)) return monotonicKey.map(semTipo);\n\n return Object.entries(monotonicKey).map(([chave, tipo]) => ({\n chave,\n tipo,\n origem: \"declarado\" as const,\n }));\n}\n\n/** `a > b`, já sabendo que os dois lados são do mesmo tipo. */\nfunction maior(a: number | string, b: number | string): boolean {\n return typeof a === \"string\" ? a > String(b) : a > Number(b);\n}\n\n/** O que uma chave decidiu ao comparar a versão antiga com a nova. */\ninterface VotoMonotonico {\n chave: string;\n /** Tipo usado na comparação; `null` quando a chave é incomparável. */\n tipo: TipoComparacao | null;\n /** Se o tipo veio do mapa, da tabela de nomes ou da detecção pelo valor. */\n origem: OrigemDoTipo;\n oldVal: unknown;\n newVal: unknown;\n /** Qualquer resultado diferente de \"avançou\" derruba a resposta nova. */\n voto: \"avançou\" | \"não avançou\" | \"incomparável\";\n}\n\ninterface VereditoMonotonico {\n /** true -> aceitar o dado novo; false -> manter o que está em cache. */\n avancou: boolean;\n detalhes: VotoMonotonico[];\n}\n\n/**\n * Compara todas as chaves monotônicas entre o dado em cache e o dado novo.\n *\n * Verificação estrita: o dado novo só entra quando TODAS as chaves cresceram.\n * Basta uma que não cresça para a resposta ser descartada — e uma chave\n * incomparável (ausente de um dos lados, ilegível, fora do tipo esperado ou\n * com tipos divergentes entre as versões) também não cresceu, então também\n * derruba a resposta. Só entra o que comprovadamente cresceu em todas.\n */\nfunction avaliarMonotonicidade(\n oldData: unknown,\n newData: unknown,\n monotonicKey: MonotonicKey,\n tabela: Record<string, MonotonicType>,\n): VereditoMonotonico {\n const detalhes: VotoMonotonico[] = [];\n // Sem nenhuma chave configurada não há regra a aplicar, e o dado novo passa.\n let todasCresceram = true;\n\n // Avalia TODAS as chaves: nenhuma decide sozinha e nenhuma interrompe o laço,\n // para que o log de debug mostre o estado de cada uma.\n for (const { chave, tipo, origem } of normalizarChaves(monotonicKey, tabela)) {\n const oldVal = getPath(oldData, chave);\n const newVal = getPath(newData, chave);\n\n const oldCmp = tipo ? classificarComTipo(oldVal, tipo) : classificar(oldVal);\n const newCmp = tipo ? classificarComTipo(newVal, tipo) : classificar(newVal);\n\n // Quando o tipo é conhecido, ele é quem manda — `100` e `\"101\"` sob\n // \"number\" são o mesmo campo. Quando foi detectado pelo valor, tipos\n // divergentes entre as versões (número de um lado, string do outro; string\n // numérica vs. string de data) são sinal de que o campo mudou de forma.\n const divergente =\n tipo === null &&\n (typeof oldVal !== typeof newVal || oldCmp?.tipo !== newCmp?.tipo);\n\n // Incomparável não é \"neutro\": se a lib não consegue afirmar que a chave\n // cresceu, ela não cresceu, e a resposta nova cai.\n if (oldCmp === null || newCmp === null || divergente) {\n todasCresceram = false;\n detalhes.push({ chave, tipo: null, origem, oldVal, newVal, voto: \"incomparável\" });\n continue;\n }\n\n const avancou = maior(newCmp.ordem, oldCmp.ordem);\n if (!avancou) todasCresceram = false;\n detalhes.push({\n chave,\n tipo: newCmp.tipo,\n origem,\n oldVal,\n newVal,\n voto: avancou ? \"avançou\" : \"não avançou\",\n });\n }\n\n return { avancou: todasCresceram, detalhes };\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 8 s — base\r\n * 3g 12 s — 1,5x\r\n * 2g 16 s — 2x\r\n * slow-2g 24 s — 3x; borda de cobertura, interior\r\n *\r\n * A base fica logo abaixo de um ciclo de polling de 10 s: numa tela que\r\n * repete a chamada, a resposta que chega depois do próximo tick já nasce\r\n * velha — o tick novo traz dado mais fresco. Abortar a tempo deixa o ciclo\r\n * seguinte começar limpo, e o `staleOnError` cobre o buraco com o dado\r\n * anterior em vez de travar a UI.\r\n *\r\n * A folga ainda cresce com a rede ruim, e os tiers piores passam de um ciclo\r\n * de propósito: lá a deduplicação por rota faz o polling desacelerar sozinho,\r\n * o que é melhor do que martelar um client que não dá conta.\r\n *\r\n * O custo aparece em `fallbackUrls`, porque a fila é sequencial: o pior caso\r\n * é N x o tier — veja `timeoutMs` em GetFetchOptions.\r\n */\r\nexport const TIMEOUTS_POR_REDE: Record<EffectiveConnectionType | \"desconhecida\", number> = {\r\n \"slow-2g\": 24_000,\r\n \"2g\": 16_000,\r\n \"3g\": 12_000,\r\n \"4g\": 8_000,\r\n desconhecida: 8_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":"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"}
@@ -47,6 +47,28 @@ export type MonotonicType = "number" | "date" | "string";
47
47
  * declarado por nome de chave (recomendado: não depende de adivinhação).
48
48
  */
49
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
+ }
50
72
  export interface GetFetchOptions {
51
73
  /**
52
74
  * Caminho(s) da chave monotônica. Suporta aninhamento com ponto.
@@ -93,6 +115,21 @@ export interface GetFetchOptions {
93
115
  * (`url` + `fallbackUrls`). Útil para observabilidade/log.
94
116
  */
95
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[];
96
133
  }
97
134
  /** Uma entrada do cache. `rota` é a chave identificadora. */
98
135
  export interface CacheEntry<T = unknown> {
@@ -137,6 +174,15 @@ export interface RequestCacheConfig {
137
174
  * ("last_updated").
138
175
  */
139
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;
140
186
  }
141
187
  export declare class RequestCache {
142
188
  private storage;
@@ -148,6 +194,7 @@ export declare class RequestCache {
148
194
  private debug;
149
195
  private logger;
150
196
  private monotonicTypes;
197
+ private timeoutTiers;
151
198
  constructor(config?: RequestCacheConfig);
152
199
  /** Liga/desliga o modo debug em runtime, sem precisar recriar a instância. */
153
200
  setDebug(enabled: boolean): void;
@@ -172,10 +219,27 @@ export declare class RequestCache {
172
219
  private nextAccess;
173
220
  /** Emite uma linha de log, só quando o modo debug está ligado. */
174
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;
175
232
  /**
176
233
  * Tenta cada URL da lista em ordem (primário, depois os `fallbackUrls`).
177
234
  * Devolve o JSON da primeira que responder OK; se todas falharem, lança o
178
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.
179
243
  */
180
244
  private fetchComRedundancia;
181
245
  private revalidate;
@@ -186,5 +250,27 @@ export declare class RequestCache {
186
250
  private readAll;
187
251
  private writeAll;
188
252
  }
253
+ /**
254
+ * Tier list de timeout por qualidade de rede, em ms, aplicado a CADA tentativa.
255
+ *
256
+ * 4g / desconhecida 8 s — base
257
+ * 3g 12 s — 1,5x
258
+ * 2g 16 s — 2x
259
+ * slow-2g 24 s — 3x; borda de cobertura, interior
260
+ *
261
+ * A base fica logo abaixo de um ciclo de polling de 10 s: numa tela que
262
+ * repete a chamada, a resposta que chega depois do próximo tick já nasce
263
+ * velha — o tick novo traz dado mais fresco. Abortar a tempo deixa o ciclo
264
+ * seguinte começar limpo, e o `staleOnError` cobre o buraco com o dado
265
+ * anterior em vez de travar a UI.
266
+ *
267
+ * A folga ainda cresce com a rede ruim, e os tiers piores passam de um ciclo
268
+ * de propósito: lá a deduplicação por rota faz o polling desacelerar sozinho,
269
+ * o que é melhor do que martelar um client que não dá conta.
270
+ *
271
+ * O custo aparece em `fallbackUrls`, porque a fila é sequencial: o pior caso
272
+ * é N x o tier — veja `timeoutMs` em GetFetchOptions.
273
+ */
274
+ export declare const TIMEOUTS_POR_REDE: Record<EffectiveConnectionType | "desconhecida", number>;
189
275
  /** Instância pronta para uso, caso não queira configurar nada. */
190
276
  export declare const requestCache: RequestCache;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sevn/reqcache",
3
- "version": "1.3.0",
3
+ "version": "1.5.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",