DeepSeek Harness: التحميل المبني على الاعتماديات وإعادة…

آخر تحديث: 2026-08-31

من "التحكم يدوياً بترتيب التحميل" إلى "صرّح بالاعتماديات، الإطار يتولى الباقي" — التحميل المبني على الاعتماديات يعني أن المطورين يصرّحون بالعلاقات فقط. مُقترناً بإعادة تحميل الوحدات السريعة (HMR)، تغييرات الكود تسري بدون إعادة تشغيل. تجربة التطوير تتحسن بشكل كبير.

💡 نصيحة: تحميل مبني على الاعتماديات + HMR = "غيّر وسري مفعوله." أنت فقط تصرّح بعلاقات الاعتماديات؛ الإطار يتولى ترتيب التحميل. أنت فقط تحفظ الكود؛ الإطار يتولى إعادة التحميل السريع. المكسب الأساسي في الإنتاجية هو إلغاء وقت "انتظار إعادة التشغيل."

📋 المتطلبات المسبقة: أكمل 14-inject.md، تفهم تصريحات اعتماديات inject

1. ما ستتعلمه

التحميل والإعادة القائمان على التبعية


2. رسم الاعتماديات والفرز الطوبولوجي

(1) بناء رسم الاعتماديات

عند البدء، يجتاز الإطار تصريحات inject لجميع الإضافات ويبني رسماً أكليلاً موجهاً غير دوري (DAG):

TYPESCRIPT
function buildDependencyGraph(plugins: Plugin[]) {
  const graph = new DAG()
  for (const plugin of plugins) {
    graph.addNode(plugin.name)
    for (const dep of plugin.inject) {
      graph.addEdge(dep, plugin.name)
    }
  }
  return graph
}

(2) الفرز الطوبولوجي

الفرز الطوبولوجي يُحدد ترتيب التحميل:

TEXT 📖 للعرض فقط
تصريحات الإضافات:
  core:    inject = []
  tools:   inject = ['core']
  llm:     inject = ['core']
  my-tool: inject = ['tools', 'llm']

رسم الاعتماديات:
  core → tools → my-tool
  core → llm   → my-tool

نتيجة الفرز الطوبولوجي: [core, tools, llm, my-tool]

(3) التحميل المتوازي

الإضافات بدون علاقات اعتماد تُحمّل بالتوازي:

100%
graph TB
    subgraph Phase1[المرحلة 1]
        CORE[core]
    end
    subgraph Phase2[المرحلة 2 - متوازي]
        TOOLS[tools]
        LLM[llm]
    end
    subgraph Phase3[المرحلة 3]
        MYTOOL[my-tool]
    end
    CORE --> TOOLS
    CORE --> LLM
    TOOLS --> MYTOOL
    LLM --> MYTOOL

(4) ▶ مثال 4

100%
graph TB
    CORE[core] --> TOOLS[tools]
    CORE --> SESSIONS[sessions]
    CORE --> LOGGER[logger]
    TOOLS --> MY_TOOL[my-tool]
    SESSIONS --> MY_TOOL
    LOGGER --> TRAJECTORY[trajectory]
    SESSIONS --> TRAJECTORY

3. التحميل التلقائي عند جاهزية الاعتماديات

(1) إشباع الاعتماديات الديناميكي

الإضافات لا تحتاج جميع اعتمادياتها مُشبعة عند البدء. عند تسجيل خدمة اعتمادية لاحقاً، تُفعّل الإضافات المنتظرة تلقائياً:

TYPESCRIPT
// Plugin A: inject = ['tools'] — tools لم تُسجّل بعد
// → حالة Fiber: pending

// لاحقاً، إضافة tools تُحمّل وتُسجّل الخدمة
// → Fiber الخاص بـ Plugin A ينتقل تلقائياً لـ active، يستدعي apply

(2) ▶ مثال 2

TYPESCRIPT
export function apply(ctx: Context) {
  if (someCondition) {
    ctx.provide('optional-service', impl)
    // الإضافات المنتظرة المعتمدة على optional-service تُفعّل تلقائياً الآن
  }
}

(3) ▶ مثال 3

100%
sequenceDiagram
    participant F as الإطار
    participant A as Plugin A (inject: tools)
    participant T as Tools Plugin
    
    F->>A: تسجيل → pending (tools غير جاهز)
    F->>T: تسجيل → active
    T->>F: تسجيل خدمة tools
    F->>A: الاعتمادية جاهزة → active
    A->>F: apply() يُنفّذ

(4) اعتماديات غير قابلة للإشباع بشكل دائم

إذا كانت اعتمادية مطلوبة مُصرّح بها لا يمكن إشباعها أبداً:

TEXT 📖 للعرض فقط
[warn] plugin my-plugin has unsatisfied dependency: nonexistent-service
[warn] my-plugin will remain in pending state

الإضافة لا تُخطئ — تبقى في حالة pending للأبد. الاعتماديات الاختيارية (مع ?) لا تُنتج تحذيراً عند عدم الإشباع.


4. آلية إعادة تحميل الوحدات السريعة (HMR)

(1) مفهوم HMR

