Intermedio7 min di lettura

Come generare tipi TypeScript da una tabella DynamoDB

In Postgres faresti introspezione su information_schema e genereresti i tipi da lì. DynamoDB non ha alcun equivalente — e non è una feature mancante, è il modello dei dati: DynamoDB non memorizza alcuno schema degli item. Gli unici attributi che il servizio conosce sono quelli usati nelle chiavi. Le AttributeDefinitions di DescribeTable sono esplicite sul proprio ambito: ogni voce "describes one attribute in the table and index key schema" (riferimento API AWS) — gli altri cinquanta attributi della tua tabella semplicemente non sono registrati da nessuna parte.

Quindi "generare tipi TypeScript da DynamoDB" significa sempre una di tre cose: dichiarare la forma tu stesso, derivarla da uno schema che scrivi in codice, o inferirla dagli item che esistono davvero.

Come ottengo tipi TypeScript per una tabella DynamoDB?

Non c'è alcuna API che restituisca la forma degli item di una tabella — DescribeTable conosce solo gli attributi chiave. Le tue opzioni: scrivere a mano un'interface e validare al confine (uno schema Zod rende i tipi e il controllo a runtime un unico artefatto), usare una libreria schema-first dove lo schema che scrivi produce i tipi, oppure inferire la forma da item reali — via script, o con uno strumento come DynoTable che scansiona la tabella ed esporta un'interfaccia TypeScript, uno schema Zod o un JSON Schema.

Metodo 1: scrivi a mano l'interfaccia + valida al confine

L'SDK AWS non può tipizzare i tuoi item per te. Il Document client v3 restituisce gli item come record non tipizzati — ogni risultato di GetCommand / QueryCommand è di fatto Record<string, unknown> finché tu non asserisci altrimenti. Un nudo cast as Order compila benissimo e mente a runtime, ed è per questo che la versione robusta accoppia l'interfaccia a un controllo a 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

Uno schema, due lavori: z.infer ti dà il tipo statico, parse cattura l'item che non gli corrisponde — che in uno store senza schema è un quando, non un se. Il limite è altrettanto chiaro: lo schema documenta la tua intenzione, non la tua tabella. Nulla impedisce a un vecchio writer di aver memorizzato total come stringa, e i tipi scritti a mano derivano silenziosamente mentre i dati evolvono.

Se lavori dall'output API grezzo (non Document client), ricorda che il formato sul filo è DynamoDB-JSON type-tagged ({"S": "..."}, {"N": "123"}) — vedi marshalling, e usa il DynamoDB JSON converter per ribaltare un campione tra forma sul filo e forma semplice mentre scrivi lo schema.

Metodo 2: librerie schema-first

Toolkit come ElectroDB e DynamoDB-Toolbox attaccano il problema della deriva dal lato scrittura: scrivi uno schema di entità in codice, e la libreria deriva i tipi TypeScript e impone la forma su ogni lettura e scrittura che esegue. È la garanzia più forte disponibile — ma nota la direzione: lo schema lo scrivi tu; la libreria non lo scopre. Puntarne una a una tabella esistente significa comunque fare prima reverse-engineering delle forme degli item da solo, e gli item scritti fuori dalla libreria sono fuori dalle sue garanzie. Brillano sui single-table design greenfield dove ogni entità passa dal toolkit fin dal primo giorno.

Metodo 3: inferisci i tipi da item reali

Per una tabella esistente, la verità di base sono i dati. Scansiona un campione, unisci le forme:

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

Le trappole del mondo reale in cui la versione ingenua cade immediatamente:

  • Attributi sparsi. In una stessa tabella gli item DynamoDB possono avere attributi diversi; un attributo presente sull'80% degli item è optional, non mancante. Traccia la frequenza per-attributo, non solo la presenza.
  • Entità miste. In un single-table design, gli item USER# e ORDER# condividono la tabella — un'unica interfaccia fusa per entrambi è inutile. Partiziona il campione per il type attribute ed emetti un tipo per entità.
  • Collisioni di tipo. Lo stesso attributo memorizzato come N qui e S là è un bug dei dati reale (e comune) — fallo emergere come union invece di sceglierne silenziosamente uno. Il set completo dei tag è in tipi di dati.
  • Un campione è un campione. Gli attributi che appaiono solo su item rari potrebbero non essere nei tuoi primi 5.000 — e lo scan costa read capacity in ogni caso (query vs scan).

Inferenza a un click in DynoTable

Quello script di inferenza — campionamento, tracciamento delle frequenze, percorsi annidati, la separazione per-entità — è integrato nel pannello Table stats di DynoTable:

  1. Apri una tabella, premi il pulsante Stats (l'icona a grafico a barre) e clicca Index table. DynoTable scansiona la tabella una volta con progresso live e scopre ogni attributo che i tuoi item portano — inclusi quelli annidati per percorso puntato, come commonData.status — con il suo tipo e se era required o optional tra le righe scansionate.
  2. Clicca Export e scegli un formato:
    • TypeScript — un'interface.
    • Zod — uno schema z.object(...) (compatibile Standard-Schema).
    • JSON Schema — draft 2020-12.
  3. Copialo negli appunti o salvalo su file.
Il pannello Table stats di DynoTable: la lista dei campi indicizzati con tipi e flag required/optional, e il pulsante Export dello schema.
Il pannello Table stats di DynoTable: la lista dei campi indicizzati con tipi e flag required/optional, e il pulsante Export dello schema.

L'export è onesto su cosa è: ogni schema generato si apre con una nota che dice che è stato inferito dagli item campionati — un punto di partenza solido, non un contratto autorevole. L'opzionalità riflette quanto spesso ogni attributo è apparso durante l'indicizzazione, e gli attributi della primary key sono sempre marcati required. L'indicizzazione comporta normali costi di lettura DynamoDB, e Reindex aggiorna il quadro dopo che i tuoi dati cambiano.

FAQ

Posso generare tipi da DescribeTable? Solo per gli attributi chiave. AttributeDefinitions copre lo schema delle chiavi di tabella e indici — nient'altro dei tuoi item è memorizzato dal servizio, quindi non c'è alcuno schema lato server da introspezionare.

Qual è il modo migliore di tipizzare una tabella di produzione esistente? Prima inferisci, poi consolida: campiona gli item reali (script o l'export indicizzato di DynoTable) per ottenere la forma effettiva, rivedila, e promuovila a uno schema Zod di tua proprietà o a un'entità di una libreria schema-first, così la deriva futura viene catturata al confine.

Come gestisco più tipi di entità in una tabella? Un tipo per entità, mai un tipo fuso. Dividi il campione sul tuo type attribute (o prefisso di chiave) e genera un'interfaccia separata per ognuno — la discriminated union di quelle è il tipo della tua tabella.

Perché i miei tipi generati dicono che un campo obbligatorio è optional? Perché qualche item campionato non lo aveva. In uno store senza schema l'opzionalità è un'osservazione, non una dichiarazione — verifica se quegli item sono righe legacy da backfillare (vedi migrazioni) o un attributo genuinamente opzionale.

I tipi coprono i set e i binary di DynamoDB? Un convertitore deve scegliere rappresentazioni in JSON semplice: i set diventano array e i binary diventano una stringa codificata — le stesse stranezze di mappatura trattate in marshalling. Fai il round-trip di un campione nel DynamoDB JSON converter per vedere esattamente come appaiono i tuoi attributi su ciascun lato.

Smetti di indovinare la forma della tua tabella — scarica DynoTable, indicizza la tabella ed esporta un TypeScript, Zod o JSON Schema in un click.

Aggiornato