Pular para o conteúdo

ADR-0035 Runtime e toolchain

Padrão da empresa para runtime e toolchain — o Bun como runtime, gestor de pacotes, workspaces e executor de testes, com preferência pelas APIs built-in do Bun em vez de pacotes; Oxlint, Oxfmt e Vite ficam para o que o Bun não faz.

Decisão
Bun, built-ins primeiroEntregar e operar
Estado
Aceite
Data
Âmbito
Predefinição da empresa para todas as apps e ferramentas

O ADR-0006 escolheu Node LTS, pnpm e Vite+, e rejeitou o Bun por “não ser o runtime de referência”. Desde então foi lançado o Bun 1.4, que já cobre várias coisas para as quais instalamos pacotes:

  • Um só binário executa a app, instala dependências, corre scripts do workspace e corre testes
  • Clientes built-in: um cliente PostgreSQL (Bun.SQL), um cliente S3 (Bun.S3Client), um cliente Redis, hashing de palavras-passe (Bun.password, argon2id), carregamento de .env, uma shell (Bun.$), globbing e hashing
  • Workspaces e catálogos no package.json da raiz, instalações isoladas por predefinição nos novos workspaces, e lifecycle scripts bloqueados a menos que o pacote esteja em trustedDependencies
  • O ecossistema que usamos corre nele: o Bun documenta o TanStack Start (o preset bun do Nitro) e o Drizzle, o Drizzle v1 tem um driver bun-sql, o Better Auth corre no Bun, o BullMQ lista o Bun, e o Testcontainers corre um smoke test com Bun no CI
  • Uma imagem distroless oficial, oven/bun:<version>-distroless, sem shell e sem gestor de pacotes

Cada pacote que o Bun dispensa é uma dependência a menos para atualizar e auditar. As lacunas do Bun também são reais, e são elas que decidem onde param os built-ins.

Prós: o runtime de referência; a auto-instrumentação do OpenTelemetry funciona; o modo de browser real do Vitest; uma linha LTS. Contras: cada driver e cada cliente é um pacote; três ferramentas para instalar e fixar (Node, pnpm, Vite+); o Vite+ é jovem e a sua CLI é mais uma coisa para aprender.

Prós: instalações rápidas, mantém a compatibilidade do Node. Contras: outra vez duas ferramentas, e nenhum dos built-ins do runtime — perde-se a maior parte do ganho.

3. O Bun para runtime, pacotes, workspaces e testes

Seção intitulada “3. O Bun para runtime, pacotes, workspaces e testes”

Prós

  • Uma só ferramenta, do portátil à imagem de produção
  • As APIs built-in de Postgres, S3, Redis e palavras-passe substituem pacotes de drivers e de SDKs
  • O bun.lock é texto e permite diffs; os lifecycle scripts são opt-in por pacote
  • Uma imagem de runtime distroless oficial