إعادة تحميل الوحدات السريعة تتيح استبدال كود الإضافة وقت التشغيل بدون إعادة تشغيل DSH بالكامل:

100%
graph LR
    CHANGE[تغيير الكود] --> DETECT[اكتشاف تغيير الملف]
    DETECT --> DISPOSE[Fiber القديم disposing]
    DISPOSE --> LOAD[الكود الجديد حُمّل]
    LOAD --> ACTIVE[Fiber الجديد active]

(2) تفعيل HMR

BASH
pnpm dsh web --patch --watch

علامة --watch تُفعّل مراقبة الملفات؛ عند تغيير كود مصدر الإضافة، تُحفّز إعادة التحميل تلقائياً.

(3) سير HMR الكامل

  1. راقب نظام الملفات يكتشف تغيير src/index.ts
  2. Fiber الإضافة القديمة يدخل حالة disposing
  3. جميع دوال التنظيف تُنفّذ (ctx.effect، التنظيف التلقائي)
  4. الكود الجديد يُترجَم ويُحمّل
  5. Fiber جديد يُنشأ، يدخل pending/active
  6. الإضافات المعتمدة تُعاد تحميلها حسب الحاجة

(4) قيود HMR

السيناريو دعم HMR ملاحظات
تعديل دالة execute منطق الأداة يُحدّث سريعاً
تعديل Config الإعدادات يُعاد التحقق منها
تعديل inject ⚠️ قد يُحفّز إعادة تحميل متسلسلة
تعديل name يتطلب إعادة تشغيل يدوية
تعديل إصدارات الاعتماديات يتطلب إعادة تشغيل يدوية

(5) إعادة التحميل المتسلسلة

عندما تُعاد تحميل إضافة يعتمد عليها آخرون، تُعاد تحميل الإضافات المعتمدة أيضاً:

TEXT 📖 للعرض فقط
إعادة تحميل HMR لإضافة tools → my-tool (يعتمد على tools) يُعاد تحميله أيضاً

هذا يضمن تناسق الاعتماديات، لكن يمكن أن يُسبب "عواصف إعادة تحميل":

TEXT 📖 للعرض فقط
core إعادة تحميل → tools إعادة تحميل → my-tool إعادة تحميل → ... (سلسلة الاعتماديات بأكملها تُعاد تحميلها)

5. السياق المتداخل

(1) هرمية السياق

Cordis يدعم سياقات متداخلة — السياقات الأبناء ترث خدمات السياق الأب لكن يمكنها تجاوزها:

TYPESCRIPT
export function apply(ctx: Context) {
  const childCtx = ctx.extend({
    // تجاوز أو إضافة خدمات
  })
  
  childCtx.plugin({
    name: 'child-plugin',
    apply(innerCtx) {
      // innerCtx يرث خدمات ctx
    }
  })
}

(2) قواعد توريث السياق

TEXT 📖 للعرض فقط
السياق الأب: { tools, llm, sessions }
السياق الابن: { tools(مُتجاوز), cache(مُضاف) }

السياق الابن يرى: { tools(النسخة المُتجاوزة), llm, sessions, cache }

(3) حالات استخدام السياق المتداخل

السيناريو الوصف
عزل الجلسات كل جلسة لها ctx مستقل
نطاق الطلب كل طلب يُنشئ ctx مؤقت
الاختبار إنشاء سياقات اختبار معزولة
متعدد الوكلاء كل وكيل له مجموعة أدوات مستقلة

(4) عمق التداخل

نظرياً غير محدود، لكن التداخل المفرط يؤثر على الأداء:

TYPESCRIPT
// ❌ عميق جداً
ctx.extend().extend().extend().extend()

// ✅ تداخل معتدل
const sessionCtx = ctx.extend({ session })

6. إعادة التحميل المُحفّزة بتغيير الإعدادات

(1) إعادة التحميل التلقائية

عندما يُعدّل المستخدمون إعدادات الإضافة في واجهة الويب، يُحفّز الإطار إعادة التحميل تلقائياً:

100%
graph LR
    UI[واجهة الويب تُعدّل الإعدادات] --> VALID[تحقق Schema]
    VALID --> OLD[Fiber القديم disposing]
    OLD --> NEW[Config جديد + Fiber جديد]
    NEW --> ACTIVE[Fiber active]

(2) تحديث ساخن جزئي للإعدادات

بعض تغييرات الإعدادات لا تتطلب إعادة تحميل كاملة:

TYPESCRIPT
export function apply(ctx: Context) {
  ctx.on('config/updated', (newConfig) => {
    if (newConfig.debug !== ctx.config.debug) {
      ctx.logger.level = newConfig.debug ? 'debug' : 'info'
    }
  })
}

(3) إعدادات تتطلب إعادة تحميل كاملة

تغييرات الإعدادات هذه تتطلب إعادة تحميل كاملة:

(4) إعادة التحميل والاستمرارية

تغييرات الإعدادات تُحفظ في cordis.yml بعد إعادة التحميل:

YAML
plugins:
  my-plugin:
    config:
      debug: true  # عُدّل بواسطة المستخدم عبر واجهة الويب، حُفظ تلقائياً

