Intermedio7 min de lectura

Cómo generar tipos de TypeScript a partir de una tabla de DynamoDB

En Postgres introspeccionarías information_schema y generarías los tipos a partir de él. DynamoDB no tiene equivalente — y no es una funcionalidad que falte, es el modelo de datos: DynamoDB no almacena ningún schema de elementos. Los únicos atributos que el servicio conoce son los que se usan en las claves. Las AttributeDefinitions de DescribeTable son explícitas sobre su propio alcance: cada entrada "describe un atributo del schema de claves de la tabla y los índices" (referencia de la API de AWS) — los otros cincuenta atributos de tu tabla simplemente no están registrados en ninguna parte.

Así que "generar tipos de TypeScript desde DynamoDB" siempre significa una de tres cosas: declarar la forma tú mismo, derivarla de un schema que escribes en código, o inferirla de los elementos que realmente existen.

¿Cómo obtengo tipos de TypeScript para una tabla de DynamoDB?

No hay ninguna API que devuelva la forma de los elementos de una tabla — DescribeTable solo conoce los atributos de las claves. Tus opciones: escribir a mano una interface y validar en la frontera (un schema de Zod convierte los tipos y la comprobación en tiempo de ejecución en un solo artefacto), usar una librería schema-first donde el schema que escribes produce los tipos, o inferir la forma a partir de elementos reales — con un script, o con una herramienta como DynoTable que escanea la tabla y exporta una interface de TypeScript, un schema de Zod o un JSON Schema.

Método 1: escribir la interface a mano + validar en la frontera

El SDK de AWS no puede tipar tus elementos por ti. El Document client v3 devuelve los elementos como registros sin tipo — cada resultado de GetCommand / QueryCommand es en la práctica Record<string, unknown> hasta que afirmes lo contrario. Un simple cast as Order compila sin problema y miente en tiempo de ejecución, y por eso la versión robusta empareja la interface con una comprobación en tiempo de ejecución:

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

Un schema, dos trabajos: z.infer te da el tipo estático, parse atrapa el elemento que no coincide con él — lo cual, en un almacén sin schema, es un cuándo, no un si. La pega es igual de clara: el schema documenta tu intención, no tu tabla. Nada impide que un escritor antiguo haya almacenado total como cadena, y los tipos escritos a mano se desvían en silencio a medida que los datos evolucionan.

Si trabajas con la salida cruda de la API (sin Document client), recuerda que el formato de cable es DynamoDB-JSON con etiquetas de tipo ({"S": "..."}, {"N": "123"}) — consulta marshalling, y usa el conversor de DynamoDB JSON para alternar una muestra entre la forma de cable y la forma plana mientras escribes el schema.

Método 2: librerías schema-first

Toolkits como ElectroDB y DynamoDB-Toolbox atacan el problema de la deriva desde el lado de la escritura: escribes un schema de entidad en código, y la librería deriva los tipos de TypeScript y aplica la forma en cada lectura y escritura que realiza. Es la garantía más fuerte disponible — pero fíjate en la dirección: tú escribes el schema; la librería no lo descubre. Apuntar una a una tabla existente sigue significando hacer ingeniería inversa de las formas de los elementos tú mismo primero, y los elementos escritos fuera de la librería quedan fuera de sus garantías. Brillan en diseños de tabla única nuevos donde cada entidad pasa por el toolkit desde el primer día.

Método 3: inferir los tipos a partir de elementos reales

Para una tabla existente, la verdad de referencia son los datos. Escanea una muestra y une las formas:

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

