Skip to content

001 — Stack de infraestrutura (libs a adotar)

Status: Aceita (2026-06-03)

O domínio do mel_finance hoje é all-in-memory: seis cenários (000–006) modelados em VOs/entities/aggregates puros, sem nenhuma dependência de infra. Os próximos cenários (007 persistir budget, 008 chat com agente, importar fatura PDF real) forçam a entrada de várias capabilities externas ao mesmo tempo — persistência, LLM, parsing de PDF, datas robustas, dinheiro multi-currency. Decidir lib por lib quando o cenário bater geraria ruído e potencial retrabalho (ex: trocar Money artesanal por dinero depois de já ter persistido schema).

Além disso, gotchas já documentadas no AGENTS.md (“aritmética de data em UTC sempre”, “domínio não converte moeda”) apontam pra ferramentas que resolveriam classes inteiras de bugs (Temporal mata o UTC, dinero formaliza Money multi-currency). Vale antecipar a escolha e deixar a transição mecânica.

Research externo já foi feito (relatório separado, conhecido pelo leitor) cobrindo trade-offs de cada categoria. Esta ADR consolida as escolhas — sem reabrir benchmark.

Constraint operacional: sem remote git, sem CI, sem deploy. Tudo local-first. Instalação dos libs é só npm install quando o cenário consumir.

Adotar a stack abaixo, on-demand (instalar quando o primeiro cenário consumir, não preventivamente):

  • dinero.js v2 — substitui o VO Money em shared-kernel/. Wrap atrás do re-export atual (shared-kernel/Money.ts) pra domínio não notar a troca. v2 estável desde mar/2026, immutable, multi-currency safe.
  • drizzle-orm + better-sqlite3 — persistência. Driver sync casa com aggregate roots POJO-serializáveis; BudgetRepo (e equivalentes) mapeiam aggregate ↔ row sem cerimônia. Schema-as-TS evita drift.
  • temporal-polyfill (variante ~20KB, não a full ~200KB) — substitui Date em qualquer aritmética de mês/dia no domínio. Mata a gotcha UTC. Polyfill sai quando Node 26 LTS unflag o Temporal nativo.
  • @openrouter/ai-sdk-provider + modelo google/gemini-2.5-flash — agente conversacional via Vercel AI SDK. Travar no 2.5, não 3.x: research identificou bugs em tool-calling no Gemini 3.x via OpenRouter (thought_signature ausente, conteúdo stripado em multi-turn).
  • unpdf + banksheet (plugin Nubank) — parser de fatura Nubank. unpdf faz text extraction (pure JS, edge-safe); banksheet tem plugin Nubank pronto. Se banksheet ficar abandonado, fork apenas do plugin.
  • clone do vercel/chatbot starter — base do chat UI. Stripar Postgres + Vercel AI Gateway, plugar SQLite (drizzle) + OpenRouter provider. Ganha de graça: RSC chat shell, useChat, setup de Playwright, padrão de MockLLM pra testes.

Skip explícito (avaliadas e descartadas):

  • Money: currency.js, big.js, decimal.js — dinero ganha em multi-currency + immutability.
  • ORM: prisma (driver async + codegen pesado), kysely (query builder, não ORM — não casa com aggregate root).
  • PDF: pdf-parse (unmaintained, deps nativas em serverless).
  • Open Finance: skip permanente — pluggy é pago (sandbox grátis só 2 semanas, depois plano pago), belvo idem (sales-led, sem self-serve). Open Finance BR direto seria de graça mas exige homologação ICP-Brasil + mTLS — pesado pra projeto solo de casal. PDF upload via chat (cenário 009) é a fatura import path canônica.
  • Datas: date-fns, luxon, dayjs — Temporal cobre todos.

Positivas:

  • Gotchas existentes (UTC, Money artesanal) viram non-issues mecanicamente.
  • Aggregates continuam POJO-serializáveis — drizzle só mapeia, não invade o domínio.
  • Chat UI sai do zero com vercel/chatbot como esqueleto; foco fica no domain wiring, não em React boilerplate.
  • Stack toda pure-JS ou single native dep (better-sqlite3) — local-first sem fricção.
  • LLM com modelo travado (Gemini 2.5 Flash) evita classe conhecida de bug de tool-calling.

Negativas / Trade-offs:

  • 6 dependências novas pra um domínio que hoje tem zero — surface de manutenção cresce considerável de uma vez. Mitigação: instalar on-demand por cenário.
  • Migração de Money (artesanal → dinero v2) toca todos os specs que constroem Money.of(...). Mitigação: o wrap atrás do re-export segura a API; só o interno muda.
  • temporal-polyfill é dead-weight quando Node 26 LTS sair — remoção planejada, não automática.
  • banksheet é manutenção de terceiro com bus factor incerto. Mitigação aceita: fork do plugin Nubank se necessário (escopo pequeno).
  • Gemini 2.5 Flash trava upgrade até OpenRouter consertar 3.x — re-avaliar quando issues fecharem.
  • vercel/chatbot starter traz opinions (Postgres, Auth.js, Vercel Gateway) que precisam ser stripadas antes de plugar SQLite + OpenRouter — custo único de bring-up.
  • Sem ingestão automática de transações — fatura entra via PDF, escala razoável pra um casal mas não pra muitas contas.
  • Manter Money artesanal: barato hoje, caro quando aparecer FX/rounding edge cases. Dinero v2 paga a antecipação.
  • Prisma: ergonomia melhor pra CRUD, mas driver async e codegen pesado atrapalham o mapeamento aggregate ↔ row típico de DDD.
  • Kysely + repo manual: flexível, mas obriga reescrever boilerplate que drizzle entrega.
  • Postgres local (docker): overkill pra um app local-first de casal; SQLite resolve.
  • date-fns + manter Date: cobre 80% mas não mata a gotcha UTC estruturalmente — Temporal sim.
  • OpenAI/Anthropic direto, sem OpenRouter: perde swap de modelo barato; OpenRouter dá Gemini Flash com latência/preço bons sem vendor-lock.
  • Construir parser PDF do zero: layout Nubank muda — manter plugin externo (mesmo com bus factor) é mais barato que reverse-engineer recorrente.
  • AGENTS.md — gotchas “aritmética de data em UTC, sempre” e “domínio não converte moeda; UX converte”.
  • Research report (externo, conhecido pelo time) — comparativo Money libs, ORMs, PDF parsers, providers LLM.
  • vercel/chatbot — starter base pro chat UI.
  • OpenRouter issues sobre Gemini 3.x tool-calling (thought_signature, multi-turn content stripping).