Ao construir APIs type-safe com Elysia e Bun, um dos recursos mais poderosos é o Eden Treaty: uma biblioteca cliente que oferece segurança de tipos end-to-end entre seu backend e frontend. Servir definições TypeScript de forma eficiente em diferentes ambientes requer alguns cuidados. Este post cobre uma abordagem pronta para produção para servir arquivos .d.ts que funciona tanto no desenvolvimento quanto em implantações na nuvem.

O Desafio: Definições de Tipos em Diferentes Ambientes

O Eden Treaty depende de arquivos de definição TypeScript (.d.ts) para fornecer inferência de tipos nos endpoints da sua API. Gerar esses tipos on-the-fly funciona bem no desenvolvimento, mas cria problemas em produção:

  • Desenvolvimento: você quer feedback instantâneo ao modificar sua API.
  • Produção: você quer respostas rápidas e previsíveis sem o overhead de geração de tipos em tempo de execução.

A Solução de Dois Modos

A implementação usa dois modos:

  1. Cloud Run (Produção): servir definições de tipos estáticas pré-geradas a partir de dist/types.d.ts.
  2. Desenvolvimento: gerar tipos on-the-fly usando elysia-remote-dts.

Implementação

A Configuração do Elysia

Esta é a implementação central que trata ambos os casos:

import { swagger } from '@elysiajs/swagger';
import { dts } from 'elysia-remote-dts';
import { Elysia } from 'elysia';
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';

export function createElysiaApp(options: { enableSwagger?: boolean } = {}) {
    const { enableSwagger = true } = options;

    const app = new Elysia()
        // ... seu middleware e rotas
        .use(routes);

    if (enableSwagger) {
        app.use(swagger({ path: '/docs' }));
    }

    // Serve definições TypeScript para o Eden Treaty
    // No Cloud Run: serve arquivo estático pré-gerado de dist/types.d.ts
    // No desenvolvimento: gera on-the-fly usando elysia-remote-dts
    // K_SERVICE é definido automaticamente pelo Google Cloud Run
    const staticTypesPath = path.resolve(process.cwd(), 'dist/types.d.ts');
    const isCloudRun = !!process.env.K_SERVICE;
    const hasStaticTypes = existsSync(staticTypesPath);

    if (isCloudRun && hasStaticTypes) {
        const staticTypes = readFileSync(staticTypesPath, 'utf-8');

        app.get('/types.d.ts', ({ set }) => {
            set.headers['Content-Type'] = 'application/typescript';
            return staticTypes;
        });
    } else {
        app.use(dts('./src/index.ts', { dtsPath: '/types.d.ts' }));
    }

    return app;
}

Decisões de Design Principais

1. Detecção de Ambiente com K_SERVICE

O Google Cloud Run define automaticamente a variável de ambiente K_SERVICE com o nome do serviço. Essa é uma forma confiável de detectar produção sem precisar definir manualmente NODE_ENV:

const isCloudRun = !!process.env.K_SERVICE;

2. Verificação de Existência do Arquivo Estático

Antes de tentar servir os tipos estáticos, verificamos se o arquivo existe:

const hasStaticTypes = existsSync(staticTypesPath);

Isso evita uma falha caso o arquivo estático não tenha sido gerado por algum motivo. O sistema faz fallback para a geração on-the-fly automaticamente.

3. Header Content-Type

Ao servir o arquivo .d.ts, definimos o content type apropriado:

set.headers['Content-Type'] = 'application/typescript';

Isso garante que navegadores e ferramentas interpretem corretamente a resposta como TypeScript.

Geração de Tipos em Tempo de Build no Docker

A geração de tipos ocorre uma vez durante o build do Docker, não a cada requisição:

FROM oven/bun:alpine
WORKDIR /usr/src/app

COPY . .

RUN bun install

# Gera types.d.ts em tempo de build (em vez de on-the-fly)
RUN bun run generate-types

# Define o fuso horário
RUN apk add --no-cache tzdata zip && \
    cp /usr/share/zoneinfo/US/Pacific /etc/localtime && \
    echo "US/Pacific" > /etc/timezone && \
    apk del tzdata

ENTRYPOINT [ "bun", "run", "src/index.ts" ]

O Script de Geração de Tipos

Adicione um script ao seu package.json:

{
  "scripts": {
    "generate-types": "bun run scripts/generate-types.ts"
  }
}

E o script correspondente:

// scripts/generate-types.ts
import { $ } from 'bun';

// Gera definições TypeScript para o Eden Treaty
await $`bunx elysia-remote-dts generate ./src/index.ts -o ./dist/types.d.ts`;

console.log('Types generated successfully at dist/types.d.ts');

Benefícios desta Abordagem

Zero Overhead em Tempo de Execução em Produção

A geração de tipos ocorre uma vez em tempo de build. O tempo de resposta é consistente, o uso de memória é menor (sem engine de geração de tipos carregada) e não há parsing de AST por requisição.

