Intermediário7 min de leitura

Como Gerar Tipos TypeScript a partir de uma Tabela DynamoDB

No Postgres você faria introspecção do information_schema e geraria os tipos a partir dele. O DynamoDB não tem equivalente — e não é uma feature faltando, é o modelo de dados: o DynamoDB não armazena nenhum schema de item. Os únicos atributos que o serviço conhece são os usados em chaves. O AttributeDefinitions do DescribeTable é explícito sobre o próprio escopo: cada entrada "descreve um atributo no schema de chaves da tabela e dos índices" (referência da API da AWS) — os outros cinquenta atributos da sua tabela simplesmente não são registrados em lugar nenhum.

Então "gerar tipos TypeScript do DynamoDB" sempre significa uma de três coisas: declarar o formato você mesmo, derivá-lo de um schema que você escreve em código, ou inferi-lo dos itens que realmente existem.

Como obtenho tipos TypeScript para uma tabela DynamoDB?

Não existe API que retorne o formato dos itens de uma tabela — o DescribeTable só conhece os atributos de chave. Suas opções: escrever uma interface à mão e validar na fronteira (um schema Zod torna os tipos e a checagem de runtime um só artefato), usar uma biblioteca schema-first onde o schema que você escreve produz os tipos, ou inferir o formato de itens reais — por script, ou com uma ferramenta como o DynoTable que escaneia a tabela e exporta uma interface TypeScript, um schema Zod ou um JSON Schema.

Método 1: escreva a interface à mão + valide na fronteira

O AWS SDK não consegue tipar seus itens por você. O Document client v3 retorna itens como registros sem tipo — todo resultado de GetCommand / QueryCommand é efetivamente Record<string, unknown> até você afirmar o contrário. Um cast seco de as Order compila numa boa e mente em runtime, e é por isso que a versão robusta emparelha a interface com uma checagem de runtime:

import {z} from 'zod';

const Order = z.object({
  PK: z.string(), // ORDER#<id>
  SK: z.string(), // META
  status: z.enum(['open', 'shipped', 'cancelled']),
  total: z.number(),
  couponCode: z.string().optional() // sparse attribute
});
type Order = z.infer<typeof Order>;

const {Item} = await doc.send(new GetCommand({TableName: 'Orders', Key: key}));
const order = Order.parse(Item); // typed AND verified

Um schema, dois trabalhos: z.infer te dá o tipo estático, parse pega o item que não bate com ele — o que em um armazenamento sem schema é um quando, não um se. O porém é igualmente claro: o schema documenta a sua intenção, não a sua tabela. Nada impede que um escritor antigo tenha armazenado total como string, e tipos escritos à mão derivam silenciosamente conforme os dados evoluem.

Se você está trabalhando com a saída crua da API (sem Document client), lembre que o formato de rede é DynamoDB-JSON com tags de tipo ({"S": "..."}, {"N": "123"}) — veja marshalling, e use o conversor DynamoDB JSON para alternar uma amostra entre a forma de rede e a forma simples enquanto escreve o schema.

Método 2: bibliotecas schema-first

Toolkits como ElectroDB e DynamoDB-Toolbox atacam o problema do desvio pelo lado da escrita: você escreve um schema de entidade em código, e a biblioteca deriva os tipos TypeScript e impõe o formato em cada leitura e escrita que ela executa. Essa é a garantia mais forte disponível — mas note a direção: você escreve o schema; a biblioteca não o descobre. Apontar uma para uma tabela existente ainda significa fazer engenharia reversa dos formatos de item você mesmo primeiro, e itens escritos fora da biblioteca ficam fora das suas garantias. Elas brilham em single-table designs greenfield onde cada entidade passa pelo toolkit desde o primeiro dia.

Método 3: infira os tipos de itens reais

Para uma tabela existente, a verdade fundamental são os dados. Escaneie uma amostra, faça a união dos formatos:

const seen = new Map<string, Set<string>>(); // attr -> observed types
let count = 0;
let key: Record<string, unknown> | undefined;
do {
  const page = await doc.send(new ScanCommand({TableName: 'Orders', ExclusiveStartKey: key}));
  for (const item of page.Items ?? []) {
    count++;
    for (const [attr, value] of Object.entries(item)) {
      const t = Array.isArray(value) ? 'array' : typeof value;
      (seen.get(attr) ?? seen.set(attr, new Set()).get(attr)!).add(t);
    }
  }
  key = page.LastEvaluatedKey;
} while (key && count < 5000);
// emit: attribute -> type union, optional if seen in < count items

