001 — Stack de infraestrutura (libs a adotar)
Status: Aceita (2026-06-03)
Contexto
Section titled “Contexto”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.
Decisão
Section titled “Decisão”Adotar a stack abaixo, on-demand (instalar quando o primeiro cenário consumir, não preventivamente):
- dinero.js v2 — substitui o VO
Moneyemshared-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
Dateem 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_signatureausente, conteúdo stripado em multi-turn). - unpdf + banksheet (plugin Nubank) — parser de fatura Nubank.
unpdffaz text extraction (pure JS, edge-safe);banksheettem plugin Nubank pronto. Sebanksheetficar abandonado, fork apenas do plugin. - clone do
vercel/chatbotstarter — 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),belvoidem (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.
Consequências
Section titled “Consequências”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/chatbotcomo 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 constroemMoney.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/chatbotstarter 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.
Alternativas consideradas
Section titled “Alternativas consideradas”- Manter
Moneyartesanal: 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+ manterDate: 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.
Referências
Section titled “Referências”- 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).