Vol. 9 · № 03 · Un atelier TypeScriptA TypeScript workshop

Les limites & les mensonges externes, ou où la preuve s'arrête The limits & external lies, or where the proof stops

Tout l'édifice de types repose sur des déclarations qu'on n'a pas écrites ni vérifiées : .d.ts fournis, @types tiers, declare maison. Un .d.ts peut affirmer n'importe quoi sur du JavaScript qui fait autre chose ; skipLibCheck ne les vérifie même pas. Ta garantie n'est jamais plus forte que la déclaration la plus faible sur le chemin. Aux frontières des libs mal typées, on revient au Vol 7 : on valide, on ne croit pas. Fin de la série. The whole type edifice rests on declarations you didn't write or verify: bundled .d.ts, third-party @types, homemade declare. A .d.ts can assert anything about JavaScript that does otherwise; skipLibCheck doesn't even check them. Your guarantee is never stronger than the weakest declaration on the path. At mistyped libs' boundaries, you return to Vol 7: validate, don't trust. End of the series.

AudienceAudience
Dev dont le build est vert mais qui plante au contact d'une lib mal typée Dev whose build is green but crashes on contact with a mistyped lib
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ Declaration files@typesTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Tout l'édifice repose sur des déclarations — et une déclaration peut mentir.The whole edifice rests on declarations — and a declaration can lie.

Le système de types est une preuve. Mais toute preuve part d'axiomes — et ici, les axiomes sont les déclarations qu'on n'a pas écrites : les .d.ts des bibliothèques, les @types tiers, les declare posés sur du JavaScript nu. Un .d.ts peut affirmer n'importe quoi sur du code qui fait autre chose. Si une déclaration annonce number là où la lib renvoie un string, le compilateur te croit vert — et le runtime plante. Ta garantie de type n'est jamais plus forte que la déclaration la plus faible sur le chemin.The type system is a proof. But every proof starts from axioms — and here, the axioms are the declarations you didn't write: libraries' .d.ts, third-party @types, the declare laid over bare JavaScript. A .d.ts can assert anything about code that does otherwise. If a declaration claims number where the lib returns a string, the compiler tells you green — and the runtime crashes. Your type guarantee is never stronger than the weakest declaration on the path.

La déclaration affirme — le JavaScript réel n'a rien promisThe declaration asserts — the real JavaScript promised nothing
// dans un .d.ts fourni par la lib (ou un @types tiers) :
declare function parseAmount(raw: string): number;

const total = parseAmount(input);   // le compilateur croit : number
total.toFixed(2);                    // 💥 la lib renvoie en réalité un string → crash
// le .d.ts a AFFIRMÉ 'number' ; le JavaScript réel n'a jamais tenu cette promesse
Sur quoi repose la garantie ?What does the guarantee rest on?
Déclaration crueRaw declaration
un axiome non vérifiéan unchecked axiom
vert à la compilation, faux au runtimegreen at compile, false at runtime
Frontière validéeValidated boundary
une preuve à l'exécutiona runtime proof
retour au Vol 7 : parse, ne crois pasback to Vol 7: parse, don't trust

Une declare n'est pas une preuve : c'est une affirmation qu'on signe sur du code qu'on n'a pas vérifié — un as à l'échelle d'un module. Là où elle est douteuse, on revient au seul recours honnête : valider à la frontière.A declare isn't a proof: it's an assertion you sign over code you didn't verify — an as at module scale. Where it's doubtful, you return to the only honest recourse: validate at the boundary.

La frontière douteuse — on valide, on ne croit pasThe doubtful boundary — validate, don't trust
// On ne fait pas confiance aveugle à une déclaration externe douteuse :
const raw: unknown = parseAmount(input);     // on retype en unknown (7.1)
const total = AmountSchema.parse(raw);        // on VALIDE à la frontière (7.2)
total.toFixed(2);                             // ✓ prouvé à l'exécution, pas sur parole
Le réflexe du numéroThe issue's reflex

« Ma garantie n'est jamais plus forte que la déclaration la plus faible sur le chemin. » Aux frontières des libs non typées ou mal typées, je suis revenu, exactement, au Vol 7 : je ne fais pas confiance, je valide."My guarantee is never stronger than the weakest declaration on the path." At the boundaries of untyped or mistyped libs, I'm back, exactly, at Vol 7: I don't trust, I validate.

Le contre-pied final de la série.The series' final counter-stroke.

Tout ce qu'on a construit — formes, narrowing, invariants, génériques, types calculés, frontières, async, réglages — tient sur des déclarations qu'on n'a pas relues. Ce dernier numéro montre la limite ultime : le compilateur ne prouve que ce qu'on lui a déclaré vrai, et il n'a aucun moyen de savoir si une déclaration ment sur le JavaScript qu'elle décrit.Everything we built — shapes, narrowing, invariants, generics, computed types, boundaries, async, settings — rests on declarations we never reread. This final issue shows the ultimate limit: the compiler proves only what you declared true, and it has no way to know whether a declaration lies about the JavaScript it describes.