Trampas del mundo real con las que la versión ingenua choca de inmediato:

  • Atributos dispersos. Los elementos de DynamoDB de una misma tabla pueden tener atributos distintos; un atributo presente en el 80% de los elementos es optional, no inexistente. Registra la frecuencia por atributo, no solo la presencia.
  • Entidades mezcladas. En un diseño de tabla única, los elementos USER# y ORDER# comparten la tabla — una interface fusionada para ambos es inútil. Particiona la muestra por el atributo de tipo y emite un tipo por entidad.
  • Colisiones de tipo. El mismo atributo almacenado como N aquí y S allá es un bug de datos real (y común) — hazlo aflorar como una unión en lugar de elegir uno en silencio. El conjunto completo de etiquetas está en tipos de datos.
  • Una muestra es una muestra. Los atributos que solo aparecen en elementos raros pueden no estar en tus primeros 5.000 — y el scan cuesta capacidad de lectura de todos modos (query vs scan).

Inferencia con un clic en DynoTable

Ese script de inferencia — muestreo, seguimiento de frecuencias, rutas anidadas, la separación por entidad — está integrado en el panel de estadísticas de tabla de DynoTable:

  1. Abre una tabla, pulsa el botón de Estadísticas (el icono de gráfico de barras) y haz clic en Indexar tabla. DynoTable escanea la tabla una vez con progreso en vivo y descubre cada atributo que llevan tus elementos — incluidos los anidados por ruta con puntos, como commonData.status — con su tipo y si fue obligatorio u opcional entre las filas escaneadas.
  2. Haz clic en Exportar y elige un formato:
    • TypeScript — una interface.
    • Zod — un schema z.object(...) (compatible con Standard Schema).
    • JSON Schema — draft 2020-12.
  3. Cópialo al portapapeles o guárdalo en un archivo.
El panel de estadísticas de tabla de DynoTable: la lista de campos indexados con tipos y marcas de obligatorio/opcional, y el botón de exportación de schema.
El panel de estadísticas de tabla de DynoTable: la lista de campos indexados con tipos y marcas de obligatorio/opcional, y el botón de exportación de schema.

La exportación es honesta sobre lo que es: cada schema generado empieza con una nota de que fue inferido a partir de los elementos muestreados — un punto de partida sólido, no un contrato autoritativo. La opcionalidad refleja la frecuencia con la que apareció cada atributo durante la indexación, y los atributos de la clave principal siempre se marcan como obligatorios. Indexar incurre en costes de lectura normales de DynamoDB, y Reindexar refresca la imagen cuando tus datos cambian.

Preguntas frecuentes

¿Puedo generar tipos desde DescribeTable? Solo para los atributos de las claves. AttributeDefinitions cubre el schema de claves de la tabla y los índices — el servicio no almacena nada más sobre tus elementos, así que no hay un schema del lado del servidor que introspeccionar.

¿Cuál es la mejor forma de tipar una tabla de producción existente? Primero inferir, luego endurecer: muestrea los elementos reales (con un script o la exportación indexada de DynoTable) para obtener la forma real, revísala y promuévela a un schema de Zod de tu propiedad o a una entidad de librería schema-first, de modo que la deriva futura se atrape en la frontera.

¿Cómo manejo varios tipos de entidad en una misma tabla? Un tipo por entidad, nunca un tipo fusionado. Separa la muestra por tu atributo de tipo (o prefijo de clave) y genera una interface separada para cada uno — la unión discriminada de esas es el tipo de tu tabla.

¿Por qué mis tipos generados dicen que un campo obligatorio es opcional? Porque algún elemento muestreado no lo tenía. En un almacén sin schema la opcionalidad es una observación, no una declaración — comprueba si esos elementos son filas antiguas que rellenar (consulta migraciones) o un atributo genuinamente opcional.

¿Cubren los tipos los conjuntos y los binarios de DynamoDB? Un conversor tiene que elegir representaciones en JSON plano: los conjuntos se convierten en arrays y los binarios en una cadena codificada — las mismas peculiaridades de mapeo que se cubren en marshalling. Pasa una muestra de ida y vuelta por el conversor de DynamoDB JSON para ver exactamente cómo se ven tus atributos en cada lado.

Deja de adivinar la forma de tu tabla — descarga DynoTable, indexa la tabla y exporta un TypeScript, Zod o JSON Schema en un clic.

Actualizado