Contras

  • Pouca auto-instrumentação do OpenTelemetry. Desde a 1.4, as instrumentações http e fs do OTel funcionam no Bun para node:http e node:fs. Mas o servidor do Start corre em Bun.serve, não em node:http; o Bun.SQL e o fetch do Bun não são bibliotecas em que alguma instrumentação faça patch; e o Bun trata module.register, o hook de loader ESM, como um no-op. O tracing nativo (Bun.otel) é um pull request em aberto (oven-sh/bun#39965). Os spans de pedidos, de queries e de fetch vêm de três pequenos wrappers nossos até chegar (ADR-0015)
  • Sem modo de testes em browser real. O bun test corre os componentes em happy-dom; o comportamento de foco, teclado e ponteiro passa para o Playwright (ADR-0014)
  • Sem linter, formatador nem verificador de tipos: Oxlint, Oxfmt e tsc ficam, como simples dev dependencies
  • Não substitui o Vite: o TanStack Start é construído sobre o Vite, pelo que o vite build fica, executado pelo Bun
  • Sem linha LTS: uma só linha de releases, pelo que a versão é fixada exatamente e atualizada de forma deliberada
  • Lacunas de compatibilidade com o Node: hooks de node:module, partes de node:async_hooks, algumas opções de node:worker_threads. O Playwright corre no Bun desde a 1.4, mas o próprio projeto do Playwright lista o suporte ao Bun como não planeado

Prós: permissões seguras por predefinição, tooling built-in. Contras: sem configuração documentada do TanStack Start, e a compatibilidade com o npm é um modo e não a predefinição.

O Bun é o runtime, o gestor de pacotes, a ferramenta de workspaces e o executor de testes da empresa, em desenvolvimento local, no CI e na imagem de produção (ADR-0003). Quando o Bun tem um built-in para alguma coisa, usamo-lo em vez de um pacote — exceto quando o built-in não consegue cumprir o requisito de outro ADR; essas exceções estão em “Onde um pacote fica”.

Em concreto:

  • Versão: Bun 1.4.x (1.4.4 à data de escrita), fixado exatamente no package.json da raiz como "packageManager": "bun@1.4.4". O CI instala essa versão, e as imagens Docker usam a mesma versão, por digest — a imagem de runtime é oven/bun:<version>-distroless, executada com um utilizador não-root (ADR-0016). O Renovate atualiza o campo e as tags das imagens em conjunto

  • Pacotes: bun install; o bun.lock é o único lockfile e entra no commit; o CI e o Docker correm bun ci, que falha quando o lockfile está desatualizado. Nunca npm, pnpm nem yarn

  • Workspaces: workspaces.packages e workspaces.catalog no package.json da raiz (ADR-0026), com o linker isolado. bun run --filter tem como alvo um só pacote

  • Os lifecycle scripts continuam bloqueados. trustedDependencies está vazio por predefinição; acrescentar lá um pacote é revisto como uma dependência nova. Os pacotes pré-1.0 acrescentam-se com bun add --exact

  • As tarefas são scripts do package.json, corridos com bun run:

    Script Corre
    dev bun --bun vite dev
    build bun --bun vite build
    check oxfmt --check . && oxlint && tsc --noEmit
    fix oxfmt . && oxlint --fix
    test bun test (ADR-0014)
  • Built-ins que usamos em vez de pacotes:

    Necessidade Built-in Substitui
    Cliente Postgres Bun.SQL, através de drizzle-orm/bun-sql (ADR-0008) pg
    Armazenamento compatível com S3 Bun.S3Client (ADR-0030) @aws-sdk/client-s3, presigner
    Hashing de palavras-passe Bun.password, argon2id (ADR-0009) o scrypt predefinido do Better Auth
    Testes, mocks, cobertura bun test (ADR-0014) Vitest, @vitest/coverage-v8
    Ficheiros .env em desenvolvimento carregamento automático dotenv
    Scripts, ficheiros, globs, hashes Bun.$, Bun.file, Bun.Glob, Bun.hash execa, fs-extra, glob
  • Onde um pacote fica, e porquê:

    • ioredis — o rate-limiter-flexible e o @better-auth/redis-storage só aceitam os clientes que listam, e o cliente Redis do Bun não é um deles; um só cliente para o Valkey vale mais do que dois (ADR-0010)
    • Vite — o TanStack Start é um plugin do Vite (ADR-0002)
    • Oxlint, Oxfmt, TypeScript — o Bun não tem linter, formatador nem verificador de tipos
    • Nodemailer e pino — o Bun não tem cliente SMTP nem logger (ADR-0029, ADR-0015)
    • O pipeline de Markdown — o Bun.markdown está marcado como instável (ADR-0021)
    • O createCsrfMiddleware() do Start — verifica Sec-Fetch-Site e Origin sem tokens; o Bun.CSRF emite tokens, um desenho diferente (ADR-0013)
    • Playwright — o Bun.WebView controla um browser headless, mas não tem executor de testes, traces, execuções cross-browser nem integração com o axe; o Playwright corre no Bun desde a 1.4 (ADR-0014)
  • O código de servidor importa de bun só dentro de src/server/ (ADR-0026), pelo que o build do browser nunca o vê

Os fatores decisivos: uma ferramenta em vez de três, menos pacotes para manter atualizados, clientes built-in de Postgres e S3, lifecycle scripts bloqueados por predefinição e uma imagem distroless oficial. Aceitamos manter os nossos próprios spans de pedidos, de queries e de fetch até o tracing nativo do Bun chegar, testes de componentes num DOM simulado e um runtime sem linha LTS.

Mais fácil:

  • Um portátil, um runner de CI e a imagem precisam de um só binário, numa só versão fixada
  • Menos dependências: sem driver de Postgres, sem AWS SDK, sem executor de testes, sem dotenv
  • As instalações são rápidas, e nenhum pacote corre scripts de instalação a menos que o tenhamos listado

Mais difícil:

  • Os spans de pedidos, de queries e de fetch são wrappers nossos até o Bun.otel chegar (ADR-0015); o Valkey, o node:http, os jobs, as chamadas de IA e os logs usam instrumentação mantida. Rever quando chegar
  • Uma lacuna de compatibilidade com o Node numa dependência fica por nossa conta, para contornar ou reportar
  • O Bun evolui depressa: ler as notas de lançamento em cada atualização e correr a suite de testes completa antes de fazer merge
  • Os exemplos online das nossas bibliotecas assumem quase sempre Node e pnpm

Do que abdicamos: do estatuto de runtime de referência do Node e da instrumentação OpenTelemetry mantida para o servidor HTTP, o cliente Postgres e o fetch, do modo de browser real do Vitest e do comando único vp do Vite+. Aceite — um runtime com clientes built-in é mais simples de operar e de proteger do que um runtime mais os pacotes que lhe faltam.