TypeScript پروڈکشن تک پہنچنے سے پہلے ہی بیوقوفانہ بگ (bugs) پکڑ لیتا ہے۔ یہ غلط سپیلنگ والی پراپرٹی، بھولے ہوئے آرگومنٹ، اور غلط فارمیٹ واپس کرنے والی برانچ پر آپ کو ٹوکتا ہے۔ آپ اسے ٹھیک کرتے ہیں، بلڈ پاس ہو جاتا ہے، اور آپ اسے شیپ (ship) کر دیتے ہیں۔ لیکن اسٹیٹک ٹائپس (static types) کی ایک سخت حد ہوتی ہے۔ ایک بار جب کمپائلر اپنا کام مکمل کر لیتا ہے، تو ہر اینوٹیشن (annotation) ہٹا دی جاتی ہے۔ آپ کے کوڈ کو چلانے والا JavaScript انجن آپ کے انٹرفیس (interfaces)، برانڈڈ ٹائپس (branded types)، یا آپ کے احتیاط سے بنائے گئے اسٹرنگ لٹرلز (string literals) کے بارے میں کچھ نہیں جانتا۔ وہ صرف ویلیوز (values) اور زبان کے اصل قواعد کو جانتا ہے۔

بلڈ کا پاس ہونا اس بات کی ضمانت نہیں کہ رن ٹائم (runtime) پر بھی سب محفوظ ہے۔ ٹیسٹ کا گرین ہونا اس بات کی علامت نہیں کہ صارفین کو ایک مستحکم ایپلی کیشن ملے گی۔ اگر آپ کا ذہنی ماڈل (mental model) صرف TypeScript کی حد تک ہی محدود ہے، تو آپ بالکل اسی جگہ اندھیرے میں پرواز کر رہے ہیں جہاں اصل میں کریشز (crashes) ہوتے ہیں۔

The Compile-Time Mirage

TypeScript کا پورا ٹائپ سسٹم کمپائلیشن کے دوران ختم ہو جاتا ہے۔ کسی بھی پروجیکٹ کے کمپائل شدہ JavaScript آؤٹ پٹ کو کھولیں اور آپ کو interface، type یا جنرک کنسٹرینٹس (generic constraints) کا کوئی نشان نہیں ملے گا۔ یہ صرف ڈیزائن ٹائم کے لیے ڈھانچے (scaffolding) کی طرح ہیں۔ براؤزر یا Node.js پروسیس سادہ JavaScript کو چلا رہا ہوتا ہے، اور آپ کے فنکشنز کے ذریعے گزرنے والی ویلیوز اس بات کی ضمانت نہیں دیتیں کہ وہ ان ٹائپس سے مطابقت رکھیں جو آپ نے کاغذ پر بیان کیے تھے۔

یہ فرق آپ کے سسٹم کے کناروں (edges) پر سب سے زیادہ اہمیت رکھتا ہے۔ نیٹ ورک رسپانسز، صارف کا ان پٹ، اور تھرڈ پارٹی لائبریریز ایسی ویلیوز داخل کر سکتی ہیں جو آپ کے ٹائپس کی خلاف ورزی کرتی ہیں۔ ایک ویری ایبل جسے آپ نے strictEmail: string کے طور پر بیان کیا ہو، وہ رن ٹائم پر اب بھی ایک نمبر ہو سکتا ہے اگر کسی غیر قابل اعتماد API کے ذریعے غلط ڈیٹا داخل ہو جائے۔ TypeScript پروڈکشن میں آپ کے کوڈ کے ساتھ جا کر کسی بھی چیز کو نافذ (enforce) نہیں کر سکتا۔ رن ٹائم بالکل ایک الگ سطح پر کام کرتا ہے، اور ان دونوں سطحوں کو ایک ہی سمجھ لینا ایسی ناکامیوں کا باعث بنتا ہے جنہیں اسٹیٹک اینالیسس (static analysis) کبھی نہیں پکڑ سکے گا۔

When Numbers Betray You

TypeScript ایک number دیکھتا ہے۔ JavaScript انجن ایک IEEE 754 ڈبل پریسیژن فلوٹ (double-precision float) دیکھتا ہے۔ یہ فرق تب تک بے ضرر ہے جب تک کہ یہ تباہ کن نہ ہو جائے۔

JavaScript ہر نمبر کے لیے 64 بٹس مختص کرتا ہے، لیکن صرف 53 بٹس مینٹیسا (mantissa) کو اسٹور کرتے ہیں۔ یہ 9,007,199,254,740,991 کی ایک محفوظ انٹیجر حد (safe integer ceiling) پیدا کرتا ہے۔ اس سے بڑی کوئی بھی چیز قریبی قابلِ نمائندگی ویلیو تک راؤنڈ (round) ہو جاتی ہے۔ عملی طور پر، آپ کی ایپلی کیشن کے اندر دو حقیقی طور پر مختلف آئیڈنٹیفائرز (identifiers) ایک ہی ویلیو میں تبدیل ہو سکتے ہیں۔

Snowflake IDs اور دیگر تقسیم شدہ (distributed) 64-bit انٹیجر آئیڈنٹیفائرز عام طور پر اس حد سے تجاوز کر جاتے ہیں۔ مالیاتی نظام جو چھوٹی کرنسی اکائیوں میں بڑی رقموں کا حساب رکھتے ہیں، وہ بھی اس حد کے قریب پہنچ سکتے ہیں۔ خطرہ اکثر آپ کے بزنس لاجک کے چلنے سے پہلے ہی ظاہر ہو جاتا ہے: JSON.parse پی لوڈ (payload) میں موجود نیومیرک لٹرلز کو تیزی سے JavaScript نمبرز میں تبدیل کر دیتا ہے، جس سے پہنچتے ہی پریسیژن (precision) خاموشی سے کم ہو جاتی ہے۔ آپ کی ٹائپ ڈیفینیشن id: number کا وعدہ کر سکتی ہے، لیکن پہلے فنکشن کال سے پہلے ہی رن ٹائم ویلیو خراب ہو چکی ہوتی ہے۔

اس کا حل سادہ ہے لیکن اس کے لیے آپ کے پورے اسٹیک (stack) میں نظم و ضبط کی ضرورت ہے۔ نیٹ ورک ٹرانسپورٹ کے دوران بڑے آئیڈنٹیفائرز کو اسٹرنگ (string) کے طور پر رکھیں۔ اپنے JSON schemas اور API contracts میں، ان فیلڈز کو نمبر کے بجائے اسٹرنگ کے طور پر بیان کریں۔ اگر آپ کو محفوظ حد سے باہر کی ویلیوز پر حساب کتاب کرنا ہو، تو BigInt کا استعمال کریں۔ تاہم، محتاط رہیں: BigInt خود بخود اسٹینڈرڈ JavaScript نمبرز کے ساتھ مکس نہیں ہوتا، اور JSON.stringify بغیر کسی ایرر کے BigInt کو سیریلائز (serialize) نہیں کر سکتا جب تک کہ آپ اسے پہلے واضح طور پر دوبارہ اسٹرنگ میں تبدیل نہ کر دیں۔ آئی ڈیز (IDs) کو ڈیفالٹ کے طور پر غیر شفاف ٹوکنز (opaque tokens) کے طور پر سمجھیں۔ صرف اس وقت انہیں نیومیرک فارم میں تبدیل کریں جب آپ کسی ایسے الگ تھلگ کیلکولیشن ماڈیول کے اندر ہوں جسے واقعی اس کی ضرورت ہو۔