كان من المفترض أرشفة عشرين جهة اتصال في نظام إدارة علاقات العملاء (CRM)، ومع ذلك ومضت واجهة المستخدم (UI) بشارة نجاح خضراء، بينما لم تفعل العملية شيئاً في صمت. أداة مساعدة (helper) بلغة TypeScript تجعل حقل error إلزامياً في عمليات Supabase-JS الآن تجبر المطورين على مواجهة هذا الفشل بدلاً من التستر عليه.
لماذا يُعد شكل الإرجاع في Supabase-JS فخاً
لا يقوم عميل JavaScript الخاص بـ Supabase (@supabase/supabase-js) برمي استثناءات (exceptions) عندما يتم رفض عملية كتابة في قاعدة البيانات. بدلاً من ذلك، فإنه يحل الوعد (promise) بكائن { data, error }. إذا منعت سياسة أمن مستوى الصف (RLS)، أو انتهاك مفتاح فريد، أو أي قيد آخر الاستعلام، فإن data تعود كـ null ويحتوي error على رسالة قاعدة البيانات. يفترض العميل أن المستدعي سيفحص error؛ فهو لا يوقف الاستدعاء أبداً.
في الممارسة العملية، تتعامل العديد من قواعد الأكواد مع الاستدعاء كعملية "أطلق وانسَ" (fire-and-forget):
await supabase
.from('contacts')
.update({ statut: 'ancien', archived_at: now })
.eq('id', id)
return { ok: true }
عندما يتم حظر عملية update بواسطة قاعدة RLS، لا يزال الوعد يُحل. ولأن المطور لا يقوم أبداً بتفكيك (destructuring) الكائن { error } ، فإن الفشل يظل غير مرئي. تعيد الدالة { ok: true } ، وتظهر واجهة المستخدم النجاح، وتظل البيانات دون تغيير. لا تظهر أي سجلات (logs)، ولا يتم إطلاق تنبيه Sentry، ويمكن للخطأ أن يستمر لأيام.
أداة الـ linter ليست كافية
يمكن لأدوات التحليل الساكن (Static analysis) أن تحذر عند تجاهل خاصية error ، ولكنها لا تستطيع فرض عقد وقت التشغيل (runtime contract). لا يزال بإمكان المطور كتابة const _ = await … وتجاهل التحذير، أو يمكنه إضافة تعليق لإسكات القاعدة. المشكلة الأساسية هي أن نظام الأنواع (type system) يسمح بنجاح الاستدعاء دون ذكر error على الإطلاق.
أداة المساعدة mutate(): جعل معالجة الأخطاء إلزامية
قام المؤلف ببناء غلاف (wrapper) صغير يسمى mutate() يغير شكل نوع الإرجاع. بدلاً من { data, error } ، تعيد أداة المساعدة زوجاً (tuple) [data, error] حيث يكون error حقلاً مطلوباً. عندها يرفض TypeScript تجميع (compile) أي استدعاء يتجاهل العنصر الثاني.
async function mutate<T>(promise: Promise<{ data: T | null; error: any }>) {
const { data, error } = await promise
// Send error to Sentry immediately
if (error) Sentry.captureException(error)
return [data, error] as const
}
يصبح الاستخدام صريحاً:
const [result, err] = await mutate(
supabase
.from('contacts')
.update({ statut: 'ancien', archived_at: now })
.eq('id', id)
)
if (err) {
// Handle or rethrow
return { ok: false, message: err.message }
}
return { ok: true, data: result }
إذا نسي المطور التقاط err ، سيصدر TypeScript خطأ: "Tuple type [T, any] of length 2 has no element at index 1." لن يتم تجميع الكود حتى يتم معالجة الخطأ. كما تقوم أداة المساعدة بحقن أدوات مراقبة Sentry، مما يضمن تسجيل كل رفض لقاعدة البيانات حتى لو قام المستدعي بابتلاعه لاحقاً.
ما هو على المحك
- سلامة البيانات – تسمح الإخفاقات الصامتة بتسلل حالات غير صالحة إلى بيئة الإنتاج. جهة اتصال مؤرشفة لم تغادر القائمة النشطة أبداً يمكن أن تؤدي إلى تقارير خاطئة لاحقاً.
- ثقة المستخدم – واجهة المستخدم التي تدعي النجاح بينما لم يتغير شيء تقوض الثقة. يرى العملاء كلمة "مؤرشف" ولكنهم لا يزالون يجدون جهة الاتصال في عمليات البحث.
- وقت المطور – مطاردة الأخطاء الوهمية تستهلك ساعات. معالجة الأخطاء الصريحة تظهر المشكلة عند نقطة الفشل، مما يقصر دورة تصحيح الأخطاء (debugging loop).
- التكلفة التشغيلية – إضافة بضعة أسطر إضافية من الكود وغلاف صغير أمر لا يُذكر مقارنة بتكلفة حادث فقدان بيانات صامت.
المقايضة
تضيف أداة المساعدة نوعاً من الإطالة (verbosity): فكل عملية mutation تعيد الآن زوجاً (tuple)، ويجب على المستدعيين كتابة كتلة if (err). قد ترى بعض الفرق هذا كضجيج برمجي (boilerplate noise)، خاصة في عمليات CRUD البسيطة حيث يتوقعون النجاح. الحجة المضادة هي أن الكود الإضافي هو وسيلة حماية، وليس ميزة اختيارية. في البيئات التي تكون فيها صحة البيانات ذات أهمية قصوى — مثل أنظمة CRM، والتمويل، والصحة — فإن فرض التحقق يؤتي ثماره.
ما التالي
يعد المؤلف بجزء ثالث يفحص سياسات RLS التي تعيد صفراً من الصفوف دون تفسير. هذا النمط، مثل حالة الخطأ الصامت، يخفي الإخفاقات وراء استعلام يبدو ناجحاً. تهدف السلسلة معاً إلى كشف الجانب "الإشاعي" لواجهة برمجة تطبيقات Supabase ومنح المطورين أدوات ملموسة للمطالبة بالحقائق.
الخلاصة: يسمح تصميم Supabase-JS لعملية كتابة فاشلة بأن تبدو كأنها نجاح ما لم تتذكر قراءة حقل error. من خلال تغليف الاستدعاءات في أداة مساعدة يفرضها TypeScript تجعل error إلزامياً وتسجله في Sentry، فإنك تحول الإخفاقات الصامتة إلى أحداث مرئية وقابلة للتنفيذ. الزيادة المتواضعة في حجم الكود تمنحك موثوقية البيانات وثقة المستخدم — وهما أمران لا يمكن لأي نجاح صامت أن يحققهما أبداً.