№ 02La surpriseThe surprise4 min

skipLibCheck est presque toujours actif — donc les .d.ts ne sont pas vérifiés.skipLibCheck is almost always on — so the .d.ts are not checked.

Deux surprises qui se renforcent. D'abord, skipLibCheck traîne dans la plupart des configs de démarrage, par souci de vitesse — et il dit au compilateur de ne pas vérifier les fichiers de déclaration. Les .d.ts sur lesquels toute ton application s'appuie ne sont donc même pas contrôlés pour leur cohérence interne. Ensuite, declare est par nature une affirmation : il pose un type sur du JavaScript sans jamais le confronter au code réel. C'est un as à l'échelle du module — utile, parfois indispensable, mais c'est un acte de foi, pas une preuve.Two surprises that reinforce each other. First, skipLibCheck lingers in most starter configs, for speed — and it tells the compiler not to check declaration files. The .d.ts your whole application relies on aren't even checked for internal consistency. Second, declare is by nature an assertion: it lays a type over JavaScript without ever confronting it with the real code. It's an as at module scale — useful, sometimes indispensable, but it's an act of faith, not a proof.

Les fichiers de déclaration sont sautés à la vérificationDeclaration files are skipped at checking
// Présent dans la plupart des tsconfig de démarrage :
{ "compilerOptions": { "skipLibCheck": true } }
// → TypeScript NE VÉRIFIE PAS les .d.ts (les tiens compris).
//   Les déclarations sur lesquelles toute l'app s'appuie ne sont pas contrôlées.

On l'active pour accélérer tsc (9.2) et contourner des .d.ts tiers mal fichus — un compromis souvent raisonnable. Mais il faut le savoir : les déclarations qui fondent tes garanties ne sont pas relues par le compilateur. La base de la preuve n'est, elle-même, pas prouvée.You enable it to speed up tsc (9.2) and work around broken third-party .d.ts — often a reasonable trade. But you must know it: the declarations underpinning your guarantees aren't rechecked by the compiler. The proof's base is, itself, unproven.

Le compilateur vérifie ton code contre des déclarations — qu'il ne vérifie pas, et qui ne vérifient pas le JavaScript. La confiance s'arrête là, qu'on le sache ou non.The compiler checks your code against declarations — which it doesn't check, and which don't check the JavaScript. Trust stops there, known or not.
№ 03La mécaniqueThe mechanics5 min

Trois sources de types externes, une chaîne de confiance — aussi forte que son maillon faible.Three sources of external types, one chain of trust — as strong as its weakest link.