Atualizações Instantâneas no Desenvolvimento

O elysia-remote-dts gera tipos on-the-fly durante o desenvolvimento. Nenhuma etapa de build é necessária: basta iniciar o servidor e os tipos se atualizam conforme você altera sua API.

Fallback Elegante

A lógica condicional trata casos extremos de forma limpa. Se os tipos estáticos não foram gerados, o sistema faz fallback para a geração on-the-fly. Isso também cobre o desenvolvimento local sem as variáveis de ambiente do Cloud Run e ambientes de staging.

Usando o Eden Treaty no Cliente

Com os tipos sendo servidos, seu frontend pode consumi-los:

import { edenTreaty } from '@elysiajs/eden';
import type { App } from 'your-api-types'; // ou buscar de /types.d.ts

const api = edenTreaty<App>('https://your-api.run.app');

// Segurança de tipos completa!
const { data, error } = await api.users.get();
//     ^? User[]

Para busca dinâmica de tipos, configure sua IDE ou ferramentas de build para buscar os tipos do endpoint /types.d.ts.

Visão Geral da Arquitetura

┌─────────────────────────────────────────────────────────────────────┐
│                        Request Flow                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  Client Request: GET /types.d.ts                                     │
│           │                                                          │
│           ▼                                                          │
│  ┌─────────────────┐                                                 │
│  │ Is Cloud Run?   │                                                 │
│  │ (K_SERVICE set) │                                                 │
│  └────────┬────────┘                                                 │
│           │                                                          │
│     YES   │   NO                                                     │
│     ┌─────┴─────┐                                                    │
│     ▼           ▼                                                    │
│  ┌──────────┐  ┌──────────────┐                                      │
│  │ Static   │  │ On-the-fly   │                                      │
│  │ File     │  │ Generation   │                                      │
│  │ (fast)   │  │ (elysia-dts) │                                      │
│  └────┬─────┘  └──────┬───────┘                                      │
│       │               │                                              │
│       └───────┬───────┘                                              │
│               ▼                                                      │
│        Response: types.d.ts                                          │
│        Content-Type: application/typescript                          │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Pipeline de Build

┌─────────────────────────────────────────────────────────────────────┐
│                     Docker Build Process                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  1. COPY source files                                                │
│           │                                                          │
│           ▼                                                          │
│  2. RUN bun install                                                  │
│           │                                                          │
│           ▼                                                          │
│  3. RUN bun run generate-types    ◄── Types generated HERE           │
│           │                                                          │
│           ▼                                                          │
│  4. dist/types.d.ts created                                          │
│           │                                                          │
│           ▼                                                          │
│  5. Image ready with pre-generated types                             │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Comparação de Performance

Métrica Geração On-the-fly Servindo Arquivo Estático
Tempo de Resposta 50-200ms 1-5ms
Uso de Memória Maior (engine de tipos) Mínimo
Uso de CPU Parsing por requisição Nenhum
Impacto no Cold Start Significativo Negligenciável

Boas Práticas

1. Versionamento dos Seus Tipos

Inclua um comentário de versão nos tipos gerados:

// scripts/generate-types.ts
import { $ } from 'bun';
import { version } from '../package.json';

const header = `// Generated types for API v${version}\n// Generated at: ${new Date().toISOString()}\n\n`;
// ... gera e acrescenta o header

2. Headers de Cache

Adicione headers de cache para produção:

if (isCloudRun && hasStaticTypes) {
    app.get('/types.d.ts', ({ set }) => {
        set.headers['Content-Type'] = 'application/typescript';
        set.headers['Cache-Control'] = 'public, max-age=3600'; // 1 hora
        return staticTypes;
    });
}

3. Integração com CI/CD

Verifique se os tipos estão sendo gerados corretamente no seu pipeline de CI:

# .github/workflows/deploy.yml
- name: Generate and verify types
  run: |
    bun run generate-types
    test -f dist/types.d.ts || exit 1

Conclusão

Servir definições TypeScript para o Eden Treaty se resume a uma decisão: gerar em tempo de build para produção, gerar on-the-fly para desenvolvimento. A implementação de modo duplo trata isso de forma limpa lendo a variável de ambiente K_SERVICE que o Cloud Run define automaticamente.

O resultado: o desenvolvimento permanece rápido e preciso (os tipos sempre refletem sua API atual), enquanto a produção serve arquivos estáticos em 1 a 5ms em vez de 50 a 200ms. Ambos os ambientes mantêm segurança de tipos completa para os consumidores da API.

O mesmo padrão se aplica a qualquer cenário onde você precisa de comportamento específico por ambiente para servir artefatos gerados: detecte o ambiente de forma confiável e tenha um fallback claro.


Conquista Técnica: Serviço de tipos com consciência de ambiente que reduz os tempos de resposta em 95% em produção, mantendo total flexibilidade no desenvolvimento.

Tecnologias Principais: Elysia, Bun, Eden Treaty, Google Cloud Run, Docker, TypeScript