/** * Domínio-guia da disciplina: o acervo de uma biblioteca. * * Este módulo faz o papel que, nas aulas do Módulo 2, cabe ao Prisma e ao * PostgreSQL: devolver dados depois de um tempo. A diferença é que aqui a * demora é declarada em milissegundos e as falhas são determinísticas — o * mesmo programa produz sempre a mesma sequência de eventos. * * Isso é deliberado. O material extra sobre assincronismo pede que o aluno * observe ordem e duração, e observação exige repetibilidade. Nenhum exemplo * deste projeto acessa a rede, a base de dados ou a API do Módulo 2. */ export type Livro = { id: number; titulo: string; autor: string; ano: number; isbn: string; }; export type Emprestimo = { id: number; livroId: number; leitor: string; dataEmprestimo: string; devolvido: boolean; }; const acervo: readonly Livro[] = [ { id: 1, titulo: 'Grande Sertão: Veredas', autor: 'João Guimarães Rosa', ano: 1956, isbn: '9788520925805' }, { id: 2, titulo: 'Memórias Póstumas de Brás Cubas', autor: 'Machado de Assis', ano: 1881, isbn: '9788535910667' }, { id: 3, titulo: 'Vidas Secas', autor: 'Graciliano Ramos', ano: 1938, isbn: '9788501069665' }, { id: 4, titulo: 'A Hora da Estrela', autor: 'Clarice Lispector', ano: 1977, isbn: '9788532508126' }, ]; const emprestimos: readonly Emprestimo[] = [ { id: 1, livroId: 1, leitor: 'ana', dataEmprestimo: '2026-03-02', devolvido: true }, { id: 2, livroId: 1, leitor: 'bruno', dataEmprestimo: '2026-04-11', devolvido: false }, { id: 3, livroId: 3, leitor: 'carla', dataEmprestimo: '2026-05-20', devolvido: false }, ]; /** Erro de domínio: o identificador não existe no acervo. */ export class LivroNaoEncontradoError extends Error { constructor(public readonly livroId: number) { super(`Livro ${livroId} não existe no acervo`); this.name = 'LivroNaoEncontradoError'; } } /** Erro de infraestrutura: o serviço externo de capas não respondeu. */ export class CatalogoIndisponivelError extends Error { constructor(mensagem: string, opcoes?: { cause: unknown }) { super(mensagem, opcoes); this.name = 'CatalogoIndisponivelError'; } } /** * A ponte entre o mundo de callbacks e o mundo de Promises. * * `setTimeout` é uma API de callback: devolve um identificador para cancelar * o timer e chama a função depois. Envolvê-la em uma Promise é o padrão que a Parte 2 do material * generaliza. */ export function esperar(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } /** Leitura de um livro: 300 ms de latência simulada. */ export async function buscarLivro(id: number): Promise { await esperar(300); const livro = acervo.find((item) => item.id === id); if (livro === undefined) { throw new LivroNaoEncontradoError(id); } return livro; } /** Listagem do acervo: 200 ms de latência simulada. */ export async function listarLivros(): Promise { await esperar(200); return [...acervo]; } /** Empréstimos em aberto de um livro: 250 ms de latência simulada. */ export async function contarEmprestimosAbertos(livroId: number): Promise { await esperar(250); return emprestimos.filter((item) => item.livroId === livroId && !item.devolvido).length; } /** * Registro de um empréstimo: 200 ms, e rejeita se o livro não existir. * É a operação usada nos exemplos de promise solta — escrita como um efeito * colateral que se costuma disparar "e esquecer". */ export async function salvarEmprestimo(livroId: number, leitor: string): Promise { await esperar(200); if (!acervo.some((item) => item.id === livroId)) { throw new LivroNaoEncontradoError(livroId); } return { id: emprestimos.length + 1, livroId, leitor, dataEmprestimo: new Date().toISOString().slice(0, 10), devolvido: false, }; } /** * Serviço externo de capas — instável de propósito. * * Falha nas duas primeiras chamadas de cada processo e responde na terceira. * É o alvo do exemplo de repetição com recuo (Parte 6). */ let tentativasCatalogo = 0; export async function consultarCatalogoExterno(isbn: string): Promise<{ isbn: string; capa: string }> { tentativasCatalogo += 1; await esperar(150); if (tentativasCatalogo < 3) { throw new CatalogoIndisponivelError( `Catálogo externo indisponível (tentativa ${tentativasCatalogo})`, ); } return { isbn, capa: `https://capas.exemplo.org/${isbn}.jpg` }; } /** Reinicia o contador de falhas do catálogo, para rodar o exemplo de novo. */ export function reiniciarCatalogo(): void { tentativasCatalogo = 0; } /** * Cronômetro de saída: devolve uma função que imprime o rótulo com o tempo * decorrido desde a criação. É o instrumento de medida dos exemplos — sem ele * "sequencial" e "concorrente" imprimem exatamente a mesma coisa. */ export function cronometro(): (rotulo: string) => void { const inicio = Date.now(); return (rotulo: string) => { const decorrido = String(Date.now() - inicio).padStart(4, ' '); console.log(`${decorrido} ms ${rotulo}`); }; } /** Mensagem segura para qualquer motivo de rejeição. */ export function mensagemDeErro(erro: unknown): string { if (erro instanceof Error) return erro.message; if (typeof erro === 'string') return erro; return 'erro desconhecido'; }