DeepSeek Harness: التنظيف التلقائي و ctx.effect()
آخر تحديث: 2026-08-31
أقوى تصاميم Cordis هو التنظيف التلقائي — عند إلغاء تحميل إضافة، تُستعاد جميع الموارد المُسجّلة عبر ctx تلقائياً دون حاجة لتحرير يدوي. لكن عندما لا تُدار الموارد مباشرة بواسطة ctx، ctx.effect() هو نقطة دخولك للتنظيف اليدوي.
📋 المتطلبات المسبقة: أكمل 11-first-plugin.md، تفهم دالة apply و Context
1. ما ستتعلمه
- مبدأ التنظيف التلقائي: جميع الموارد المُسجّلة عبر ctx تُستعاد تلقائياً
ctx.effect(): تسجيل تنظيف الموارد يدوياً- نمط دالة التنظيف المُعادة
- التنظيف الصحيح لـ setInterval/setTimeout
- أنماط تنظيف اتصالات الشبكة
- أخطاء التنظيف الشائعة وكيفية تجنبها
2. مبدأ التنظيف التلقائي
(1) ctx هو مركز تسجيل الموارد
كل نسخة ctx تحتفظ بسجل يُسجّل جميع الموارد القابلة للتنظيف التي سجّلتها الإضافة:
class Context {
private _disposables: Disposable[] = []
register(disposable: Disposable) {
this._disposables.push(disposable)
}
dispose() {
for (const d of this._disposables.reverse()) {
d.dispose()
}
}
}
عند إلغاء تحميل الإضافة، ctx.dispose() يستعيد جميع الموارد بترتيب التسجيل العكسي.
(2) أنواع الموارد القابلة للتنظيف التلقائي
الموارد التالية المُسجّلة عبر ctx تُنظّف تلقائياً جميعها:
| طريقة التسجيل | سلوك التنظيف |
|---|---|
ctx.on('event', handler) |
إزالة مستمع الحدث |
ctx.setInterval(fn, ms) |
مسح المؤقت |
ctx.setTimeout(fn, ms) |
مسح المؤقت |
ctx.command('name') |
إلغاء تسجيل الأمر |
ctx.service('name', impl) |
إلغاء تسجيل الخدمة |
(3) ▶ مثال 3
import { Context } from '@deepseek-ai/cordis'
export const name = 'auto-cleanup-demo'
export function apply(ctx: Context) {
// جميع التسجيلات أدناه تُنظّف تلقائياً
ctx.on('session/created', (s) => {
ctx.logger.info(`session: ${s.id}`)
})
ctx.setInterval(() => {
ctx.logger.info('tick')
}, 10000)
ctx.command('demo')
.action(() => 'demo command')
}
// عند إلغاء تحميل الإضافة: المستمع أُزيل + المؤقت مُسح + الأمر أُلغي، صفر كود يدوي
3. ctx.effect(): تنظيف الموارد يدوياً
(1) لماذا الحاجة للتنظيف اليدوي
ليست كل الموارد يمكن تسجيلها مباشرة عبر ctx. مثلاً:
- اتصالات أنشأتها مكتبات طرف ثالث (مثلاً WebSocket، تجمعات اتصال قاعدة البيانات)
- موارد أنشأتها واجهات Node.js الأصلية (مثلاً
net.Server) - تعديلات الحالة العامة (مثلاً متغيرات
process.envمؤقتة)
هنا يأتي دور ctx.effect():
ctx.effect(() => {
// أعد دالة تنظيف
return () => {
// منطق التنظيف
}
})
(2) ▶ مثال 2
import { Context } from '@deepseek-ai/cordis'
export const name = 'manual-cleanup'
export function apply(ctx: Context) {
const connection = createExternalConnection()
ctx.effect(() => {
return () => {
connection.close()
ctx.logger.info('connection closed')
}
})
}
ctx.effect() يستقبل دالة مصنع تُعيد دالة تنظيف. عند إلغاء تحميل الإضافة، Cordis يستدعي دالة التنظيف لتحرير الموارد.
(3) ▶ مثال 3
// النمط 1: إعادة دالة التنظيف (مُوصى به)
ctx.effect(() => {
const ws = new WebSocket('ws://localhost:8080')
return () => ws.close()
})
// النمط 2: تمرير مرجع دالة التنظيف
const cleanup = () => { /* ... */ }
ctx.effect(cleanup)
ميزة النمط 1 أن إنشاء المورد وتنظيفه في نفس الإغلاق، مما يحافظ على تماسك المنطق.
4. نمط دالة التنظيف المُعادة
(1) النمط القياسي
ctx.effect(() => {
const resource = acquireResource()
return () => {
releaseResource(resource)
}
})
هذا النمط "اكتساب-تحرير" مشابه لـ try-finally:
// نموذج try-finally المُكافئ ذهنياً
try {
const resource = acquireResource()
// استخدام المورد
} finally {
releaseResource(resource)
}
(2) تنظيف موارد متعددة
export function apply(ctx: Context) {
ctx.effect(() => {
const db = openDatabase()
const cache = openCache()
return () => {
cache.close() // أغلق التابع أولاً
db.close() // ثم أغلق ما يعتمد عليه
}
})
}
⚠️ ترتيب التنظيف مهم — أغلق الكائنات التي تعتمد على موارد أخرى أولاً، ثم أغلق الموارد التي تعتمد عليها.
(3) معالجة الأخطاء في دوال التنظيف
ctx.effect(() => {
const conn = createConnection()
return () => {
try {
conn.close()
} catch (e) {
ctx.logger.warn('cleanup error:', e)
}
}
})
الاستثناءات في دوال التنظيف لا يجب أن تقطع تنظيف الموارد الأخرى. Cordis يحمي كل دالة تنظيف بـ try-catch داخلياً، لكن المعالجة الصريحة أكثر أماناً.
5. التنظيف الصحيح لـ setInterval/setTimeout
(1) طريقة التنظيف التلقائي (مُوصى بها)
export function apply(ctx: Context) {
// استخدم ctx.setInterval — تنظيف تلقائي
ctx.setInterval(() => {
ctx.logger.info('heartbeat')
}, 30000)
}
(2) واجهة API الأصلية + ctx.effect()
إذا كان يجب استخدام setInterval الأصلية:
export function apply(ctx: Context) {
const timer = setInterval(() => {
ctx.logger.info('heartbeat')
}, 30000)
ctx.effect(() => {
return () => clearInterval(timer)
})
}
(3) المقارنة
| الطريقة | حجم الكود | الموثوقية | مُوصى بها |
|---|---|---|---|
ctx.setInterval |
سطر 1 | عالية (تلقائية) | ✅ |
أصلية + ctx.effect() |
3 أسطر | متوسطة (يدوية) | ⚠️ |
| أصلية (بدون تنظيف) | سطر 1 | منخفضة (تسرب) | ❌ |
(4) فخ setTimeout
// ❌ خطأ: setTimeout يظل يعمل بعد إلغاء التحميل
export function apply(ctx: Context) {
setTimeout(() => {
ctx.logger.info('delayed action') // الإضافة قد تكون أُلغي تحميلها بالفعل!
}, 5000)
}
// ✅ صحيح: استخدم ctx.setTimeout
export function apply(ctx: Context) {
ctx.setTimeout(() => {
ctx.logger.info('delayed action') // لن يعمل بعد إلغاء التحميل
}, 5000)
}
6. أنماط تنظيف اتصالات الشبكة
(1) خادم HTTP
import { createServer } from 'http'
export function apply(ctx: Context) {
const server = createServer((req, res) => {
res.end('ok')
})
server.listen(3456)
ctx.effect(() => {
return () => {
server.close()
ctx.logger.info('HTTP server closed')
}
})
}
(2) اتصال WebSocket
import WebSocket from 'ws'
export function apply(ctx: Context) {
const ws = new WebSocket('ws://localhost:8080')
ws.on('open', () => {
ctx.logger.info('ws connected')
})
ctx.effect(() => {
return () => {
if (ws.readyState === WebSocket.OPEN) {
ws.close()
}
}
})
}
(3) تجمع اتصال قاعدة البيانات
import { Pool } from 'pg'
export function apply(ctx: Context) {
const pool = new Pool({
connectionString: 'postgresql://localhost/mydb',
max: 10
})
ctx.effect(() => {
return async () => {
await pool.end()
ctx.logger.info('db pool closed')
}
})
}
⚠️ دوال التنظيف يمكن أن تكون غير متزامنة. Cordis سينتظر اكتمال التنظيف غير المتزامن قبل المتابعة بالتنظيف اللاحق.
(4) تنظيف مستمعي الأحداث
export function apply(ctx: Context) {
const emitter = getExternalEmitter()
const handler = (data: any) => {
ctx.logger.info('event:', data)
}
emitter.on('data', handler)
ctx.effect(() => {
return () => {
emitter.off('data', handler)
}
})
}
7. أخطاء التنظيف الشائعة
(1) نسيان تسجيل التنظيف
// ❌ تسرب: المؤقت يظل يعمل بعد إلغاء التحميل
export function apply(ctx: Context) {
setInterval(() => {
console.log('orphan timer')
}, 1000)
}
الحل: استخدم ctx.setInterval أو سجّل ctx.effect().
(2) ترتيب تنظيف خاطئ
// ❌ إغلاق قاعدة البيانات أولاً، ثم إغلاق الذاكرة المؤقتة التي تعتمد عليها
ctx.effect(() => {
const db = openDB()
const cache = new Cache(db)
return () => {
db.close() // أُغلقت db أولاً
cache.close() // الذاكرة المؤقتة تصل لـ db داخلياً → خطأ
}
})
الحل: اعكس ترتيب التنظيف.
(3) استثناء غير ملتقط في التنظيف
// ❌ دالة التنظيف ترمي استثناءً، تقطع التنظيف اللاحق
ctx.effect(() => {
return () => {
throw new Error('cleanup failed') // تأثيرات أخرى قد لا تُنفّذ
}
})
الحل: غلّف منطق التنظيف بـ try-catch.
(4) مرجع إغلاق قديم
// ❌ يشير لمتغير خارجي قد يكون غير صالح بعد إلغاء التحميل
let globalRef: SomeObject | null = new SomeObject()
export function apply(ctx: Context) {
ctx.effect(() => {
return () => {
globalRef!.cleanup() // globalRef قد أُعيّن لـ null بكود آخر
}
})
}
الحل: التقط المرجع داخل إغلاق effect.
❓ أسئلة شائعة
ctx.on() يُسجّل مستمع حدث يُزال تلقائياً عند إلغاء التحميل. ctx.effect() يُسجّل دالة تنظيف عادية تُستدعى عند إلغاء التحميل. كلاهما يُكمّل الآخر: ctx.on يعالج الأحداث، ctx.effect يعالج الموارد الأخرى.📖 ملخص
- التنظيف التلقائي هو ميزة Cordis الأساسية: الموارد المُسجّلة عبر ctx تُستعاد تلقائياً عند إلغاء التحميل
ctx.effect()يُسجّل تنظيف موارد يدوي، يُعيد دالة تنظيف- دوال التنظيف تُنفّذ بترتيب التسجيل العكسي (LIFO)؛ انتبه لترتيب الاعتماديات
- فضّل
ctx.setInterval/setTimeoutعلى واجهات API الأصلية - اتصالات الشبكة وتجمعات اتصال قواعد البيانات إلخ يجب استخدام
ctx.effect()لتسجيل التنظيف - غلّف دوال التنظيف بـ try-catch لمنع الاستثناءات من قطع التنظيف اللاحق
📝 تمارين
1. ⭐ أساسي: اكتب إضافة تُخرج عدّاداً كل ثانية باستخدام ctx.setInterval. ابدأها وتأكد أن المؤقت يُنظّف بشكل صحيح عند إلغاء التحميل.
2. ⭐⭐ متوسط: اكتب إضافة تنشئ خادم HTTP يستمع على المنفذ 3456، مع تسجيل التنظيف عبر ctx.effect(). ابدأها، اختبر طلبات HTTP، ثم ألغِ تحميل الإضافة وتأكد أن المنفذ حُرّر.
3. ⭐⭐⭐ تحدٍ: اكتب إضافة تدير اتصال WebSocket وتجمع اتصال قاعدة بيانات، مع التأكد أن التنظيف يُغلق WebSocket أولاً ثم قاعدة البيانات، مع معالجة الاستثناءات في دوال التنظيف. الاختبار: ارمِ خطأً متعمداً أثناء إغلاق قاعدة البيانات، وتحقق أن WebSocket أُغلق بشكل صحيح رغم ذلك.