SELFLAB الستاك الكامل · ١١ إقليم SYSTEM ONLINE
الإقليم 01منهجٌ يُدرَس وحدك

الإقليم ١ — JS + TS من عيون مبرمج C

يبني فوق: معرفتك بـ C (الأنواع الساكنة، المؤشّرات، function pointers، الـ stack/heap، tagged unions). يحيل إلى: منهج TS القديم (third/typescript-curriculum) لنظرية الأنواع العميقة — هنا فقط ما تحتاجه لتقرأ وتدير الستاك. يفتح: الإقليم ٢ (Node)، وكل كودٍ بعده.

النبذة

كل سطرٍ ستكتبه في الـ backend والـ frontend بهذه اللغة. لا يمكن أن تدير مشروعاً أنت أعمى عن لغته. والخبر السارّ: أنت لا تبدأ من الصفر — تبدأ من C، ومعظمُ JS يُفهم أسرع وأعمق حين تراه كنقيضٍ مدروسٍ لـ C: حيثما وضع C قراراً، وضعت JS عكسه، ولكلٍّ سبب. سنركّب اللغة من هذا التباين، ثم نضع TypeScript فوقها — لا كنظرية أنواعٍ مجرّدة (تلك في منهجك القديم)، بل كأداةٍ تجعل تدفّق البيانات في مشروعك مكتوبَ-النوع من طرفٍ لطرف.


اللغز المستفزّ

أنت مبرمج C. لو طلبتُ منك دالةَ جمع، ستلتزم فوراً: int add(int a, int b). النوع عقدٌ، والمترجم حارسٌ يرفض ما يخالفه قبل التشغيل.

الآن تأمّل هذا في JS — وتوقّع المخرجات قبل أن تقرأ الجواب:

js
function add(a, b) { return a + b; } add(2, 3) // ? add("2", "3") // ? add(2, "3") // ? add([], {}) // ? add(true, 1) // ?

ثم الصدمة الأكبر: ولا واحدٌ منها يرمي خطأً ولا يتوقّف البرنامج. كلّها تُرجع شيئاً. في C، خلطُ الأنواع هكذا إما لا يُترجم، أو ينفجر وقت التشغيل. هنا الأمور "تمشي" بصمت وتعطيك قيمةً قد تكون كارثية ("23" بدل 5).

ثلاثة أسئلة، اقعد معها:

  1. أين ذهب النوع؟ في C النوع على المتغيّر (int a). هنا a بلا نوع. فمن يحمل النوع إذن — ومتى؟
  2. لماذا لم ينفجر شيء؟ ما الذي يجعل لغةً تختار أن "تتدبّر أمرها" بدل أن تصرخ؟ أي مشكلةٍ كانت تحلّ بهذا القرار؟
  3. لو بنيتَ خادمَ تسجيلٍ على لغةٍ تقبل add([], {}) بهدوء — كيف تنام في الليل؟ ما الذي يمنع password من أن يكون undefined ويُخزَّن هكذا؟ (هذا سؤال TypeScript و Zod — بذرة.)

ليش: لماذا JS هكذا؟

JS وُلدت في عشرة أيام سنة ١٩٩٥ لتُحرّك صفحاتٍ في متصفّح، تحت قيدين قاهرين:

هذه القرارات وُلِّدت منها كل خصائص اللغة التي ستراها. لا تحفظها — اشتقّها من هذين القيدين كلّما تعجّبت.

نموذج ذهني

النموذج الذهني الجذر: في C، النوع على المتغيّر، ويُمحى وقت التشغيل (يصير المترجمُ النوعَ إزاحاتٍ وأحجاماً في الكود؛ لا يبقى منه أثرٌ في الذاكرة). في JS، القيمةُ نفسها تحمل نوعها وقت التشغيل (قيمةٌ موسومةٌ — tagged value، تماماً كـ tagged union في C: وسمٌ يقول "هذا int" + الحمولة). والمتغيّرُ مجرّد اسمٍ يشير لقيمة. إنه انعكاسُ مرآةٍ لـ C. أمسك هذا، وينحلّ نصف العجب.

typeof هو قراءةُ ذلك الوسم:

js
typeof 42 // "number" typeof "hi" // "string" typeof true // "boolean" typeof undefined // "undefined" typeof null // "object" ← خطأٌ تاريخيٌّ مشهور، لا تثق به (سنرجع له) typeof {} // "object" typeof [] // "object" ← المصفوفة كائن! typeof function(){}// "function"