Les types des dépendances arrivent par trois voies. Les .d.ts fournis avec la lib, versionnés avec elle — les plus fiables. Les @types/* (DefinitelyTyped), maintenus par la communauté, séparément de la lib — donc susceptibles de décaler quand la lib évolue. Et l'absence totale de types, qu'on comble par un declare maison — un as de module. Dans tous les cas, le même principe : ton code est vérifié contre ces déclarations, mais ta garantie réelle ne vaut que celle du maillon le plus faible de la chaîne.Dependency types arrive by three routes. The .d.ts shipped with the lib, versioned with it — the most reliable. The @types/* (DefinitelyTyped), community-maintained, separately from the lib — hence liable to drift when the lib evolves. And the total absence of types, filled by a homemade declare — an as at module scope. In every case, the same principle: your code is checked against these declarations, but your real guarantee is worth only that of the chain's weakest link.

Les trois provenances des types tiersThe three origins of third-party types
// D'où viennent les types des dépendances :
import { z } from "zod";          // .d.ts FOURNI par la lib (souvent fiable, versionné avec elle)
import _ from "lodash";           // @types/lodash : maintenu par la COMMUNAUTÉ, peut décaler
import legacy from "old-lib";     // aucun type → 'declare' à écrire soi-même (un as de module)
La chaîne de confiance, et son maillon faibleThe chain of trust, and its weak link
// La chaîne de confiance — ta garantie = le maillon le plus faible :
//
//   TON code  ──fait confiance──▶  .d.ts / @types  ──affirme──▶  JavaScript réel
//      (vérifié)                    (PAS vérifié si skipLibCheck)   (jamais confronté)
//
// une seule déclaration fausse sur le chemin, et toute la preuve en aval est creuse.
La source, la fiabilité, le recoursSource, reliability, recourse
La sourceThe source
.d.ts fournibundled .d.tsversionné avec la libversioned with the lib
@types/*communauté, à partcommunity, separate
declarele tien, sur du JS nuyours, over bare JS
Le recoursThe recourse
versionsversionsaligner @types et libalign @types and lib
doutedoubtvalider au runtime (Vol 7)validate at runtime (Vol 7)
skipLibChecksavoir qu'il est actifknow it's on
Les voisins, seulement nommés.The neighbors, only named.

Un declare est un as de module (Vol 4.3) : il faut pouvoir le justifier. Quand un type tiers ment ou manque, on peut le corriger localement par module augmentation (réouvrir le module pour redéclarer), mais c'est encore une affirmation. Et la seule garantie réelle sur une donnée venue d'une lib douteuse reste celle du Vol 7 : la valider à l'exécution, pas la croire sur la foi d'un .d.ts.A declare is a module-scale as (Vol 4.3): you must be able to justify it. When a third-party type lies or is missing, you can patch it locally via module augmentation (reopen the module to redeclare), but that's still an assertion. And the only real guarantee on data from a doubtful lib remains Vol 7's: validate it at runtime, don't believe it on a .d.ts's word.

№ 04Le piègeThe trap4 min

Le @types obsolète : le compilateur est vert, et le runtime plante.The stale @types: the compiler is green, and the runtime crashes.

Le contre-exemple final : faire une confiance aveugle à un @types dont la version a décroché de la lib réelle. La lib passe en v4, sa fonction change de signature ; mais @types reste en v2 et déclare encore l'ancienne forme. Le compilateur vérifie ton code contre une déclaration périmée : tout est vert. À l'exécution, la vraie lib fait autre chose, et ça casse — un bug que le typage promettait justement d'empêcher. Le réflexe : aligner les versions, préférer les types fournis avec la lib, et pour une frontière critique, valider au runtime plutôt que croire un .d.ts.The final counter-example: blindly trusting an @types whose version drifted from the real lib. The lib goes to v4, its function changes signature; but @types stays at v2 and still declares the old shape. The compiler checks your code against a stale declaration: all green. At runtime, the real lib does otherwise, and it breaks — a bug typing was precisely meant to prevent. The reflex: align versions, prefer the types shipped with the lib, and for a critical boundary, validate at runtime rather than believe a .d.ts.

Les types décrivent une version que la lib a quittéeThe types describe a version the lib has left
// package.json : la lib a bondi, ses types non
"dependencies":    { "date-lib": "^4.0.0" },     // v4 : parse() renvoie un objet Date
"devDependencies": { "@types/date-lib": "^2.1.0" } // v2 : déclare encore 'string'

const d = parse("2026-12-01");   // le compilateur croit : string (selon @types v2)
d.trim();                        // ✓ VERT — sur un string, trim() est parfaitement légitime
//   mais v4 rend un Date : « d.trim is not a function » 💥
//   le build ne prouve que la cohérence avec le .d.ts, pas avec la lib

Le compilateur fait son travail parfaitement — contre la mauvaise carte. Un build vert ne prouve que la cohérence avec les déclarations, pas avec la lib réelle. Quand @types ment par retard, le typage ne te protège plus : il t'endort.The compiler does its job perfectly — against the wrong map. A green build only proves consistency with the declarations, not with the real lib. When @types lies by lagging, typing no longer protects you: it lulls you.

Reconnaître où la preuve s'arrête.Recognizing where the proof stops.

C'est l'aboutissement du fil rouge : savoir exactement où la preuve s'arrête et où le runtime reprend. Une déclaration externe est une frontière comme une autre — du « typé » qui repose, en dernière instance, sur du JavaScript non vérifié. Aux libs fiables et versionnées avec leurs types, on fait raisonnablement confiance ; aux @types tiers décalés, aux declare maison, au JS legacy, on applique le réflexe du Vol 7 : la garantie réelle se prouve au runtime, ou n'existe pas.It's the culmination of the through-line: knowing exactly where the proof stops and the runtime resumes. An external declaration is a boundary like any other — "typed" resting, in the last instance, on unverified JavaScript. Reliable libs versioned with their types, you reasonably trust; lagging third-party @types, homemade declare, legacy JS, you treat with Vol 7's reflex: the real guarantee is proven at runtime, or it doesn't exist.

Un build vert prouve que ton code est cohérent avec ce qu'on lui a déclaré — jamais avec le JavaScript qui tournera vraiment. La dernière frontière est celle des déclarations.A green build proves your code is consistent with what was declared to it — never with the JavaScript that will actually run. The last boundary is the declarations'.
№ 05Bilan & éditoWrap-up & editorial2 min

La preuve s'arrête à la déclaration la plus faible — et la série, ici.The proof stops at the weakest declaration — and the series, here.

Tout l'édifice de types repose sur des déclarations qu'on n'a pas écrites ni vérifiées : .d.ts fournis, @types tiers, declare maison. skipLibCheck les laisse même non contrôlées, et declare affirme sans jamais confronter le JavaScript réel. Ta garantie n'est jamais plus forte que la déclaration la plus faible sur le chemin. Aux frontières des libs non typées ou mal typées, on revient au Vol 7 : on valide, on ne croit pas.The whole type edifice rests on declarations you didn't write or verify: bundled .d.ts, third-party @types, homemade declare. skipLibCheck even leaves them unchecked, and declare asserts without ever confronting the real JavaScript. Your guarantee is never stronger than the weakest declaration on the path. At the boundaries of untyped or mistyped libs, you return to Vol 7: validate, don't trust.

Tu reçois un type de…You get a type from…FiabilitéReliabilityCe que ça coûteWhat it costs
Un .d.ts fourni par la libA lib-bundled .d.tsVersionné avec elle — raisonnablement sûr.Versioned with it — reasonably safe.Toujours une affirmation, jamais une preuve runtime.Still an assertion, never a runtime proof.
Un @types tiersA third-party @typesCommunautaire — peut décaler de la lib.Community — can drift from the lib.Aligner les versions ; sinon, vert mais faux.Align versions; else green but false.
Un declare sur du JS nuA declare over bare JSUn as de module — ta parole.A module-scale as — your word.Aucun contrôle : justifie-le, ou valide (Vol 7).No check: justify it, or validate (Vol 7).
Ne PAS faireNOT doConfiance aveugle à un @types obsolète.Blind trust in a stale @types.Le compilateur est vert, le runtime plante.The compiler is green, the runtime crashes.
Le compilateur ne prouve que ce qu'on lui a déclaré vrai. À la frontière de la dernière déclaration, on est revenu au runtime nu — exactement là où la série a commencé.The compiler proves only what was declared true to it. At the boundary of the last declaration, you're back to bare runtime — exactly where the series began.
Mot de l'éditeur — fin de la sérieFrom the editor — end of the series

« Où la preuve s'arrête. »"Where the proof stops."

Neuf volumes plus tôt, on partait d'une seule règle : en TypeScript, le type prouve à la compilation, puis s'efface à l'exécution. Tout en découle. La forme plutôt que le nom (Vol 1) ; l'effacement et le narrowing qui reconstruit une preuve par-dessus des valeurs nues (Vol 2) ; rendre l'état illégal irreprésentable (Vol 3) ; les échappatoires — any qui éteint, unknown qui exige, as qui signe (Vol 4) ; les génériques qui relient (Vol 5) ; le langage des types, et son prix (Vol 6) ; la frontière où la donnée du dehors devient prouvée (Vol 7) ; l'asynchrone qui ne type que le succès (Vol 8) ; et le réglage du compilateur, jusqu'à cette dernière limite (Vol 9).Nine volumes earlier, we started from a single rule: in TypeScript, the type proves at compile time, then erases at runtime. Everything follows. Shape over name (Vol 1); erasure and the narrowing that rebuilds a proof over bare values (Vol 2); making the illegal state unrepresentable (Vol 3); the escape hatches — any that switches off, unknown that demands, as that signs (Vol 4); generics that relate (Vol 5); the type language, and its price (Vol 6); the boundary where outside data becomes proven (Vol 7); async, which types only success (Vol 8); and the compiler's tuning, down to this last limit (Vol 9).

Et l'on revient, pour finir, au point de départ. Même le système de types le plus rigoureux s'arrête quelque part : à la déclaration la plus faible, au .d.ts qu'on n'a pas relu, au JavaScript qui prétend avoir une forme. La discipline tient en deux gestes, et ce sont les deux que la série aura tenté d'installer : pousser les invariants dans le type tant qu'on le peut, et savoir exactement où la preuve s'arrête et où le runtime reprend. Le compilateur n'est pas un gardien tatillon ; c'est un système de preuve partiel, et chacun de ses refus est un théorème qu'on n'avait pas encore su énoncer — ou un mensonge qu'on s'apprêtait à écrire.And we return, at last, to the start. Even the most rigorous type system stops somewhere: at the weakest declaration, the .d.ts you never reread, the JavaScript that pretends to have a shape. The discipline holds in two moves, the two the series has tried to instill: push invariants into the type while you can, and know exactly where the proof stops and the runtime resumes. The compiler isn't a fussy gatekeeper; it's a partial proof system, and each of its refusals is a theorem you hadn't yet learned to state — or a lie you were about to write.

La promesse, en une phrase, était celle-ci : à la fin, on ne fait plus taire le compilateur — on l'entend dire quel théorème on n'a pas encore su énoncer, et on sait exactement où la preuve s'arrête et où le runtime reprend. C'était The TypeScript Loop. Merci de l'avoir parcourue.The promise, in one sentence, was this: in the end, you no longer silence the compiler — you hear it tell you which theorem you haven't yet learned to state, and you know exactly where the proof stops and the runtime resumes. This was The TypeScript Loop. Thank you for walking it.