هذا المقال يتطلب مستوى محترف. لو لسه بتتعلم TypeScript أو ما اشتغلتش على نظام أنواع كبير قبل كده، ابدأ بمقال Type Hints أو Type Narrowing الأول، ثم ارجع لهنا.
Branded Types في TypeScript: امنع خلط الـ IDs compile-time
لو الـ codebase بتاعك فيه 14 نوع ID مختلف — UserId و OrderId و ProductId و SessionId و InvoiceId — وكلهم string، أنت على بُعد bug واحد من charge عميل غلط. Branded Types بتقفل الباب ده compile-time بدون أي runtime overhead، وبتحوّل bugs كانت بتطلع في الإنتاج لرسائل خطأ في الـ editor قبل ما الكود يتبني أصلاً.
المشكلة باختصار
TypeScript بيستخدم نظام أنواع structural (يعرف كمان بـ duck typing). يعني نوعين بنفس الشكل بيتعاملوا كنفس النوع تمامًا. لو كاتب type UserId = string و type OrderId = string، الـ compiler هيقبل تمرير واحد مكان التاني بدون ما يرمش حرف. ده مذكور صراحة في الـ TypeScript Handbook تحت قسم Type Compatibility: "TypeScript's structural type system was designed based on how JavaScript code is typically written".
النتيجة العملية: bug صامت بيوصل للإنتاج. الدالة refundOrder(id: string) هتقبل أي string، حتى لو ده customer_id جاي من webhook غلط. الـ compiler مش هيرفش. الـ tests اللي بتحقن mock IDs مش هتلاحظ. هتعرف بس لما عميل يكلّمك يقولك: "أنا اشتريت، السحب اتم، الطلب اختفى".
تمثيل واقعي قبل التعريف العلمي
تخيل فندق كبير فيه 200 غرفة. كل غرفة لها مفتاح، والمفاتيح كلها بنفس الشكل بالظبط — معدن أصفر، نفس الطول، نفس الوزن. لو خلطت مفتاحين، هتدخل غرفة الغلط، وأحيانًا هتدخل غرفة عميل تاني نايم. كارثة.
الحل اللي بيستخدمه أي فندق محترم؟ يحط على كل مفتاح tag ملوّن: أحمر للجناح الملكي، أزرق للغرف العادية، أخضر لغرف الموظفين. المفتاح المعدني نفسه ما اتغيرش. القفل ما اتغيرش. الـ tag مالوش أي تأثير ميكانيكي على الباب. هو مجرد علامة بصرية بتمنع البشري اللي ماسك المفتاح من إنه يخلط.
ده بالظبط اللي Branded Types بتعمله. النوع الأصلي (string) بيفضل زي ما هو في الـ runtime — JavaScript الناتجة ما فيهاش أي حاجة زيادة. بس وقت الـ compile بنضيف "tag" وهمي على النوع علشان الـ compiler يفرق بين UserId و OrderId رغم إنهم structurally identical.
التعريف العلمي الدقيق
Branded Type في TypeScript هو محاكاة لـ nominal typing فوق نظام structural. الـ trick بيعتمد على intersection type مع property وهمية (phantom property) مفتاحها unique symbol. الـ phantom property مش بتتولد في الـ JavaScript الناتج لأنها مش بتتسند ليها قيمة فعلية — هي بس marker للـ type checker.
الفرق بين Structural و Nominal typing مذكور في ورقة Pierce — "Types and Programming Languages" (2002، فصل 19.3). فريق TypeScript نفسه بيستخدم البتيرن ده داخليًا في الـ compiler source: لو فتحت في مستودع microsoft/TypeScript هتلاقي و ودول phantom properties بنفس الطريقة بالظبط.