كيف: نموذج القيم (القاع الذي يُبنى عليه كل شيء)

القيم الأوّلية مقابل الكائنات — وهذا مؤشّراتٌ تعرفها

JS فيها سبعُ قيمٍ أوّلية: number (لا int/float — كلّها 64-bit float واحد!)، string، boolean، undefined، null، bigint، symbol. وكل ما عداها كائن (objects)، والمصفوفاتُ والدوال كائناتٌ خاصّة.

الفرق الذي يهمّك عملياً، وهو لغةٌ تتكلّمها أصلاً:

js
let a = 5; let b = a; // نُسخت القيمة (كأنها int b = a) b = 9; // a == 5 ← لم تتأثّر. الأوّليات تُنسخ بالقيمة. let o = { n: 5 }; let p = o; // نُسخ المرجع فقط (كأنها struct *p = o) p.n = 9; // o.n == 9 ← تأثّرت! كلاهما يشير لنفس الكائن في الـ heap.

الترجمة لـ C: الأوّليات تتصرّف كقيمٍ على الـ stack تُنسخ بالإسناد. الكائنات تتصرّف كأنها دائماً خلف مؤشّر: تمرّر دالةً كائناً = تمرّر مؤشّراً؛ تعديلُ الحقل يُرى عند الجميع. الفرق الوحيد عن C: لا malloc/free. الذاكرة يديرها garbage collector يحرّر ما لا مرجعَ له. لا تملك العنوان، ولا تحرّره يدوياً. (ثمنه: لا تحكّم دقيق بالعمر؛ مقابل: لا تسريباتٍ ولا use-after-free. مقايضةٌ واعية.)

ملاحظة

فخٌّ يصطاد القادمين من C: const o = {n:5}; o.n = 9; مسموح! const يجمّد الرابط (الاسم لا يشير لكائنٍ آخر)، لا المحتوى (الكائن نفسه قابلٌ للتغيير). تماماً كـ T* const p في C: المؤشّر ثابت، والمُشار إليه لا.

undefined مقابل null — فراغان لا واحد

C فيه NULL واحد. JS فيه اثنان، والتمييز عمليّ:

ستلتقيهما طوال الوقت: حقلٌ اختياريّ في طلبٍ يأتي undefined؛ عمودٌ في قاعدة البيانات بلا قيمةٍ يأتي null. (وفي إقليم TypeScript القديم رأيتَ كيف يقرّر علمُ strict ضمَّهما للأنواع — مرجعُك للغوص.)

الكائنات ليست structs

js
const user = { username: "yazeed", age: 22 }; user.role = "admin"; // أضفتَ حقلاً وقت التشغيل! delete user.age; // وحذفتَ آخر! user["username"]; // وصولٌ بالاسم كنصّ (لأنه قاموسٌ فعلاً)

الكائن قاموسٌ ديناميكيّ (key→value)، لا بنيةٌ ذاتُ تخطيطٍ ثابتٍ كـ struct. لا إزاحاتٌ محسوبةٌ وقت الترجمة؛ بحثٌ بالاسم وقت التشغيل. هذا يعطي مرونةً هائلة (وهو سبب كون JSON طبيعياً جداً في JS — كائنٌ = JSON تقريباً)، وثمنُه أنك بلا أي ضمانٍ أن user.passwrod (بخطأٍ مطبعيّ) خطأ — يرجع undefined بهدوء. هنا يدخل TypeScript ليعيد لك الضمان الذي خسرته.


كيف: الدوال قيمٌ — والإغلاق (closure) هو الجديد الكبير

في C تعرف function pointers: الدالة عنوانٌ تمرّره. JS تأخذها أبعد: الدالة قيمةٌ كاملة تُسنَد وتُمرَّر وتُرجَع.

js
const greet = function (name) { return "hi " + name; }; // دالةٌ في متغيّر function apply(fn, x) { return fn(x); } // دالةٌ تأخذ دالة apply(greet, "yazeed"); // "hi yazeed"

لكن الفرق الجوهريّ عن function pointer في C هو الإغلاق: الدالة في JS تحمل معها البيئة التي وُلدت فيها — تظلّ ترى متغيّرات نطاقها الخارجيّ حتى بعد أن ينتهي ذلك النطاق:

