Problem Statement
A Bible API pública (api.midvash.com) cresceu organicamente e acumulou dívida arquitetural que torna mudanças custosas e propensas a bugs. O mesmo pipeline de domínio (buscar capítulo/versículo) é implementado três vezes com cache boilerplate repetido ~12 vezes. Módulos são rasos — sua interface é tão complexa quanto sua implementação — e não há testes.
Cada correção ou feature nova exige editar múltiplos arquivos, e a falta de seams claros impede testar a lógica de domínio isoladamente.
Solution
Extrair um módulo VerseService profundo que concentre todo o pipeline de capítulo/versículo atrás de uma interface simples. Extrair o boilerplate de cache em um middleware reutilizável. Unificar a construção de erros. Decompor o monólito da landing page. O resultado é um código com alta localidade (uma mudança, um arquivo) e alta alavancagem (mudanças no pipeline ou cache tocam um módulo, não três).
User Stories
- Como desenvolvedor da API, quero que alterações no pipeline de versículos (validação, parsing, formatação) toquem um único módulo, para que bugs não precisem ser corrigidos em três lugares.
- Como desenvolvedor da API, quero que a estratégia de cache (cache key, ETag, HEAD handling) seja configurada em um único lugar, para que mudanças no cache não exijam editar 12 ocorrências.
- Como desenvolvedor da API, quero testar a lógica de domínio (buscar capítulo, extrair versículos, formatar referência) sem infraestrutura de rede, para que os testes rodem rápido e de forma determinística.
- Como desenvolvedor da API, quero que handlers HTTP sejam thin adapters que só traduzem request → chamada de domínio → response, para que cada handler tenha uma única responsabilidade.
- Como desenvolvedor da API, quero que erros HTTP sigam um padrão único independente da versão da API (legacy vs v1), para que adicionar um novo endpoint não exiga escolher entre 3 padrões de erro.
- Como desenvolvedor da API, quero navegar e editar a landing page sem precisar entender 1700 linhas num arquivo só, para que mudanças de copy/CSS/JS sejam localizadas.
- Como mantenedor, quero que a adição de um novo locale (idioma) exija mudança em um único ponto de definição, para que o risco de inconsistência entre books.ts, locale.ts e i18n.ts seja eliminado.
- Como desenvolvedor, quero que cache keys para endpoints inexistentes (/characters, /dictionary) não sejam normalizados com lógica morta, para que endpoints futuros não tenham cache key corrompida.
Implementation Decisions
Módulo VerseService (profundo)
- Novo módulo
src/lib/verse-service.ts com interface:
fetchVerses(ctx, params: { version, bookSlug, chapter, verses? }) → VerseResult
where VerseResult = { data: ChapterData, meta: Meta } | { error: ApiError }
- O módulo é profundo: sua interface (uma única função) esconde cache strategy, LRU, R2 multi-format parsing, verse parsing, extração de range, formatação de referência.
- O teste de deleção: deletar VerseService forçaria os 3 handlers a reimplementar todo o pipeline — a complexidade se concentraria, não se moveria.
- Os 3 handlers atuais (legacy.ts, v1/chapters.ts, votd.ts) tornam-se adapters finos que mapeiam HTTP request → chamada ao VerseService → HTTP response com envelope apropriado.
Middleware de cache (aspecto transversal)
- Extrair o boilerplate de cache (~12 ocorrências) para um wrapper:
withCache(ctx, request, label, handler: (request) => Response) → Response
- O wrapper é um aspecto transversal: normalize cache key → check cache → serve from cache → executa handler → armazena no cache → adapta HEAD/304.
- Seam: a interface Cache API do Cloudflare é um adapter. Adicionar um segundo adapter (ex: MemoryCache para testes) tornaria o seam real ("one adapter = hypothetical seam, two = real").
Unificação de erros
- Função única
apiError(request, code, message, details?) que constrói o envelope correto (legacy { error } vs v1 { error, meta }) baseado no path.
- Elimina as 3 implementações paralelas atuais.
Decomposição da landing page
landing/page.ts (1713 linhas) dividido em:
landing/page.css.ts — CSS como template strings
landing/page.svg.ts — ícones SVG como componentes
landing/page.client.ts — JavaScript client-side (tabs, runner, copy)
landing/page.templates.ts — funções de template HTML puras
landing/page.ts — composer fino que importa e orquestra
- Cada sub-módulo é profundo para sua preocupação: uma interface (1 export) esconde toda a complexidade de CSS/SVG/JS/templates.
Consolidação do tipo Locale
- Tipo
Locale (presente em 3 arquivos: books.ts, locale.ts, i18n.ts) movido para src/lib/locale.ts como fonte única da verdade.
- Os outros dois arquivos importam o tipo.
Remoção de código morto
- Remover referências a
/characters e /dictionary em normalizeCacheKey (lib/cache.ts:28-30) — endpoints que não existem.
Testing Decisions
- O que faz um bom teste: testar comportamento externo, não implementação. Testar a interface do módulo, não suas funções internas. Um teste do VerseService chama
fetchVerses() com params conhecidos e verifica o resultado — não testa se parseVerseParam foi chamado ou se o LRU cache foi usado.
- Módulos testáveis via interface pública: VerseService (mock R2 + mock Cache API via withCache), middleware
withCache, funções puras em lib/chapter.ts (parseVerseParam, extractVerses, formatReference), lib/locale.ts (normalizeLocale), lib/response.ts (okResponse, errorResponse), lib/votd-pool.ts (pickVotdForDate, dayOfYearUtc).
- Seam de teste principal: o middleware
withCache é o seam para testar handlers sem cache real. Um adapter MemoryCache implementa a mesma interface do Cache API.
- Test runner: Vitest (já é o mais comum em projetos Cloudflare Workers, e há prior art na comunidade).
Out of Scope
- Adicionar novos endpoints ou versões de API.
- Mudar o schema de dados no R2.
- Alterar o formato de resposta da API (breaking changes).
- Adicionar autenticação ou rate limiting.
- Migrar para outro runtime que não Cloudflare Workers.
- Refatorar o conteúdo dos arquivos de tradução (i18n.ts, docs.ts) — apenas sua estrutura.
Further Notes
- Este PRD não propõe interfaces finais para os módulos — as interfaces serão detalhadas durante a implementação via grillagem com o desenvolvedor.
- Nenhum ADR existente é contradito por este PRD (não há ADRs no repositório).
- A ordem de implementação recomendada: (1) middleware withCache, (2) VerseService, (3) unificação de erros, (4) landing page decomposition, (5) locale type consolidation, (6) dead code removal. A ordem prioriza criar o seam de cache primeiro, pois o VerseService depende dele.
Problem Statement
A Bible API pública (api.midvash.com) cresceu organicamente e acumulou dívida arquitetural que torna mudanças custosas e propensas a bugs. O mesmo pipeline de domínio (buscar capítulo/versículo) é implementado três vezes com cache boilerplate repetido ~12 vezes. Módulos são rasos — sua interface é tão complexa quanto sua implementação — e não há testes.
Cada correção ou feature nova exige editar múltiplos arquivos, e a falta de seams claros impede testar a lógica de domínio isoladamente.
Solution
Extrair um módulo VerseService profundo que concentre todo o pipeline de capítulo/versículo atrás de uma interface simples. Extrair o boilerplate de cache em um middleware reutilizável. Unificar a construção de erros. Decompor o monólito da landing page. O resultado é um código com alta localidade (uma mudança, um arquivo) e alta alavancagem (mudanças no pipeline ou cache tocam um módulo, não três).
User Stories
Implementation Decisions
Módulo VerseService (profundo)
src/lib/verse-service.tscom interface:Middleware de cache (aspecto transversal)
Unificação de erros
apiError(request, code, message, details?)que constrói o envelope correto (legacy{ error }vs v1{ error, meta }) baseado no path.Decomposição da landing page
landing/page.ts(1713 linhas) dividido em:landing/page.css.ts— CSS como template stringslanding/page.svg.ts— ícones SVG como componenteslanding/page.client.ts— JavaScript client-side (tabs, runner, copy)landing/page.templates.ts— funções de template HTML puraslanding/page.ts— composer fino que importa e orquestraConsolidação do tipo Locale
Locale(presente em 3 arquivos: books.ts, locale.ts, i18n.ts) movido parasrc/lib/locale.tscomo fonte única da verdade.Remoção de código morto
/characterse/dictionaryemnormalizeCacheKey(lib/cache.ts:28-30) — endpoints que não existem.Testing Decisions
fetchVerses()com params conhecidos e verifica o resultado — não testa separseVerseParamfoi chamado ou se o LRU cache foi usado.withCache, funções puras em lib/chapter.ts (parseVerseParam, extractVerses, formatReference), lib/locale.ts (normalizeLocale), lib/response.ts (okResponse, errorResponse), lib/votd-pool.ts (pickVotdForDate, dayOfYearUtc).withCacheé o seam para testar handlers sem cache real. Um adapter MemoryCache implementa a mesma interface do Cache API.Out of Scope
Further Notes