7. أفضل ممارسات HMR للتطوير

(1) أبقِ apply مُساواة

دالة apply يجب أن تكون مُساواة — استدعاءات متعددة تُنتج نتائج متسقة:

TYPESCRIPT
// ✅ مُساواة: كل apply يُسجّل نفس الأداة
export function apply(ctx: Context) {
  ctx.tools.register(fileCountTool)
}

// ❌ غير مُساواة: apply يُراكم آثاراً جانبية
let counter = 0
export function apply(ctx: Context) {
  counter++  // العداد يزداد بعد إعادة التحميل
}

(2) تجنب الحالة العامة

TYPESCRIPT
// ❌ حالة عامة: الحالة القديمة تبقى بعد إعادة تحميل HMR
const globalCache = new Map()

// ✅ حالة الإغلاق: كل apply يُنشئ حالة جديدة
export function apply(ctx: Context) {
  const cache = new Map()
  ctx.effect(() => () => cache.clear())
}

(3) أكمل دوال التنظيف

أثناء إعادة تحميل HMR، دوال تنظيف Fiber القديم يجب أن تنظّف جميع الموارد بالكامل:

TYPESCRIPT
export function apply(ctx: Context) {
  const ws = new WebSocket('ws://localhost:8080')
  
  // ✅ تسجيل التنظيف
  ctx.effect(() => () => ws.close())
  
  // ❌ نسيان التنظيف → الاتصال القديم يتسرب بعد إعادة التحميل
}

(4) سير عمل التطوير

حلقة تطوير HMR المُوصى بها من Alice:

BASH
# 1. بدء وضع التطوير مع HMR
pnpm dsh web --patch --watch

# 2. اكتب الكود بشكل طبيعي، يُعاد التحميل تلقائياً عند الحفظ
# مخرجات الطرفية:
# [hmr] file changed: src/index.ts
# [hmr] disposing my-plugin (old)
# [hmr] loading my-plugin (new)
# [my-plugin] plugin reloaded

# 3. تحقق من سجلات إعادة التحميل، أكد عدم أخطاء تنظيف

(5) نصائح تصحيح HMR

TYPESCRIPT
// أضف سجل تصحيح في بداية apply
export function apply(ctx: Context) {
  ctx.logger.info('apply called at', new Date().toISOString())
  // ...
}
// إذا رأيت apply استُدعي عدة مرات بشكل غير متوقع، فهذا يعني إعادة تحميل HMR متسلسلة

❓ أسئلة شائعة

س ما الفرق بين HMR وإعادة التشغيل اليدوية؟
ج HMR يُعيد تحميل الإضافات المتغيرة فقط وسلاسل اعتمادياتها؛ الإضافات الأخرى غير متأثرة. إعادة التشغيل اليدوية تُعيد تحميل جميع الإضافات، تستغرق وقتاً أطول.
س هل تُفقد الجلسات أثناء إعادة تحميل HMR؟
ج لا. بيانات الجلسات تُدار بواسطة خدمة sessions، وهي غير متأثرة بـ HMR. فقط حالة الإضافات المُعاد تحميلها تُفقد.
س كيف أعرف إن كانت إعادة التحميل ناجحة؟
ج تحقق من سجلات الطرفية. إعادة التحميل الناجحة تُخرج [hmr] loading xxx (new) و [xxx] plugin reloaded. الفشل يُخرج رسائل خطأ.
س ماذا لو كانت عمليات إعادة التحميل المتسلسلة متكررة جداً؟
ج تحقق إن كنت تعتمد بشكل غير ضروري على إضافات منخفضة المستوى (مثل core). إذا كانت إضافة أداة تعتمد فقط على tools، لن تتسلسل عندما يُعاد تحميل core.
س سلوك HMR في السياقات المتداخلة؟
ج عندما تُعاد تحميل إضافة في سياق ابن، فقط الإضافات داخل ذلك السياق الابن تتأثر؛ السياق الأب غير متأثر.
س هل يجب استخدام HMR في الإنتاج؟
ج لا. HMR أداة تطوير؛ الإنتاج يجب أن يستخدم التحميل المستقر. علامة --watch للتطوير فقط.

📖 ملخص


📝 تمارين

1. ⭐ أساسي: ابدأ pnpm dsh web --patch --watch، عدّل دالة apply لإضافة مُحمّلة (أضف سطر سجل)، احفظ ولاحظ رسائل إعادة تحميل HMR في الطرفية.

2. ⭐⭐ متوسط: أنشئ إضافتين بعلاقة اعتماد (A inject B). ابدأ HMR، عدّل كود B، ولاحظ إن كان A يُعاد تحميله بشكل متسلسل. ثم عدّل كود A فقط وتأكد أن B غير متأثر.

3. ⭐⭐⭐ تحدٍ: اكتب إضافة تستخدم متغيرات عامة (غير مُساواة). بعد إعادة تحميل HMR، لاحظ تغيرات قيم المتغيرات. ثم أعد البناء لحالة إغلاق (مُساواة) وتحقق من سلوك متسق بعد إعادة التحميل. سجّل مقارنة مخرجات الطرفية قبل وبعد إعادة البناء.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%