Armadilhas do mundo real que a versão ingênua encontra imediatamente:

  • Atributos esparsos. Itens do DynamoDB em uma mesma tabela podem ter atributos diferentes; um atributo presente em 80% dos itens é optional, não ausente. Rastreie a frequência por atributo, não só a presença.
  • Entidades misturadas. Em um single-table design, itens USER# e ORDER# compartilham a tabela — uma interface fundida para ambos é inútil. Particione a amostra pelo atributo de tipo e emita um tipo por entidade.
  • Colisões de tipo. O mesmo atributo armazenado como N aqui e S ali é um bug de dados real (e comum) — exponha-o como uma união em vez de silenciosamente escolher um. O conjunto completo de tags está em tipos de dados.
  • Uma amostra é uma amostra. Atributos que só aparecem em itens raros podem não estar nos seus primeiros 5.000 — e o scan custa capacidade de leitura de qualquer forma (query vs scan).

Inferência em um clique no DynoTable

Esse script de inferência — amostragem, rastreio de frequência, caminhos aninhados, a divisão por entidade — está embutido no painel Estatísticas da tabela do DynoTable:

  1. Abra uma tabela, aperte o botão Estatísticas (o ícone de gráfico de barras) e clique em Indexar tabela. O DynoTable escaneia a tabela uma vez com progresso ao vivo e descobre cada atributo que seus itens carregam — incluindo os aninhados por caminho pontuado, como commonData.status — com seu tipo e se ele foi obrigatório ou opcional entre as linhas escaneadas.
  2. Clique em Exportar e escolha um formato:
    • TypeScript — uma interface.
    • Zod — um schema z.object(...) (compatível com Standard-Schema).
    • JSON Schema — draft 2020-12.
  3. Copie para a área de transferência ou salve em um arquivo.
O painel Estatísticas da tabela do DynoTable: a lista de campos indexados com tipos e flags de obrigatório/opcional, e o botão Exportar de schema.
O painel Estatísticas da tabela do DynoTable: a lista de campos indexados com tipos e flags de obrigatório/opcional, e o botão Exportar de schema.

O export é honesto sobre o que é: cada schema gerado abre com uma nota de que foi inferido dos itens amostrados — um ponto de partida forte, não um contrato autoritativo. A opcionalidade reflete a frequência com que cada atributo apareceu durante a indexação, e atributos de chave primária são sempre marcados como obrigatórios. Indexar incorre em custos normais de leitura do DynamoDB, e Reindexar atualiza o retrato depois que seus dados mudarem.

FAQ

Posso gerar tipos a partir do DescribeTable? Apenas para os atributos de chave. O AttributeDefinitions cobre o schema de chaves da tabela e dos índices — nada mais sobre seus itens é armazenado pelo serviço, então não existe schema no servidor para introspectar.

Qual é a melhor forma de tipar uma tabela de produção existente? Infira primeiro, endureça depois: amostre os itens reais (script ou o export indexado do DynoTable) para obter o formato real, revise-o, e promova-o a um schema Zod de sua propriedade ou a uma entidade de biblioteca schema-first para que desvios futuros sejam pegos na fronteira.

Como lido com múltiplos tipos de entidade em uma tabela? Um tipo por entidade, nunca um tipo fundido. Divida a amostra pelo seu atributo de tipo (ou prefixo de chave) e gere uma interface separada para cada — a união discriminada delas é o tipo da sua tabela.

Por que meus tipos gerados dizem que um campo obrigatório é opcional? Porque algum item amostrado não o tinha. Em um armazenamento sem schema, opcionalidade é uma observação, não uma declaração — verifique se esses itens são linhas legadas para backfill (veja migrações) ou um atributo genuinamente opcional.

Os tipos cobrem sets e binário do DynamoDB? Um conversor tem que escolher representações em JSON simples: sets viram arrays e binário vira uma string codificada — as mesmas peculiaridades de mapeamento cobertas em marshalling. Faça o round-trip de uma amostra pelo conversor DynamoDB JSON para ver exatamente como seus atributos ficam de cada lado.

Pare de adivinhar o formato da sua tabela — baixe o DynoTable, indexe a tabela e exporte um TypeScript, Zod ou JSON Schema em um clique.

Atualizado