js
function makeCounter() { let count = 0; // متغيّرٌ محليّ return function () { // دالةٌ تُرجَع count = count + 1; return count; }; } const next = makeCounter(); next(); // 1 next(); // 2 ← من أين بقي count حياً؟ makeCounter انتهت!

اللغز لمبرمج C: في C، count متغيّرٌ على الـ stack لـ makeCounter؛ حين تعود الدالةُ يُطوى إطارُها ويصير count قمامة. فكيف يبقى حياً هنا ويتذكّر قيمته بين النداءات؟

الجواب: لأن الدالة الداخلية ما زالت تشير إليه، لا يُطوى count مع الـ stack — يُرفَع إلى الـ heap ويُبقيه الـ GC حياً ما دامت الدالةُ المُرجَعة حيّة. الإغلاق = "function pointer + لقطةٌ حيّةٌ من بيئته على الـ heap". هذه ليست تفصيلةً أكاديمية: الإغلاقُ هو كيف تعمل الـ callbacks في الإقليم ٢ (دالةٌ تُنفَّذ لاحقاً وما زالت تتذكّر سياقها)، وكيف تعمل الـ hooks في React بالإقليم ٨ (الحالة "تبقى" بين عمليات الرسم). من يفهم الإغلاق من جذره، يقرأ نصف الستاك بلا عجب.


كيف: التساهل والمساواة — مصدر اللغز الأول وحلّه

+ المثقل: مع رقمين يجمع، ومع نصٍّ يلصق، وعند الخلط يحوّل لِما يسمح بالعملية:

js
2 + 3 // 5 (رقم + رقم) "2" + "3" // "23" (نصّ + نصّ = لصق) 2 + "3" // "23" (الرقم يُحوَّل لنصّ ليُلصق) true + 1 // 2 (true يُحوَّل 1) [] + {} // "[object Object]" (كلاهما يُحوَّل لنصّ)

هذا هو "أتدبّر أمري بدل أن أتوقّف". خطيرٌ لأنه لا يصرخ. درعك:

  • استعمل === (يقارن النوعَ والقيمة، بلا تحويل) دائماً، لا == (يحوّل أولاً: 0 == "" وnull == undefined ... شبكةُ مفاجآت). قاعدةٌ واحدة: === وفقط.
  • افهم الصدق/الكذب (truthy/falsy) لأنه يقود كل if: الكاذبة ستٌّ فقط false, 0, "", null, undefined, NaN؛ وكلُّ ما عداها صادق — بما فيها [] و{} و"0" (مفاجأة!). فحصُك if (user.age) يكسر حين تكون age === 0. قل ما تقصد: if (user.age !== undefined).

كيف: الوحدات (modules) — كيف يُقسَّم الكود

C يجمّع بـ #include: لصقٌ نصّيٌّ لمحتوى الترويسة في ملفّك (مع حُرّاس التضمين لمنع التكرار). JS عندها وحداتٌ حقيقية: لكل ملفٍّ نطاقُه الخاصّ، ويصرّح صراحةً بما يُصدّر وما يستورد — لا تلوّثَ فضاءِ الأسماء، ولا لصقَ نصّيّ. لهجتان:

js
// ESM (الحديث، ما يستعمله مشروعك) — import/export export function createUser() { /* ... */ } // user.service.ts export default router; // تصديرٌ افتراضيّ واحد import express from "express"; // استيراد الافتراضيّ import { createUser } from "./user.controller"; // استيرادٌ مُسمّى // CommonJS (الأقدم، أصل Node) — require/module.exports const express = require("express"); module.exports = router;

ليش تهمّك اللهجتان؟ لأنك سترى الاثنتين في الستاك وأدواته، وأخطاء "Cannot use import statement outside a module" أو "require is not defined" كلّها صراعُ لهجاتٍ بينهما. مشروعك يكتب ESM (تراه في import ... from في كل ملفّ). تذكّر: ./user.controller مسارٌ نسبيّ لملفّك (وحدةٌ من تأليفك)؛ express بلا ./ = حزمةٌ من node_modules (الإقليم ٢ يفصّل من أين تأتي).


كيف: بدائيات الـ async (سريعاً الآن، آليتها في الإقليم ٢)

سترى ثلاثةَ أشكالٍ لـ"شيءٍ يكتمل لاحقاً". اعرف شكلها الآن فقط؛ لماذا تعمل هكذا هو لبّ الإقليم القادم:

js
// 1) callback: مرّر دالةً تُنادى عند الاكتمال (إغلاق!) readThing((err, data) => { /* ... */ }); // 2) Promise: قيمةٌ "ستتوفّر لاحقاً"، تسلسلها بـ .then fetchThing().then(data => { /* ... */ }).catch(err => { /* ... */ }); // 3) async/await: سكّرٌ نحويٌّ يجعل (2) تُقرأ كأنها متزامنة async function go() { try { const data = await fetchThing(); } // "انتظر" بلا حجبِ الخيط (!) catch (err) { /* ... */ } }

السطر await fetchThing() يبدو كأنه يحجب كـ read() في C — وهو لا يحجب الخيط. كيف؟ هذا التناقض هو الإقليم ٢ بأكمله. اتركه بذرةً.


الجزء الثاني: TypeScript فوق هذا كلّه

ليش: لماذا نضيف أنواعاً للغةٍ رفضتها عمداً؟

رأيتَ المشكلة بعينك: JS لا تحرسك. النوعُ على القيمة وقت التشغيل فقط، فلا تكتشف add([], {}) ولا user.passwrod إلا بتشغيل الكود — وغالباً في الإنتاج، أمام المستخدم. كلّما كبر المشروع، صار هذا قاتلاً: لا تجرؤ على تغيير شيءٍ خوفاً مما سينكسر صامتاً في الطرف الآخر من ألف ملفّ.

TypeScript يعيد حارسَ C الذي خسرته — لكن في زمنٍ مختلف. حارسُ C وقتَ الترجمة. حارس JS... لا يوجد. حارس TS: طبقةُ تحقّقٍ وقتَ الكتابة/الترجمة تفحص أن القيم تطابق العقود قبل أن تشغّل، ثم تختفي تماماً وتُشغَّل JS عاديّة.

ملاحظة

الحقيقة المركزية — احفرها (وهي قلب منهجك القديم وبذرةُ الإقليم ٦): الأنواع تُمحى (type erasure). TypeScript يترجم إلى JS بحذف كل التعليقات النوعية. لا يبقى منها بايتٌ واحدٌ وقت التشغيل. النوع User لا وجود له حين يعمل البرنامج — هو خياليٌّ بحت، أداةُ تفكيرٍ للمترجم. لمبرمج C: حتى أنواعُ C "تُمحى" (تصير إزاحاتٍ وأحجاماً)، لكنها تترك أثراً في الكود المولَّد. أنواع TS لا تترك شيئاً — صفر تأثيرٍ على التنفيذ. هي شِيكٌ خالص.

النتيجة المباشرة التي ستصدمك لاحقاً: TS لا يحميك من بياناتٍ تأتيك من خارج البرنامج (طلبُ شبكة، صفُّ قاعدةِ بيانات، ملفّ). لأن الفحص انتهى وقت الترجمة، وتلك البيانات تصل وقت التشغيل حيث لا نوعَ أصلاً. تكتب const body: User = req.body فيصدّقك المترجم — والشبكةُ ترسل ما تشاء. هذا بالضبط سبب وجود Zod (الإقليم ٦). ضع علامةً هنا.

كيف يعمل TS مع Node عملياً (يفكّ ألغاز مشروعك)

لا يوجد "TS وقت تشغيل". هناك:

فحينما ترى package.json وDockerfile في مشروعك، تفهمهما الآن: tsx للتطوير السريع، tsc للبناء — وكلاهما يصبّ في نفس الشيء: JS بلا أنواع تُشغّلها Node.


كيف: TypeScript التطبيقيّ (ما تحتاجه لتدير الستاك)

أنا لن أعيد بناء نظرية الأنواع (النوع=مجموعة، التباين، البرمجة على مستوى الأنواع) — هي كاملةٌ في منهجك القديم، ومرجعُك حين تريد الغوص. هنا اللبنات التي تجعل تدفّق مشروعك مكتوبَ-النوع:

ts
// 1) تعليق نوعٍ على متغيّر/معامل/إرجاع — العقد الذي خسرته في JS عاد function add(a: number, b: number): number { return a + b; } add(2, "3"); // ✗ خطأ ترجمة! المترجم رفض قبل التشغيل — هذا هو المكسب. // 2) شكلٌ (object type): بنية بياناتك type User = { id: string; username: string; email: string; role: "USER" | "ADMIN"; // اتّحادٌ من قيمٍ حرفية: لا قيمة أخرى مسموحة age?: number; // ? = اختياريّ (قد يكون undefined) }; // 3) الأنواع البنيوية (structural) — فرقٌ جوهريٌّ عن C type Point = { x: number; y: number }; const p = { x: 1, y: 2, z: 9 }; const q: Point = p; // ✓ مقبول! لأن p يملك x و y. الزائد z لا يضرّ.

النقطة ٣ مهمّة لمبرمج C: في C الأنواع اسمية (nominal): struct A و struct B بنفس الحقول نوعان مختلفان لأن الاسم مختلف. في TS الأنواع بنيوية (structural): التطابق بالـشكل لا بالاسم — "إن مشى كالبطّة وبدا كبطّة، فهو بطّة". أي كائنٍ يملك x:number وy:number هو Point، سمّيتَه أو لا. (لماذا اختاروا هذا، وحدوده الدقيقة — في منهجك القديم.)

ts
// 4) الاستنتاج (inference): لا تكتب النوع حين يستنتجه المترجم const name = "yazeed"; // TS يعرف أنه string، لا تكتب : string const nums = [1, 2, 3]; // number[] // 5) generics كما ستستعملها (معامل نوعٍ، لا أكثر الآن) async function load(): Promise<User> { /* ... */ } // وعدٌ بـ User const ids: Array<string> = []; // مصفوفةٌ من string // Request, Response في Express؛ PrismaClient المولَّد — كلّها generics تستهلكها. // 6) any مقابل unknown — بابان للهروب من الأنواع let danger: any; // "أطفئ الفحص هنا" — المواطن الخائن. تجنّبه. let honest: unknown; // "لا أعرف النوع بعد" — صادق: يجبرك أن تتحقّق قبل الاستعمال

req.body يأتيك any (أو unknown بإعدادٍ صارم) — لأنه فعلاً مجهولٌ وقت التشغيل. وهنا تُغلق الدائرة: المترجم يعترف أنه أعمى عن الشبكة، فتأتي أداةٌ تتحقّق وقت التشغيل (Zod) لتملأ الفجوة. عرفتَ الآن لماذا ستوجد، قبل أن تراها.


اللغز / البناء من الصفر

ثلاثة، تُبنى بيدك. ممنوع أي مكتبةٍ خارجية. شغّلها بـ node file.js (لـ JS) وnpx tsx file.ts (لـ TS) — هذا كل ما تحتاج تنصيبه.

اللغز أ — أوّلاً توقّع، ثم شغّل. خذ جدول التساهل والمساواة. لكلِّ سطرٍ منه ولِما يلي، اكتب توقّعك بقلمك أوّلاً، ثم شغّل وقارن. حيثما أخطأت، لا تمرّ — اسأل "أيّ قاعدةٍ في نموذج القيم أو التحويل قادتني للخطأ؟":

js
[] == false; [0] == false; "0" == false; null == undefined; null === undefined; NaN === NaN; typeof NaN; 0.1 + 0.2 === 0.3; [1,2] + [3,4]; {} + [];

(آخر اثنين سيفاجئانك؛ الجواب في نموذج القيم لا في الحفظ. 0.1+0.2 يلمس كونَ كل الأرقام float — تعرف هذا من C.)

اللغز ب — واصِفُ القيم (value describer). اكتب describe(v) يرجّع نصّاً يصف النوع الحقيقيّ والبنية لأي قيمةٍ تُمرَّر، بلا أي مكتبة. القيود التي تجبرك على مواجهة نموذج القيم:

  • يميّز null عن الكائنات (تذكّر typeof null === "object" — الفخّ المشهور؛ كيف تكشف null إذن؟).
  • يميّز المصفوفة عن الكائن العاديّ (كلاهما "object").
  • للمصفوفة: اذكر طولها. للكائن: اذكر مفاتيحه. للدالة: قل "function".
  • جرّبه على: null, undefined, 42, "hi", [1,2,3], {a:1,b:2}, function(){}, ومصفوفةٍ داخلها كائن. اللغز كلّه: قراءة الوسم الحقيقيّ للقيمة وقت التشغيل، وهو ما تفعله كلُّ مكتبةِ تسلسلٍ (بما فيها JSON.stringify).

اللغز ج — أغلق العدّاد، ثم وثّقه بالأنواع. اكتب makeCounter() يُرجِع دالةً كما فوق، بلا class وبلا متغيّرٍ عموميّ (القيد يجبرك على الإغلاق). ثم:

  • اصنع عدّادين مستقلّين a = makeCounter() وb = makeCounter()، وأثبِت بالتشغيل أن لكلٍّ count خاصّاً به. اشرح بكلماتك أين يعيش كل count (ليس على الـ stack — أين؟ ولماذا لم يُحرَّر؟). هذا فهمُك للإغلاق محكَّ النار.
  • حوّل الملفّ إلى .ts وأعطِ makeCounter نوعَ إرجاعٍ صريحاً (() => number). ثم افتح JS الناتج (npx tsc file.ts --outFile out.js ثم اقرأ out.js) ولاحظ: أين ذهبت الأنواع؟ لا أثر. هذا الـ erasure بعينك، لا في كتاب.

اللغز د — اصطد الخطأ قبل التشغيل. اكتب buildEnvelope(success, data) يبني مظروف مشروعك { success, message, data }. أعطِه أنواعاً تجعل المترجم يرفض نداءً يمرّر نصّاً مكان success: boolean. شغّل tsc وشاهده يرفض. هذا هو الحارس الذي خسرتَه في C وأعدته. ثم اسأل نفسك السؤال الذي يفتح الإقليم ٦: لو جاء success من جسم طلبِ شبكةٍ بدل نداءِ دالة — هل سيحرسه المترجم؟ لماذا لا؟

ملاحظة

لا تنتقل قبل أن تبني ب و ج بيدك وتشرح "أين يعيش count" بثقة. الإغلاق والـ erasure هما المفتاحان اللذان تفتح بهما كل الأبواب القادمة.


الخلاصة — أين تتّصل هذه العقدة بالشجرة

  • تحت: ركّبنا اللغة من نقيض C: النوع على القيمة (لا المتغيّر)، ذاكرةٌ مُدارة (لا malloc)، الكائنات كمؤشّراتٍ لقواميسَ ديناميكية، الدوال قيمٌ تحمل بيئتها (الإغلاق)، تساهلٌ يكمل بصمت. ثم أعدنا حارس C وقتَ-الترجمة عبر TypeScript، وفهمنا أنه يُمحى تماماً وقت التشغيل.
  • العقدة الجديدة: لغةُ الستاك — قيمٌ موسومة، إغلاقات، وحدات ESM، وعقودُ أنواعٍ خياليةٌ تحرس وقتَ الكتابة وتختفي وقت التشغيل.
  • فوق: الإغلاق يفسّر callbacks الإقليم ٢ و hooks الإقليم ٨. الـ erasure يفسّر وجود Zod (٦) وأنواع Prisma المولَّدة (٥). نموذج الكائن=JSON يفسّر سهولة أجسام الطلبات (٠/٣). والاستنتاج البنيويّ يجعل Request/Response/PrismaClient تنساب في كودك بلا احتكاك.

الأبواب المفتوحة: كيف لا يحجب await الخيطَ مع أنه "ينتظر"؟ (٢). من أين تأتي حزمة express التي استوردتها بلا ./؟ (٢). كيف يصير req.body المجهول كائناً موثوقاً؟ (٦).


الوصلة للواقع (نمط SelfLab)

افتح أي ملفٍّ في backend/src/ الآن وستقرؤه، لا تتهجّاه:

بذرة الإقليم التالي

البذرة التالية: الآن تقرأ اللغة. لكن سؤالَ اللغز ظلّ معلّقاً: server.ts ينادي app.listen(PORT, () => {...}) ثم... لا ينتهي البرنامج، بل يظلّ يستقبل طلباتٍ بلا حدّ، بخيطٍ واحد، بلا while(1) ظاهرة، وawait فيه "ينتظر" دون أن يجمّد شيئاً. هذه ليست لغةً غريبة — هذه epoll التي تعرفها من C، تلبس بدلةً أنيقة. ننزل للإقليم ٢ ونشرّح كيف يخدم خيطٌ واحدٌ الآلافَ دون أن ينام.

الستاك الكامل · من باب الخادم إلى داخله ليش قبل كيف · صُمّم لـ يزيد