// O mesmo cliente sobre fetch, sem dependência. O que o axios faz por // configuração aparece aqui como código — e cabe numa função. import { API_URL } from './api.js'; import { ErroDaApi, ehCorpoDeErro, sessao, type ClienteDaApi } from './erro-da-api.js'; const TEMPO_LIMITE_MS = 5000; async function requisitar( baseURL: string, metodo: string, caminho: string, corpo?: unknown, ): Promise { // O papel do interceptor de requisição. const cabecalhos = new Headers(); if (sessao.token) { cabecalhos.set('Authorization', `Bearer ${sessao.token}`); } // O que o axios faz sozinho ao receber um objeto: serializar e declarar. if (corpo !== undefined) { cabecalhos.set('Content-Type', 'application/json'); } let resposta: Response; try { resposta = await fetch(`${baseURL}${caminho}`, { method: metodo, headers: cabecalhos, body: corpo === undefined ? undefined : JSON.stringify(corpo), // O equivalente à opção `timeout` do axios. signal: AbortSignal.timeout(TEMPO_LIMITE_MS), }); } catch (erro) { // Sem resposta: rede, porta errada, tempo esgotado. // No Node, a falha de rede chega como TypeError com o código na causa. const causa = erro instanceof Error ? erro.cause : undefined; const motivo = causa instanceof Error && 'code' in causa ? String(causa.code) : erro instanceof Error ? erro.name : String(erro); throw new ErroDaApi(null, [motivo], { cause: erro }); } // O papel do interceptor de resposta — e o que o axios decide por // `validateStatus`: fora da faixa 2xx é falha. if (!resposta.ok) { const conteudo: unknown = await resposta.json().catch(() => null); const detalhes = ehCorpoDeErro(conteudo) ? conteudo.detalhes : [resposta.statusText]; throw new ErroDaApi(resposta.status, detalhes); } // 204 não tem corpo; ler JSON dele lançaria SyntaxError (exemplo 03). if (resposta.status === 204) { return undefined as T; } return (await resposta.json()) as T; } export function criarClienteFetch(baseURL = API_URL): ClienteDaApi { return { get: (caminho: string) => requisitar(baseURL, 'GET', caminho), post: (caminho: string, corpo: unknown) => requisitar(baseURL, 'POST', caminho, corpo), patch: (caminho: string, corpo: unknown) => requisitar(baseURL, 'PATCH', caminho, corpo), delete: (caminho: string) => requisitar(baseURL, 'DELETE', caminho), }; }