Comment générer des types TypeScript depuis une table DynamoDB
Dans Postgres, tu introspecterais information_schema et générerais les types à
partir de là. DynamoDB n'a pas d'équivalent — et ce n'est pas une fonctionnalité
manquante, c'est le modèle de données : DynamoDB ne stocke aucun schéma
d'item. Les seuls attributs que le service connaît sont ceux utilisés dans les
clés. Les AttributeDefinitions de DescribeTable sont explicites sur leur propre
périmètre : chaque entrée « décrit un attribut du schéma de clés de la table et des
index »
(référence de l'API AWS) —
les cinquante autres attributs de ta table ne sont tout simplement enregistrés
nulle part.
Donc « générer des types TypeScript depuis DynamoDB » signifie toujours l'une de trois choses : déclarer la forme toi-même, la dériver d'un schema que tu écris en code, ou l'inférer des items qui existent réellement.
Comment obtenir des types TypeScript pour une table DynamoDB ?
Il n'y a pas d'API qui renvoie la forme des items d'une table — DescribeTable ne
connaît que les attributs de clé. Tes options : écrire une interface à la main et
valider à la frontière (un schema Zod fait des types et de la vérification runtime
un seul artefact), utiliser une bibliothèque schema-first où le schema que tu
écris produit les types, ou inférer la forme depuis de vrais items — par script, ou
avec un outil comme DynoTable qui scanne la table et exporte une
interface TypeScript, un schema Zod ou un JSON Schema.
- Méthode 1 : écrire l'interface à la main (+ Zod à la frontière)
- Méthode 2 : les bibliothèques schema-first (les types depuis un schema que tu écris)
- Méthode 3 : inférer depuis les données elles-mêmes
Méthode 1 : écrire l'interface à la main + valider à la frontière
Le SDK AWS ne peut pas typer tes items pour toi. Le Document client v3 renvoie les
items comme des enregistrements non typés — chaque résultat de GetCommand /
QueryCommand est effectivement un Record<string, unknown> jusqu'à ce que tu
affirmes le contraire. Un simple cast as Order compile très bien et ment au
runtime, c'est pourquoi la version robuste associe l'interface à une vérification
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 verifiedUn schema, deux emplois : z.infer te donne le type statique, parse attrape
l'item qui ne lui correspond pas — ce qui, dans un store sans schéma, est un
quand, pas un si. Le piège est tout aussi évident : le schema documente ton
intention, pas ta table. Rien n'empêche un ancien writer d'avoir stocké
total comme une chaîne, et les types écrits à la main dérivent silencieusement à
mesure que les données évoluent.
Si tu travailles depuis la sortie brute de l'API (sans Document client), rappelle-toi
que le format réseau est du DynamoDB-JSON à tags de type ({"S": "..."},
{"N": "123"}) — voir le marshalling, et
utilise le convertisseur DynamoDB JSON pour faire
basculer un échantillon entre la forme réseau et la forme simple pendant que tu
écris le schema.
Méthode 2 : les bibliothèques schema-first
Des toolkits comme ElectroDB et DynamoDB-Toolbox attaquent le problème de la dérive côté écriture : tu écris un schema d'entité en code, et la bibliothèque dérive les types TypeScript et impose la forme à chaque lecture et écriture qu'elle effectue. C'est la garantie la plus forte disponible — mais note la direction : c'est toi qui écris le schema ; la bibliothèque ne le découvre pas. En pointer une sur une table existante signifie toujours rétro-ingénier les formes d'items toi-même d'abord, et les items écrits hors de la bibliothèque sont hors de ses garanties. Elles brillent sur les single-table designs greenfield où chaque entité passe par le toolkit dès le premier jour.
Méthode 3 : inférer les types depuis de vrais items
Pour une table existante, la vérité terrain, ce sont les données. Scanne un échantillon, fais l'union des formes :
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 itemsLes pièges du monde réel que la version naïve rencontre immédiatement :
- Les attributs épars. Les items DynamoDB d'une même table peuvent avoir des
attributs différents ; un attribut présent sur 80 % des items est
optional, pas manquant. Suis la fréquence par attribut, pas juste la présence. - Les entités mélangées. Dans un single-table design,
les items
USER#etORDER#partagent la table — une interface fusionnée pour les deux est inutile. Partitionne l'échantillon par l'attribut de type et émets un type par entité. - Les collisions de types. Le même attribut stocké en
Nici et enSlà est un vrai bug de données (et un bug courant) — fais-le remonter comme une union plutôt que d'en choisir un silencieusement. Le jeu complet de tags est dans les types de données. - Un échantillon n'est qu'un échantillon. Les attributs qui n'apparaissent que sur des items rares peuvent ne pas être dans tes 5 000 premiers — et le scan coûte de la capacité de lecture dans tous les cas (query vs scan).
L'inférence en un clic dans DynoTable
Ce script d'inférence — échantillonnage, suivi de fréquence, chemins imbriqués, la séparation par entité — est intégré au panneau Table stats de DynoTable :
- Ouvre une table, clique sur le bouton Stats (l'icône de graphique en
barres), puis sur Index table. DynoTable scanne la table une fois avec la
progression en direct et découvre chaque attribut que tes items portent — y
compris les attributs imbriqués par chemin pointé, comme
commonData.status— avec son type et s'il était requis ou optionnel sur l'ensemble des lignes scannées. - Clique sur Export et choisis un format :
- TypeScript — une
interface. - Zod — un schema
z.object(...)(compatible Standard-Schema). - JSON Schema — draft 2020-12.
- TypeScript — une
- Copie-le dans le presse-papiers ou enregistre-le dans un fichier.

L'export est honnête sur ce qu'il est : chaque schema généré s'ouvre sur une note indiquant qu'il a été inféré des items échantillonnés — un point de départ solide, pas un contrat faisant autorité. L'optionalité reflète la fréquence d'apparition de chaque attribut pendant l'indexation, et les attributs de clé primaire sont toujours marqués requis. L'indexation engendre les coûts de lecture DynamoDB normaux, et Reindex rafraîchit le tableau après que tes données ont changé.
FAQ
Puis-je générer des types depuis DescribeTable ?
Seulement pour les attributs de clé. AttributeDefinitions couvre le schéma de
clés de la table et des index — rien d'autre sur tes items n'est stocké par le
service, donc il n'y a pas de schéma côté serveur à introspecter.
Quelle est la meilleure façon de typer une table de production existante ? Inférer d'abord, durcir ensuite : échantillonne les vrais items (par script ou via l'export indexé de DynoTable) pour obtenir la forme réelle, revois-la, et promeus-la en schema Zod que tu possèdes ou en entité de bibliothèque schema-first pour que la dérive future soit attrapée à la frontière.
Comment gérer plusieurs types d'entités dans une même table ? Un type par entité, jamais un type fusionné. Sépare l'échantillon sur ton attribut de type (ou le préfixe de clé) et génère une interface distincte pour chacun — l'union discriminée de celles-ci est le type de ta table.
Pourquoi mes types générés disent-ils qu'un champ requis est optionnel ? Parce qu'un item échantillonné ne l'avait pas. Dans un store sans schéma, l'optionalité est une observation, pas une déclaration — vérifie si ces items sont des lignes legacy à rétro-remplir (voir les migrations) ou un attribut authentiquement optionnel.
Les types couvrent-ils les ensembles et le binaire de DynamoDB ? Un convertisseur doit choisir des représentations JSON simples : les ensembles deviennent des tableaux et le binaire devient une chaîne encodée — les mêmes particularités de conversion couvertes dans le marshalling. Fais faire l'aller-retour à un échantillon dans le convertisseur DynamoDB JSON pour voir exactement à quoi ressemblent tes attributs de chaque côté.
Arrête de deviner la forme de ta table — télécharge DynoTable, indexe la table, et exporte un TypeScript, Zod ou JSON Schema en un clic.


