{"id":"l-at-4-4","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"ابنِ event emitter تُفحص أسماء أحداثه وحمولاتها من خريطة أحداث واحدة، وbuilder يكبر نوع نتيجته مع كل استدعاء method.","html":"<p>يُطلق نظام الدفع في Cartwheel أحداثًا: <code dir=\"ltr\">order.shipped</code> و<code dir=\"ltr\">payment.failed</code> و<code dir=\"ltr\">checkout.opened</code>. وتستمع إليها أدوات التحليلات وخدمة البريد وصفحة الطلب. النسخة الأولى استخدمت emitter عاديًا بأسماء من نوع <code dir=\"ltr\">string</code> وحمولات من نوع <code dir=\"ltr\">any</code>، فأنتجت الخطأين الكلاسيكيين: listener لـ <code dir=\"ltr\">'order.shiped'</code> لم يُطلَق أبدًا، وhandler يقرأ <code dir=\"ltr\">payload.trackingNo</code> من حدث يرسل <code dir=\"ltr\">trackingNumber</code>. كلاهما كان غير مرئي حتى لاحظ أحدهم رسائل بريد مفقودة.</p>\n<p>كل ما في هذا القسم والذي قبله يجتمع لإصلاح ذلك.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">خريطة واحدة، كل الأنواع</h2>\n<p>صِف كل حدث مرة واحدة، في خريطة من الاسم إلى نوع الحمولة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutEvents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  'checkout.opened'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  'order.shipped'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">carrier</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">trackingNumber</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  'payment.failed'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">reason</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span></code></pre></div>\n<p>يمكنك أيضًا توليد جزء من هذه الخريطة بالـ mapped type ذي الـ template literal من <a href=\"/ar/lesson/advanced-typescript/l-at-3-4\">الـ Template Literal Types</a>. وفي الحالتين، الخريطة هي مصدر الحقيقة الواحد: الأسماء مفاتيحها، والحمولات قيمها.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ emitter محدَّد الأنواع</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> EventArgs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">extends</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#953800;--shiki-dark:#FFA657\">payload</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Handler</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">payload</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Emitter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Events</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Record</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>> {</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  #handlers</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Events</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Handler</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>>();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Events</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">event</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">handler</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">payload</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Events</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> set </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#handlers.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">get</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(event);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">set) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      set </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">      this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#handlers.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(event, set);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    set.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(handler);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> set.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">delete</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(handler);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  emit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Events</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">event</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> EventArgs</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Events</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]>)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> handler</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#handlers.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">get</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(event) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> []) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">      // Handlers for K were registered with a matching payload type in on().</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      (handler </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">payload</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)(args[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>النمط هو نمط عميل الـ API، مطبَّقًا على الأحداث. يُستنتج <code dir=\"ltr\">K</code> من اسم الحدث وله مهمة واحدة: البحث عن الحمولة في <code dir=\"ltr\">Events[K]</code>. في <code dir=\"ltr\">on</code>، يسري ذلك النوع إلى معامل الـ handler، فيُحدَّد نوع الـ callback من السياق. وفي <code dir=\"ltr\">emit</code>، يفحص الحمولة التي تمرّرها.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> bus</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Emitter</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">CheckoutEvents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>();</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> off</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> bus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'order.shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sendEmail</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`Tracking: ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">e</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">trackingNumber</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">bus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">emit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'order.shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, { orderId: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1042</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, carrier: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'DHL'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, trackingNumber: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'1Z-999'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">bus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">emit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'checkout.opened'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);                         </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// no payload needed</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">bus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'order.shiped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {});</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Argument of type '\"order.shiped\"' is not assignable to parameter of type 'keyof CheckoutEvents'.</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">bus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">emit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'payment.failed'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, { orderId: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1042</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: … Property 'reason' is missing …</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">off</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// unsubscribe</span></span></code></pre></div>\n<p><code dir=\"ltr\">EventArgs</code> هي حيلة معامل البقية الشرطي من <a href=\"/ar/lesson/advanced-typescript/l-at-3-3\">الـ Conditional Types والتوزيع و infer</a>: الأحداث التي لا حمولة لها لا تأخذ أي وسيط إضافي، والباقية تأخذ واحدًا بالضبط. والأقواس في <code dir=\"ltr\">[P] extends [undefined]</code> توقف التوزيع، فيبقى نوع حمولة مثل <code dir=\"ltr\">string | undefined</code> مطلوبًا بدل أن يُقسم.</p>\n<p>لاحظ أين تعيش الأنواع الفضفاضة. داخل الصنف، تُخزَّن الـ handlers كـ <code dir=\"ltr\">Set&lt;Handler&gt;</code> وتُستدعى عبر تحويل واحد مشروح بتعليق، لأن <code dir=\"ltr\">Map</code> لا تستطيع التعبير عن “نوع القيمة يعتمد على المفتاح”. وفي الخارج، كل method دقيقة. هذا الفصل، أي تخزين بلا أنواع خلف واجهة محدَّدة الأنواع، هو طريقة بناء معظم المكتبات جيدة الأنواع. والحقل الخاص <code dir=\"ltr\">#handlers</code> حقل خاص حقيقي في JavaScript، فلا يستطيع شيء من الخارج الوصول إليه وكسر الثابت.</p>\n<p>حين يكون القلب محدَّد الأنواع، ترث الـ methods الجديدة الدقة شبه مجانًا. method باسم <code dir=\"ltr\">once</code> تحلّ promise بالحمولة التالية تأخذ أربعة أسطر، ونتيجتها محدَّدة النوع لكل حدث دون تعليقات إضافية:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">once&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Events</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">event</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Events</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> off</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(event, (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">payload</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">off</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(payload); });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> shipped</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> bus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">once</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'order.shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { orderId; carrier; trackingNumber }</span></span></code></pre></div>\n<p>هذا هو عائد وضع الأنواع في خريطة واحدة وتمرير <code dir=\"ltr\">K</code> عبرها: كل method تضيفها تُفحص مقابل مصدر الحقيقة نفسه، ولا تكرّر أيٌّ منها نوع حمولة.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>تحديد نوع معامل الحدث بـ keyof Events مباشرة</span></p>\n<p><code dir=\"ltr\">on(event: keyof Events, handler: (payload: Events[keyof Events]) =&gt; void)</code> تبدو شبه متطابقة وتمرّ في الترجمة. لكن دون معامل نوع، تكون حمولة الـ handler هي الـ union من <em>كل</em> الحمولات، فيكون <code dir=\"ltr\">e.trackingNumber</code> خطأً في <code dir=\"ltr\">order.shipped</code>، ولن تكون <code dir=\"ltr\">e.reason</code> “مسموحة” إلا بعد تضييق قيمة لا مميِّز لها. معامل النوع هو ما يربط الاسم المحدّد بحمولته المحدّدة.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">بنّاؤون يكبر نوعهم</h2>\n<p>الـ builder (الباني) يسلسل استدعاءات methods لتجميع شيء خطوة بخطوة. والحيلة على مستوى الأنواع هي حمل ما بُني حتى الآن في معامل نوع، وإعادة نوع جديد أوسع من كل استدعاء. إليك builder استعلامات صغيرًا لسجل الطلبات في Cartwheel:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">totalCents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">customer</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderQuery</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Selected</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> never</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  readonly</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> #fields</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> readonly</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[];</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  constructor</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">fields</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> readonly</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> []) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#fields </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> fields;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">fields</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[])</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderQuery</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Selected</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> OrderQuery</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Selected</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>([</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#fields, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">fields]);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  run</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">rows</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[])</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Pick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Selected</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>[] {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">    // #fields holds exactly the keys in Selected.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> rows.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">row</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Object.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">fromEntries</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.#fields.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">f</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [f, row[f]])) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Pick</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Selected</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> rows</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> OrderQuery</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">().</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'id'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'totalCents'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">run</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(allOrders);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">rows[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].totalCents; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// number</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">rows[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].customer;   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Property 'customer' does not exist on type 'Pick&#x3C;Order, \"id\" | \"totalCents\">'.</span></span></code></pre></div>\n<p>يبدأ <code dir=\"ltr\">Selected</code> بـ <code dir=\"ltr\">never</code> (لا شيء مختار) وكل <code dir=\"ltr\">select</code> تُعيد <code dir=\"ltr\">OrderQuery&lt;Selected | K&gt;</code>. لا تستطيع الـ method تغيير نوع الكائن الذي تُستدعى عليه، فتُعيد كل خطوة نسخة جديدة. وهذا أيضًا تصميم جيد وقت التشغيل: البنّاؤون الذين لا يعدّلون أبدًا يمكن مشاركتهم وإعادة استخدامهم بأمان. وهي التقنية نفسها التي يستخدمها بنّاؤو الاستعلامات مثل Kysely وDrizzle على نطاق أكبر بكثير.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>أبقِ أنواع الـ builder سطحية</span></p>\n<p>كل خطوة في الـ builder تضيف إلى نوع يجب على المحرّر عرضه وعلى الـ compiler فحصه. الـ builder الذي فيه عشرة معاملات نوع وconditional types على كل method ينتج رسائل خطأ لا يستطيع أحد قراءتها. تتبّع الحقيقة أو الحقيقتين اللتين يحتاجهما المستدعون فعلًا، مثل الحقول المختارة، وأبقِ كل ما عدا ذلك بسيطًا.</p>\n</aside>\n<p>في التمرين ستبني ناقل الأحداث محدَّد الأنواع في Cartwheel وbuilder اختيار صغيرًا. وبه يكتمل قسم الأنماط؛ والقسم 5 ينتقل إلى إيصال كل هذا إلى الإنتاج، بدءًا بخيارات الـ compiler التي تجعل هذه الأنواع جديرة بالثقة.</p>\n","toc":[{"id":"sec-1","text":"خريطة واحدة، كل الأنواع","level":2},{"id":"sec-2","text":"الـ emitter محدَّد الأنواع","level":2},{"id":"sec-3","text":"بنّاؤون يكبر نوعهم","level":2}],"takeaways":["نوع خريطة الأحداث (<code dir=\"ltr\">{ 'order.shipped': { orderId: number } }</code>) هو مصدر الحقيقة الواحد لأسماء الأحداث وأنواع حمولاتها معًا.","<code dir=\"ltr\">on&lt;K extends keyof Events&gt;(event: K, handler: (payload: Events[K]) =&gt; void)</code> تربط معامل الـ handler باسم الحدث الذي يمرّره المستدعي.","معامل البقية الشرطي يتيح إطلاق الأحداث التي لا حمولة لها دون وسيط وهمي.","يستطيع الـ builder حمل الحالة في معامل نوع، مُعيدًا نوعًا جديدًا أوسع من كل استدعاء، فيعكس نوع النتيجة النهائي كل خطوة.","أبقِ التخزين غير محدَّد الأنواع داخل الصنف والأنواع الدقيقة على methods العامة، كي يعيش التحويل الوحيد في مكان واحد."],"further":[{"title":"Generic Classes","url":"https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-classes"},{"title":"Classes (this-based types and parameter properties)","url":"https://www.typescriptlang.org/docs/handbook/2/classes.html"},{"title":"Rest parameters with tuple types (TypeScript 3.0 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html"}],"quiz":[{"q":"<p>مع <code dir=\"ltr\">bus = new Emitter&lt;CheckoutEvents&gt;()</code>، ما نوع <code dir=\"ltr\">p</code>؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutEvents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  'order.shipped'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">carrier</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  'payment.failed'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">reason</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">bus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'payment.failed'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(p.reason));</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">CheckoutEvents[keyof CheckoutEvents]</code>، الـ union من الحمولتين","why":"هذا ما سيحدث لو كان نوع اسم الحدث <code dir=\"ltr\">keyof Events</code> دون معامل نوع. أما <code dir=\"ltr\">K</code> فيلتقط الاسم المحدّد."},{"text":"<code dir=\"ltr\">unknown</code>، لأن الـ handlers مخزّنة في Map بلا أنواع","why":"طريقة تخزين الـ handlers تفصيل في التنفيذ؛ التوقيع العام هو ما يحدّد ما يراه المستدعون."},{"text":"<code dir=\"ltr\">any</code>، لأن الـ callbacks تُحدَّد أنواعها من السياق","why":"تحديد النوع من السياق يعطي <code dir=\"ltr\">p</code> نوع المعامل من التوقيع، وهو دقيق هنا."},{"text":"<code dir=\"ltr\">{ orderId: number; reason: string }</code>","why":"صحيح. يُستنتج <code dir=\"ltr\">K</code> على أنه <code dir=\"ltr\">'payment.failed'</code>، ومعامل الـ handler هو <code dir=\"ltr\">CheckoutEvents[K]</code>."}],"answer":3},{"q":"<p>لماذا نحدّد نوع <code dir=\"ltr\">emit</code> بـ <code dir=\"ltr\">...args: EventArgs&lt;Events[K]&gt;</code> بدل <code dir=\"ltr\">payload: Events[K]</code>؟</p>","options":[{"text":"كي يمكن إطلاق الأحداث التي حمولتها <code dir=\"ltr\">undefined</code> هكذا <code dir=\"ltr\">emit('checkout.opened')</code> دون وسيط ثانٍ.","why":"صحيح. ينتج الشرط tuple فارغًا للأحداث التي لا حمولة لها، وtuple من عنصر واحد في غير ذلك."},{"text":"لأن معاملات البقية أسرع وقت التشغيل.","why":"الاختيار يتعلّق بتوقيع النوع؛ والفرق وقت التشغيل لا يُذكر."},{"text":"لأن <code dir=\"ltr\">Events[K]</code> لا يمكن استخدامها نوعًا لمعامل.","why":"يمكن ذلك؛ <code dir=\"ltr\">on</code> تستخدمها لمعامل الـ handler. صيغة البقية تحلّ فقط حالة الحمولة الغائبة."}],"answer":0},{"q":"<p>ما نوع <code dir=\"ltr\">rows</code>؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> rows</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> OrderQuery</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">().</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'id'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">select</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'totalCents'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">run</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(allOrders);</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">Order[]</code>، لأن <code dir=\"ltr\">run</code> تُعيد دائمًا طلبات كاملة","why":"في هذا الـ builder تُعيد <code dir=\"ltr\">run</code> النوع <code dir=\"ltr\">Pick&lt;Order, Selected&gt;[]</code>، و<code dir=\"ltr\">Selected</code> كبر مع كل استدعاء."},{"text":"<code dir=\"ltr\">Pick&lt;Order, 'id' | 'totalCents'&gt;[]</code>","why":"صحيح. كل <code dir=\"ltr\">select</code> تُعيد <code dir=\"ltr\">OrderQuery&lt;Selected | K&gt;</code>، فيراكم معامل النوع الحقلين كليهما."},{"text":"<code dir=\"ltr\">Pick&lt;Order, 'totalCents'&gt;[]</code>، لأن الاستدعاء الثاني يستبدل الأول","why":"الاستدعاء الثاني يوسّع <code dir=\"ltr\">Selected</code> بـ union؛ لا يستبدله."}],"answer":1},{"q":"<p>لماذا يُعيد كل استدعاء لـ <code dir=\"ltr\">select</code> كائن <code dir=\"ltr\">OrderQuery</code> جديدًا بدل تعديل <code dir=\"ltr\">this</code> وإعادته؟</p>","options":[{"text":"لأن الأصناف لا يمكنها إعادة <code dir=\"ltr\">this</code> من الـ methods.","why":"يمكنها ذلك؛ والـ APIs المتسلسلة تفعله كثيرًا. المشكلة فيما يستطيع نوع <code dir=\"ltr\">this</code> التعبير عنه."},{"text":"لأن الـ method لا تستطيع تغيير نوع الكائن الذي تُستدعى عليه، فيحتاج النوع الأوسع إلى قيمة جديدة.","why":"صحيح. <code dir=\"ltr\">this</code> يحتفظ بنوعه طوال حياته؛ وإعادة نسخة جديدة بمعامل نوع جديد هي الطريقة التي يكبر بها النوع."},{"text":"لأن التعديل غير مسموح في أصناف TypeScript.","why":"يسمح TypeScript بالتعديل بحرية؛ هذا قيد في تحديد الأنواع، لا قاعدة في اللغة."}],"answer":1}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype Order = { id: number; status: OrderStatus; totalCents: number; customer: string };\n\nconst ORDERS: Order[] = [\n  { id: 1042, status: 'shipped', totalCents: 18900, customer: 'Mona' },\n  { id: 1043, status: 'processing', totalCents: 4500, customer: 'Karim' },\n];\n\nclass Emitter<Events> {\n  #handlers = new Map<string, Set<(payload: any) => void>>();\n\n  on(event: string, handler: (payload: any) => void): () => void {\n    let set = this.#handlers.get(event);\n    if (!set) {\n      set = new Set();\n      this.#handlers.set(event, set);\n    }\n    set.add(handler);\n    return () => set.delete(handler);\n  }\n\n  emit(event: string, payload?: any): void {\n    for (const handler of this.#handlers.get(event) ?? []) handler(payload);\n  }\n}\n\nclass OrderQuery {\n  readonly #fields: readonly (keyof Order)[];\n\n  constructor(fields: readonly (keyof Order)[] = []) {\n    this.#fields = fields;\n  }\n\n  select(...fields: (keyof Order)[]): OrderQuery {\n    return new OrderQuery([...this.#fields, ...fields]);\n  }\n\n  run(rows: Order[]): Partial<Order>[] {\n    return rows.map((row) => Object.fromEntries(this.#fields.map((f) => [f, row[f]])));\n  }\n}\n\n// ---- Type tests (do not edit) ----\ntype CheckoutEvents = {\n  'checkout.opened': undefined;\n  'order.shipped': { orderId: number; carrier: string };\n  'payment.failed': { orderId: number; reason: string };\n};\n\nfunction typeTests(bus: Emitter<CheckoutEvents>) {\n  bus.on('payment.failed', (e) => {\n    type T1 = Expect<Equal<typeof e, { orderId: number; reason: string }>>;\n  });\n  bus.emit('checkout.opened');\n  bus.emit('order.shipped', { orderId: 1042, carrier: 'DHL' });\n  // @ts-expect-error: 'order.shiped' is a typo\n  bus.on('order.shiped', () => {});\n  // @ts-expect-error: payment.failed needs a reason\n  bus.emit('payment.failed', { orderId: 1042 });\n  // @ts-expect-error: order.shipped needs a payload\n  bus.emit('order.shipped');\n}\n\nconst rows = new OrderQuery().select('id', 'status').run(ORDERS);\ntype T2 = Expect<Equal<typeof rows, Pick<Order, 'id' | 'status'>[]>>;\nconst more = new OrderQuery().select('id').select('totalCents').run(ORDERS);\ntype T3 = Expect<Equal<typeof more, Pick<Order, 'id' | 'totalCents'>[]>>;\n\nconsole.log(rows, more);\n"},"solution":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype Order = { id: number; status: OrderStatus; totalCents: number; customer: string };\n\nconst ORDERS: Order[] = [\n  { id: 1042, status: 'shipped', totalCents: 18900, customer: 'Mona' },\n  { id: 1043, status: 'processing', totalCents: 4500, customer: 'Karim' },\n];\n\ntype EventArgs<P> = [P] extends [undefined] ? [] : [payload: P];\ntype Handler = (payload: never) => void;\n\nclass Emitter<Events extends Record<string, unknown>> {\n  #handlers = new Map<keyof Events, Set<Handler>>();\n\n  on<K extends keyof Events>(event: K, handler: (payload: Events[K]) => void): () => void {\n    let set = this.#handlers.get(event);\n    if (!set) {\n      set = new Set();\n      this.#handlers.set(event, set);\n    }\n    set.add(handler);\n    return () => set.delete(handler);\n  }\n\n  emit<K extends keyof Events>(event: K, ...args: EventArgs<Events[K]>): void {\n    for (const handler of this.#handlers.get(event) ?? []) {\n      // Handlers for K were registered with a matching payload type in on().\n      (handler as (payload: unknown) => void)(args[0]);\n    }\n  }\n}\n\nclass OrderQuery<Selected extends keyof Order = never> {\n  readonly #fields: readonly (keyof Order)[];\n\n  constructor(fields: readonly (keyof Order)[] = []) {\n    this.#fields = fields;\n  }\n\n  select<K extends keyof Order>(...fields: K[]): OrderQuery<Selected | K> {\n    return new OrderQuery<Selected | K>([...this.#fields, ...fields]);\n  }\n\n  run(rows: Order[]): Pick<Order, Selected>[] {\n    // #fields holds exactly the keys in Selected.\n    return rows.map((row) => Object.fromEntries(this.#fields.map((f) => [f, row[f]])) as Pick<Order, Selected>);\n  }\n}\n\n// ---- Type tests (do not edit) ----\ntype CheckoutEvents = {\n  'checkout.opened': undefined;\n  'order.shipped': { orderId: number; carrier: string };\n  'payment.failed': { orderId: number; reason: string };\n};\n\nfunction typeTests(bus: Emitter<CheckoutEvents>) {\n  bus.on('payment.failed', (e) => {\n    type T1 = Expect<Equal<typeof e, { orderId: number; reason: string }>>;\n  });\n  bus.emit('checkout.opened');\n  bus.emit('order.shipped', { orderId: 1042, carrier: 'DHL' });\n  // @ts-expect-error: 'order.shiped' is a typo\n  bus.on('order.shiped', () => {});\n  // @ts-expect-error: payment.failed needs a reason\n  bus.emit('payment.failed', { orderId: 1042 });\n  // @ts-expect-error: order.shipped needs a payload\n  bus.emit('order.shipped');\n}\n\nconst rows = new OrderQuery().select('id', 'status').run(ORDERS);\ntype T2 = Expect<Equal<typeof rows, Pick<Order, 'id' | 'status'>[]>>;\nconst more = new OrderQuery().select('id').select('totalCents').run(ORDERS);\ntype T3 = Expect<Equal<typeof more, Pick<Order, 'id' | 'totalCents'>[]>>;\n\nconsole.log(rows, more);\n"},"checks":[{"id":"types","typecheck":true},{"id":"delivers","test":"const bus = new Emitter(); const got = []; bus.on('a', (p) => got.push('a:' + p.n)); bus.on('b', (p) => got.push('b')); bus.emit('a', { n: 1 }); return got.join() === 'a:1';"},{"id":"unsubscribes","test":"const bus = new Emitter(); let n = 0; const off = bus.on('x', () => { n++; }); bus.emit('x'); off(); bus.emit('x'); return n === 1;"},{"id":"selects","test":"const r = new OrderQuery().select('id').select('customer').run([{ id: 1, status: 'shipped', totalCents: 5, customer: 'Lea' }]); return JSON.stringify(r) === '[{\"id\":1,\"customer\":\"Lea\"}]';"}],"title":"حدّد أنواع ناقل الأحداث وبنّاء الاستعلامات","prompt":"<p>اجعل الأداتين في Cartwheel محدَّدتي الأنواع بالكامل دون تغيير سلوكهما وقت التشغيل:</p>\n<ol>\n<li><code dir=\"ltr\">Emitter&lt;Events&gt;</code>: يجب ألّا تقبل <code dir=\"ltr\">on(event, handler)</code> إلا أسماء أحداث من <code dir=\"ltr\">Events</code> وأن تعطي الـ handler نوع حمولة ذلك الحدث؛ ويجب أن تفحص <code dir=\"ltr\">emit(event, payload)</code> الحمولة، وأن تُطلَق الأحداث التي حمولتها <code dir=\"ltr\">undefined</code> دون وسيط حمولة.</li>\n<li><code dir=\"ltr\">OrderQuery</code>: كل استدعاء لـ <code dir=\"ltr\">select(...fields)</code> يضيف إلى الحقول المختارة، و<code dir=\"ltr\">run(rows)</code> تُعيد <code dir=\"ltr\">Pick&lt;Order, …&gt;[]</code> من الحقول المختارة بالضبط.</li>\n</ol>\n<p>أبقِ أي تحويل داخل الصنفين، في مكان واحد مشروح بتعليق لكلٍّ منهما.</p>\n","hints":["أعطِ <code dir=\"ltr\">on</code> و<code dir=\"ltr\">emit</code> معامل نوع <code dir=\"ltr\">K extends keyof Events</code> وابحث عن الحمولة بـ <code dir=\"ltr\">Events[K]</code>. وقيّد الصنف: <code dir=\"ltr\">Emitter&lt;Events extends Record&lt;string, unknown&gt;&gt;</code>.","للأحداث التي لا حمولة لها، استخدم معامل بقية شرطيًا: <code dir=\"ltr\">type EventArgs&lt;P&gt; = [P] extends [undefined] ? [] : [payload: P];</code> و<code dir=\"ltr\">emit&lt;K …&gt;(event: K, ...args: EventArgs&lt;Events[K]&gt;)</code>.","أعطِ <code dir=\"ltr\">OrderQuery</code> معامل نوع <code dir=\"ltr\">Selected extends keyof Order = never</code>؛ تُعيد <code dir=\"ltr\">select&lt;K extends keyof Order&gt;(...fields: K[])</code> القيمة <code dir=\"ltr\">new OrderQuery&lt;Selected | K&gt;(…)</code>، وتُعيد <code dir=\"ltr\">run</code> النوع <code dir=\"ltr\">Pick&lt;Order, Selected&gt;[]</code> بتحويل واحد."],"explanation":"<p>خريطة الأحداث هي المكان الوحيد الذي تُكتب فيه أنواع الحمولات؛ يُستنتج <code dir=\"ltr\">K</code> من اسم الحدث ويربطه بـ <code dir=\"ltr\">Events[K]</code> للـ handler وللحمولة معًا. معامل البقية الشرطي ينتج صفرًا من الوسائط للحمولات <code dir=\"ltr\">undefined</code>، والأقواس تمنعه من التوزيع على الـ unions. وفي الداخل، لا تستطيع <code dir=\"ltr\">Map</code> ربط المفاتيح بأنواع القيم، فتُخزَّن الـ handlers بشكل فضفاض وتُستدعى عبر تحويل واحد. أما الـ builder فيحمل المفاتيح المختارة في معامل نوعه: كل <code dir=\"ltr\">select</code> تُعيد نسخة جديدة بـ union أوسع، وتحوّل <code dir=\"ltr\">run</code> ذلك الـ union إلى <code dir=\"ltr\">Pick</code>.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ، بما فيها كل اختبارات الأنواع","delivers":"<code dir=\"ltr\">emit</code> تستدعي فقط الـ handlers الخاصة بذلك الحدث، مع الحمولة","unsubscribes":"الدالة التي تُعيدها <code dir=\"ltr\">on</code> تُزيل الـ handler","selects":"<code dir=\"ltr\">run</code> تُعيد الحقول المختارة فقط، عبر عدة استدعاءات لـ <code dir=\"ltr\">select</code>"}},"words":594,"readMinutes":3}