@sevn/reqcache 1.1.0 → 1.3.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/README.md +115 -1
- package/dist/index.d.ts +77 -7
- package/dist/index.esm.js +215 -11
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +215 -11
- package/dist/index.js.map +1 -1
- package/dist/request-cache.d.ts +76 -6
- package/package.json +1 -1
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.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\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\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\n constructor(config: RequestCacheConfig = {}) {\n this.storageKey = config.storageKey ?? \"reqcache\";\n this.maxEntries = config.maxEntries ?? 100;\n this.storage = config.storage ?? getDefaultStorage();\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 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) return pending;\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 return all.length - mantidas.length;\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 /**\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 try {\n const res = await fetcher(candidato, options.fetchOptions);\n if (!res.ok) throw new Error(`HTTP ${res.status} ao buscar ${candidato}`);\n return (await res.json()) as T;\n } catch (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.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.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.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 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 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/** 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.esm.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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"}
|
package/dist/index.js
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
* 1. Primeira chamada -> faz o fetch e grava a entrada da rota no array.
|
|
20
20
|
* 2. Durante o TTL -> devolve os dados do cache, sem tocar na rede.
|
|
21
21
|
* 3. Após expirar -> SÓ revalida quando o client chamar de novo (lazy).
|
|
22
|
-
* Se houver `monotonicKey`, só aceita o novo dado quando
|
|
23
|
-
*
|
|
22
|
+
* Se houver `monotonicKey`, só aceita o novo dado quando TODAS as chaves
|
|
23
|
+
* tiverem AVANÇADO; senão mantém o antigo.
|
|
24
24
|
*
|
|
25
25
|
* Proteção de espaço (limite de ~5 MB do localStorage):
|
|
26
26
|
* - `maxEntries`: ao gravar, se passar do limite, remove a rota menos
|
|
@@ -34,13 +34,20 @@
|
|
|
34
34
|
*/
|
|
35
35
|
class RequestCache {
|
|
36
36
|
constructor(config = {}) {
|
|
37
|
-
var _a, _b, _c;
|
|
37
|
+
var _a, _b, _c, _d, _e, _f;
|
|
38
38
|
this.inflight = new Map();
|
|
39
39
|
/** Relógio de acesso monotônico: sempre cresce, mesmo com acessos no mesmo ms. */
|
|
40
40
|
this.tick = 0;
|
|
41
41
|
this.storageKey = (_a = config.storageKey) !== null && _a !== void 0 ? _a : "reqcache";
|
|
42
42
|
this.maxEntries = (_b = config.maxEntries) !== null && _b !== void 0 ? _b : 100;
|
|
43
43
|
this.storage = (_c = config.storage) !== null && _c !== void 0 ? _c : getDefaultStorage();
|
|
44
|
+
this.debug = (_d = config.debug) !== null && _d !== void 0 ? _d : false;
|
|
45
|
+
this.logger = (_e = config.logger) !== null && _e !== void 0 ? _e : defaultLogger;
|
|
46
|
+
this.monotonicTypes = { ...TIPOS_POR_NOME, ...((_f = config.monotonicTypes) !== null && _f !== void 0 ? _f : {}) };
|
|
47
|
+
}
|
|
48
|
+
/** Liga/desliga o modo debug em runtime, sem precisar recriar a instância. */
|
|
49
|
+
setDebug(enabled) {
|
|
50
|
+
this.debug = enabled;
|
|
44
51
|
}
|
|
45
52
|
/**
|
|
46
53
|
* Busca uma URL usando cache.
|
|
@@ -56,13 +63,17 @@
|
|
|
56
63
|
if (cached && now < cached.expiresAt) {
|
|
57
64
|
cached.lastAccess = this.nextAccess(); // marca uso p/ o LRU
|
|
58
65
|
this.writeAll(all);
|
|
66
|
+
this.log("cache hit", { url, expiresAt: new Date(cached.expiresAt).toISOString() });
|
|
59
67
|
return cached.data;
|
|
60
68
|
}
|
|
61
69
|
// 2. Deduplicação: se já há uma requisição em andamento p/ essa rota,
|
|
62
70
|
// todas as chamadas concorrentes aguardam a mesma Promise.
|
|
63
71
|
const pending = this.inflight.get(url);
|
|
64
|
-
if (pending)
|
|
72
|
+
if (pending) {
|
|
73
|
+
this.log("dedup: aguardando requisição em andamento", { url });
|
|
65
74
|
return pending;
|
|
75
|
+
}
|
|
76
|
+
this.log(cached ? "cache expirado, revalidando" : "cache miss", { url });
|
|
66
77
|
const promise = this.revalidate(url, ttlMs, options, cached !== null && cached !== void 0 ? cached : null)
|
|
67
78
|
.finally(() => this.inflight.delete(url));
|
|
68
79
|
this.inflight.set(url, promise);
|
|
@@ -83,7 +94,10 @@
|
|
|
83
94
|
const all = this.readAll();
|
|
84
95
|
const mantidas = all.filter((e) => e.expiresAt > limite);
|
|
85
96
|
this.writeAll(mantidas);
|
|
86
|
-
|
|
97
|
+
const removidas = all.length - mantidas.length;
|
|
98
|
+
if (removidas > 0)
|
|
99
|
+
this.log("cleanup: rotas expiradas removidas", { removidas });
|
|
100
|
+
return removidas;
|
|
87
101
|
}
|
|
88
102
|
/** Esvazia todo o cache. */
|
|
89
103
|
clear() {
|
|
@@ -102,6 +116,12 @@
|
|
|
102
116
|
this.tick = Math.max(Date.now(), this.tick + 1);
|
|
103
117
|
return this.tick;
|
|
104
118
|
}
|
|
119
|
+
/** Emite uma linha de log, só quando o modo debug está ligado. */
|
|
120
|
+
log(message, details) {
|
|
121
|
+
if (!this.debug)
|
|
122
|
+
return;
|
|
123
|
+
this.logger(message, details);
|
|
124
|
+
}
|
|
105
125
|
/**
|
|
106
126
|
* Tenta cada URL da lista em ordem (primário, depois os `fallbackUrls`).
|
|
107
127
|
* Devolve o JSON da primeira que responder OK; se todas falharem, lança o
|
|
@@ -111,13 +131,16 @@
|
|
|
111
131
|
var _a;
|
|
112
132
|
let ultimoErro;
|
|
113
133
|
for (const candidato of candidatos) {
|
|
134
|
+
this.log("fetch de rede", { url: candidato });
|
|
114
135
|
try {
|
|
115
136
|
const res = await fetcher(candidato, options.fetchOptions);
|
|
116
137
|
if (!res.ok)
|
|
117
138
|
throw new Error(`HTTP ${res.status} ao buscar ${candidato}`);
|
|
139
|
+
this.log("fetch OK", { url: candidato, status: res.status });
|
|
118
140
|
return (await res.json());
|
|
119
141
|
}
|
|
120
142
|
catch (err) {
|
|
143
|
+
this.log("fetch falhou", { url: candidato, erro: String(err) });
|
|
121
144
|
ultimoErro = err;
|
|
122
145
|
(_a = options.onFallback) === null || _a === void 0 ? void 0 : _a.call(options, candidato, err);
|
|
123
146
|
}
|
|
@@ -135,6 +158,7 @@
|
|
|
135
158
|
catch (err) {
|
|
136
159
|
// Todos os domínios falharam. Se temos dado antigo e staleOnError, devolve o antigo.
|
|
137
160
|
if (cached && ((_c = options.staleOnError) !== null && _c !== void 0 ? _c : true)) {
|
|
161
|
+
this.log("staleOnError: devolvendo dado antigo", { url });
|
|
138
162
|
this.upsert({
|
|
139
163
|
...cached,
|
|
140
164
|
expiresAt: Date.now() + ttlMs,
|
|
@@ -144,13 +168,16 @@
|
|
|
144
168
|
}
|
|
145
169
|
throw err;
|
|
146
170
|
}
|
|
147
|
-
// 3. Regra monotônica: só aceita o novo dado se
|
|
171
|
+
// 3. Regra monotônica: só aceita o novo dado se TODAS as chaves que
|
|
172
|
+
// conseguiram votar tiverem avançado.
|
|
148
173
|
if (cached && options.monotonicKey) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
const veredito = avaliarMonotonicidade(cached.data, fresh, options.monotonicKey, this.monotonicTypes);
|
|
175
|
+
if (!veredito.avancou) {
|
|
176
|
+
// Alguma chave não avançou -> mantém o dado antigo, só renova a expiração.
|
|
177
|
+
this.log("regra monotônica: valor não cresceu, mantendo dado antigo", {
|
|
178
|
+
url,
|
|
179
|
+
chaves: veredito.detalhes,
|
|
180
|
+
});
|
|
154
181
|
this.upsert({
|
|
155
182
|
...cached,
|
|
156
183
|
expiresAt: Date.now() + ttlMs,
|
|
@@ -161,6 +188,7 @@
|
|
|
161
188
|
}
|
|
162
189
|
// 4. Grava e devolve o dado novo.
|
|
163
190
|
const now = Date.now();
|
|
191
|
+
this.log("cache gravado", { url, ttlMs });
|
|
164
192
|
this.upsert({
|
|
165
193
|
rota: url,
|
|
166
194
|
data: fresh,
|
|
@@ -185,6 +213,7 @@
|
|
|
185
213
|
if (all[i].lastAccess < all[idxMaisAntigo].lastAccess)
|
|
186
214
|
idxMaisAntigo = i;
|
|
187
215
|
}
|
|
216
|
+
this.log("LRU: removendo rota menos usada", { url: all[idxMaisAntigo].rota });
|
|
188
217
|
all.splice(idxMaisAntigo, 1);
|
|
189
218
|
}
|
|
190
219
|
}
|
|
@@ -213,6 +242,10 @@
|
|
|
213
242
|
if (isQuotaError(err) && all.length > 0) {
|
|
214
243
|
const reduzido = [...all].sort((a, b) => a.lastAccess - b.lastAccess);
|
|
215
244
|
reduzido.splice(0, Math.ceil(reduzido.length / 2)); // descarta metade
|
|
245
|
+
this.log("quota excedida: descartando metade das entradas mais antigas", {
|
|
246
|
+
totalAntes: all.length,
|
|
247
|
+
totalDepois: reduzido.length,
|
|
248
|
+
});
|
|
216
249
|
try {
|
|
217
250
|
this.storage.setItem(this.storageKey, JSON.stringify(reduzido));
|
|
218
251
|
}
|
|
@@ -233,11 +266,182 @@
|
|
|
233
266
|
return undefined;
|
|
234
267
|
}, obj);
|
|
235
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Descobre como comparar um valor. A ordem das checagens NÃO pode ser trocada:
|
|
271
|
+
* `Date.parse` aceita strings numéricas curtas como data (`Date.parse("9")` cai
|
|
272
|
+
* em setembro, `Date.parse("10")` em outubro), então um `idg` curto viraria
|
|
273
|
+
* data se a checagem de data viesse antes da numérica. No sentido inverso não
|
|
274
|
+
* há risco: `Number("2026-10-04T18:23:00Z")` é NaN.
|
|
275
|
+
*
|
|
276
|
+
* Devolve `null` quando o valor é incomparável.
|
|
277
|
+
*/
|
|
278
|
+
function classificar(valor) {
|
|
279
|
+
if (typeof valor === "number") {
|
|
280
|
+
return Number.isFinite(valor) ? { tipo: "number", ordem: valor } : null;
|
|
281
|
+
}
|
|
282
|
+
if (typeof valor !== "string")
|
|
283
|
+
return null;
|
|
284
|
+
// String vazia ou em branco é incomparável: `Number("")` e `Number(" ")`
|
|
285
|
+
// devolvem 0, e um campo vazio viraria um "zero" comparável.
|
|
286
|
+
if (valor.trim() === "")
|
|
287
|
+
return null;
|
|
288
|
+
const numero = Number(valor);
|
|
289
|
+
if (Number.isFinite(numero))
|
|
290
|
+
return { tipo: "number", ordem: numero };
|
|
291
|
+
const data = Date.parse(valor);
|
|
292
|
+
if (!Number.isNaN(data))
|
|
293
|
+
return { tipo: "date", ordem: data };
|
|
294
|
+
return { tipo: "string", ordem: valor };
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Classifica um valor segundo um tipo DECLARADO pelo chamador (forma de mapa).
|
|
298
|
+
* Como o tipo veio declarado, não há adivinhação: o valor ou serve, ou a chave
|
|
299
|
+
* é incomparável (e derruba a resposta nova). É também mais tolerante que a
|
|
300
|
+
* detecção por valor — com
|
|
301
|
+
* `"number"`, `100` e `"101"` comparam entre si sem problema, já que o
|
|
302
|
+
* chamador afirmou que aquele campo é numérico.
|
|
303
|
+
*/
|
|
304
|
+
function classificarComTipo(valor, tipo) {
|
|
305
|
+
// String vazia ou em branco nunca é comparável: `Number("")` e `Number(" ")`
|
|
306
|
+
// devolvem 0, e um campo vazio viraria um "zero" comparável.
|
|
307
|
+
const texto = typeof valor === "string" ? valor.trim() : null;
|
|
308
|
+
if (texto === "")
|
|
309
|
+
return null;
|
|
310
|
+
if (tipo === "number") {
|
|
311
|
+
if (typeof valor === "number") {
|
|
312
|
+
return Number.isFinite(valor) ? { tipo, ordem: valor } : null;
|
|
313
|
+
}
|
|
314
|
+
if (texto === null)
|
|
315
|
+
return null;
|
|
316
|
+
const numero = Number(texto);
|
|
317
|
+
return Number.isFinite(numero) ? { tipo, ordem: numero } : null;
|
|
318
|
+
}
|
|
319
|
+
if (tipo === "date") {
|
|
320
|
+
// Um número aqui é lido como epoch em ms.
|
|
321
|
+
if (typeof valor === "number") {
|
|
322
|
+
return Number.isFinite(valor) ? { tipo, ordem: valor } : null;
|
|
323
|
+
}
|
|
324
|
+
if (texto === null)
|
|
325
|
+
return null;
|
|
326
|
+
const data = Date.parse(texto);
|
|
327
|
+
return Number.isNaN(data) ? null : { tipo, ordem: data };
|
|
328
|
+
}
|
|
329
|
+
return texto === null ? null : { tipo, ordem: texto };
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Tipos conhecidos por NOME de chave. É o que permite ao front continuar
|
|
333
|
+
* passando só os caminhos — `["idg", "summary.last_updated"]` — e ainda assim
|
|
334
|
+
* ter `idg` comparado como número e `last_updated` como data, sem depender de
|
|
335
|
+
* adivinhação pelo valor.
|
|
336
|
+
*
|
|
337
|
+
* A busca é feita pelo caminho completo e, se não achar, pelo último trecho
|
|
338
|
+
* dele: "summary.last_updated" cai em "last_updated". Nomes fora desta tabela
|
|
339
|
+
* (e de `monotonicTypes`) continuam sendo detectados pelo valor.
|
|
340
|
+
*/
|
|
341
|
+
const TIPOS_POR_NOME = {
|
|
342
|
+
idg: "number",
|
|
343
|
+
versao: "number",
|
|
344
|
+
ballots_counted: "number",
|
|
345
|
+
last_updated: "date",
|
|
346
|
+
};
|
|
347
|
+
/**
|
|
348
|
+
* Procura o tipo de uma chave na tabela de nomes: primeiro pelo caminho
|
|
349
|
+
* completo ("summary.last_updated"), depois só pelo último trecho
|
|
350
|
+
* ("last_updated").
|
|
351
|
+
*/
|
|
352
|
+
function tipoConhecido(chave, tabela) {
|
|
353
|
+
if (chave in tabela)
|
|
354
|
+
return tabela[chave];
|
|
355
|
+
const ultimo = chave.slice(chave.lastIndexOf(".") + 1);
|
|
356
|
+
return ultimo in tabela ? tabela[ultimo] : null;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Reduz as três formas aceitas de `monotonicKey` a uma lista única, já com o
|
|
360
|
+
* tipo de cada chave resolvido: o declarado no mapa vence; senão vale o que a
|
|
361
|
+
* tabela de nomes souber; senão fica `null` e o tipo é detectado pelo valor.
|
|
362
|
+
*/
|
|
363
|
+
function normalizarChaves(monotonicKey, tabela) {
|
|
364
|
+
const semTipo = (chave) => {
|
|
365
|
+
const conhecido = tipoConhecido(chave, tabela);
|
|
366
|
+
return conhecido
|
|
367
|
+
? { chave, tipo: conhecido, origem: "conhecido" }
|
|
368
|
+
: { chave, tipo: null, origem: "detectado" };
|
|
369
|
+
};
|
|
370
|
+
if (typeof monotonicKey === "string")
|
|
371
|
+
return [semTipo(monotonicKey)];
|
|
372
|
+
if (Array.isArray(monotonicKey))
|
|
373
|
+
return monotonicKey.map(semTipo);
|
|
374
|
+
return Object.entries(monotonicKey).map(([chave, tipo]) => ({
|
|
375
|
+
chave,
|
|
376
|
+
tipo,
|
|
377
|
+
origem: "declarado",
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
/** `a > b`, já sabendo que os dois lados são do mesmo tipo. */
|
|
381
|
+
function maior(a, b) {
|
|
382
|
+
return typeof a === "string" ? a > String(b) : a > Number(b);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Compara todas as chaves monotônicas entre o dado em cache e o dado novo.
|
|
386
|
+
*
|
|
387
|
+
* Verificação estrita: o dado novo só entra quando TODAS as chaves cresceram.
|
|
388
|
+
* Basta uma que não cresça para a resposta ser descartada — e uma chave
|
|
389
|
+
* incomparável (ausente de um dos lados, ilegível, fora do tipo esperado ou
|
|
390
|
+
* com tipos divergentes entre as versões) também não cresceu, então também
|
|
391
|
+
* derruba a resposta. Só entra o que comprovadamente cresceu em todas.
|
|
392
|
+
*/
|
|
393
|
+
function avaliarMonotonicidade(oldData, newData, monotonicKey, tabela) {
|
|
394
|
+
const detalhes = [];
|
|
395
|
+
// Sem nenhuma chave configurada não há regra a aplicar, e o dado novo passa.
|
|
396
|
+
let todasCresceram = true;
|
|
397
|
+
// Avalia TODAS as chaves: nenhuma decide sozinha e nenhuma interrompe o laço,
|
|
398
|
+
// para que o log de debug mostre o estado de cada uma.
|
|
399
|
+
for (const { chave, tipo, origem } of normalizarChaves(monotonicKey, tabela)) {
|
|
400
|
+
const oldVal = getPath(oldData, chave);
|
|
401
|
+
const newVal = getPath(newData, chave);
|
|
402
|
+
const oldCmp = tipo ? classificarComTipo(oldVal, tipo) : classificar(oldVal);
|
|
403
|
+
const newCmp = tipo ? classificarComTipo(newVal, tipo) : classificar(newVal);
|
|
404
|
+
// Quando o tipo é conhecido, ele é quem manda — `100` e `"101"` sob
|
|
405
|
+
// "number" são o mesmo campo. Quando foi detectado pelo valor, tipos
|
|
406
|
+
// divergentes entre as versões (número de um lado, string do outro; string
|
|
407
|
+
// numérica vs. string de data) são sinal de que o campo mudou de forma.
|
|
408
|
+
const divergente = tipo === null &&
|
|
409
|
+
(typeof oldVal !== typeof newVal || (oldCmp === null || oldCmp === void 0 ? void 0 : oldCmp.tipo) !== (newCmp === null || newCmp === void 0 ? void 0 : newCmp.tipo));
|
|
410
|
+
// Incomparável não é "neutro": se a lib não consegue afirmar que a chave
|
|
411
|
+
// cresceu, ela não cresceu, e a resposta nova cai.
|
|
412
|
+
if (oldCmp === null || newCmp === null || divergente) {
|
|
413
|
+
todasCresceram = false;
|
|
414
|
+
detalhes.push({ chave, tipo: null, origem, oldVal, newVal, voto: "incomparável" });
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const avancou = maior(newCmp.ordem, oldCmp.ordem);
|
|
418
|
+
if (!avancou)
|
|
419
|
+
todasCresceram = false;
|
|
420
|
+
detalhes.push({
|
|
421
|
+
chave,
|
|
422
|
+
tipo: newCmp.tipo,
|
|
423
|
+
origem,
|
|
424
|
+
oldVal,
|
|
425
|
+
newVal,
|
|
426
|
+
voto: avancou ? "avançou" : "não avançou",
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
return { avancou: todasCresceram, detalhes };
|
|
430
|
+
}
|
|
236
431
|
function isQuotaError(err) {
|
|
237
432
|
return (err instanceof Error &&
|
|
238
433
|
(err.name === "QuotaExceededError" ||
|
|
239
434
|
err.name === "NS_ERROR_DOM_QUOTA_REACHED"));
|
|
240
435
|
}
|
|
436
|
+
/** Logger padrão do modo debug: imprime no console com um prefixo fixo. */
|
|
437
|
+
function defaultLogger(message, details) {
|
|
438
|
+
if (details) {
|
|
439
|
+
console.debug(`[reqcache] ${message}`, details);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
console.debug(`[reqcache] ${message}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
241
445
|
/** Retorna localStorage se existir e funcionar; senão null (SSR, modo privado...). */
|
|
242
446
|
function getDefaultStorage() {
|
|
243
447
|
try {
|
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\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\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\n constructor(config: RequestCacheConfig = {}) {\n this.storageKey = config.storageKey ?? \"reqcache\";\n this.maxEntries = config.maxEntries ?? 100;\n this.storage = config.storage ?? getDefaultStorage();\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 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) return pending;\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 return all.length - mantidas.length;\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 /**\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 try {\n const res = await fetcher(candidato, options.fetchOptions);\n if (!res.ok) throw new Error(`HTTP ${res.status} ao buscar ${candidato}`);\n return (await res.json()) as T;\n } catch (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.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.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.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 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 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/** 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":["/**\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":";;;;;;IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2BG;UAiIU,YAAY,CAAA;IAWvB,IAAA,WAAA,CAAY,SAA6B,EAAE,EAAA;;IAPnC,QAAA,IAAA,CAAA,QAAQ,GAAG,IAAI,GAAG,EAA4B;;YAE9C,IAAA,CAAA,IAAI,GAAG,CAAC;YAMd,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;QAC/E;;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;;;;IAIG;IACK,IAAA,MAAM,mBAAmB,CAC/B,UAAoB,EACpB,OAAqB,EACrB,OAAwB,EAAA;;IAExB,QAAA,IAAI,UAAmB;IAEvB,QAAA,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE;gBAClC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;IAC7C,YAAA,IAAI;oBACF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC;oBAC1D,IAAI,CAAC,GAAG,CAAC,EAAE;wBAAE,MAAM,IAAI,KAAK,CAAC,CAAA,KAAA,EAAQ,GAAG,CAAC,MAAM,CAAA,WAAA,EAAc,SAAS,CAAA,CAAE,CAAC;IACzE,gBAAA,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAC5D,gBAAA,QAAQ,MAAM,GAAG,CAAC,IAAI,EAAE;gBAC1B;gBAAE,OAAO,GAAG,EAAE;IACZ,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;gBACtC;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,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;;;;;;;;;"}
|