TypeScript wyłapuje głupie błędy, zanim trafią do produkcji. Upomina Cię za literówkę w nazwie właściwości, zapomniany argument czy gałąź zwracającą niewłaściwy kształt danych. Naprawiasz to, build przechodzi i wdrażasz. Ale typy statyczne mają twardy limit. Gdy kompilator skończy pracę, każda adnotacja zostaje usunięta. Silnik JavaScript uruchamiający Twój kod nigdy nie słyszał o Twoich interfejsach, typach znakowanych (branded types) czy starannie ograniczonych literale stringów. Zna on tylko wartości i rzeczywiste reguły języka.
Pomyślny build nie oznacza bezpieczeństwa w czasie wykonywania. Zielone testy nie oznaczają, że użytkownicy widzą stabilną aplikację. Jeśli Twój model mentalny kończy się na granicy TypeScript, lecisz na ślepo dokładnie tam, gdzie faktycznie dochodzi do awarii.
Miraż czasu kompilacji
Cały system typów TypeScript jest usuwany podczas kompilacji. Otwórz skompilowany kod JavaScript dowolnego projektu, a nie znajdziesz w nim śladu po interface, type czy ograniczeniach generycznych. To jedynie rusztowanie czasowe dla fazy projektowania. Przeglądarka lub proces Node.js wykonuje zwykły JavaScript, a wartości przepływające przez Twoje funkcje nie mają gwarancji zgodności z typami, które zadeklarowałeś na papierze.
Ta luka jest najbardziej istotna na obrzeżach Twojego systemu. Odpowiedzi sieciowe, dane wejściowe użytkownika i biblioteki firm trzecich mogą wstrzykiwać wartości naruszające Twoje typy. Zmienna, którą zadeklarowałeś jako strictEmail: string, wciąż może w czasie wykonywania przechowywać liczbę, jeśli błędne dane wpłyną przez niezaufane API. TypeScript nie może podążać za Twoim kodem do produkcji, aby czegoś egzekwować. Runtime działa na zupełnie innym poziomie, a utożsamianie tych dwóch płaszczyzn prowadzi do błędów, których analiza statyczna nigdy nie wyłapie.
Gdy liczby Cię zdradzają
TypeScript widzi number. Silnik JavaScript widzi zmiennoprzecinkową liczbę podwójnej precyzji IEEE 754. To rozróżnienie jest nieszkodliwe, dopóki nie stanie się katastrofalne.
JavaScript przydziela 64 bity dla każdej liczby, ale tylko 53 bity przechowuje mantysę. Tworzy to limit bezpiecznej liczby całkowitej wynoszący 9 007 199 254 740 991. Wszystko większe zostaje zaokrąglone do najbliższej reprezentowalnej wartości. W praktyce dwa naprawdę różne identyfikatory mogą zapaść się do tej samej wartości wewnątrz Twojej aplikacji.
Identyfikatory Snowflake i inne rozproszone 64-bitowe identyfikatory liczbowe rutynowo przekraczają ten limit. Systemy finansowe śledzące wysokie kwoty w mniejszych jednostkach walutowych również mogą się do niego zbliżyć. Niebezpieczeństwo często pojawia się, zanim jeszcze uruchomisz swoją logikę biznesową: JSON.parse automatycznie konwertuje literały liczbowe w ładunku na liczby JavaScript, po cichu ucinając precyzję już przy odbiorze. Twoja definicja typu może obiecywać id: number, ale wartość w czasie wykonywania jest już uszkodzona przed pierwszym wywołaniem funkcji.
Rozwiązanie jest proste, ale wymaga dyscypliny w całym stosie technologicznym. Przechowuj duże identyfikatory jako stringi podczas transportu sieciowego. W schematach JSON i kontraktach API definiuj te pola jako stringi, a nie liczby. Jeśli musisz wykonywać operacje arytmetyczne na wartościach poza bezpiecznym zakresem, sięgnij po BigInt. Bądź jednak ostrożny: BigInt nie miesza się niejawnie ze standardowymi liczbami JavaScript, a JSON.stringify nie może zserializować BigInt bez wyrzucenia błędu, chyba że najpierw jawnie przekonwertujesz go z powrotem na string. Traktuj identyfikatory domyślnie jako nieprzezroczyste tokeny. Parsuj je do formy liczbowej tylko wtedy, gdy znajdujesz się wewnątrz odizolowanego modułu obliczeniowego, który naprawdę tego potrzebuje
