ایک CRM میں بیس رابطوں (contacts) کو آرکائیو کیا جانا تھا، لیکن UI نے کامیابی کا سبز بیج (green success badge) دکھایا اور آپریشن خاموشی سے کچھ کیے بغیر گزر گیا۔ ایک TypeScript ہیلپر جو Supabase-JS میوٹیشنز (mutations) میں error فیلڈ کو لازمی بناتا ہے، اب ڈویلپرز کو اس ناکامی کا سامنا کرنے پر مجبور کرتا ہے بجائے اس کے کہ وہ اسے نظر انداز کر دیں۔

کیوں Supabase-JS کا return shape ایک جال ہے

Supabase کا JavaScript کلائنٹ (@supabase/supabase-js) ڈیٹا بیس رائٹ (write) مسترد ہونے پر کوئی استثنا (exception) نہیں پھینکتا۔ اس کے بجائے، یہ ایک آبجیکٹ { data, error } کے ساتھ پرومیس (promise) کو ریزولیو (resolve) کرتا ہے۔ اگر کوئی row-level security (RLS) پالیسی، unique-key کی خلاف ورزی، یا کوئی دوسری پابندی کوئری کو روکتی ہے، تو data بطور null واپس آتا ہے اور error میں ڈیٹا بیس کا پیغام ہوتا ہے۔ کلائنٹ یہ فرض کر لیتا ہے کہ کال کرنے والا error کا معائنہ کرے گا؛ یہ کال کو کبھی منسوخ (abort) نہیں کرتا۔

عملی طور پر بہت سے کوڈ بیسز اس کال کو "fire-and-forget" آپریشن کے طور پر لیتے ہیں:

await supabase
  .from('contacts')
  .update({ statut: 'ancien', archived_at: now })
  .eq('id', id)

return { ok: true }

جب update کو RLS رول کے ذریعے روکا جاتا ہے، تب بھی پرومیس ریزولیو ہو جاتا ہے۔ چونکہ ڈویلپر کبھی بھی { error } کو ڈیسٹرکچر (destructure) نہیں کرتا، اس لیے ناکامی نظر نہیں آتی۔ فنکشن { ok: true } واپس کرتا ہے، UI کامیابی دکھاتا ہے، اور ڈیٹا تبدیل نہیں ہوتا۔ کوئی لاگز (logs) ظاہر نہیں ہوتے، کوئی Sentry الرٹ نہیں بجتا، اور یہ بگ دنوں تک موجود رہ سکتا ہے۔

صرف ایک linter کافی نہیں ہے

اسٹیٹک اینالیسس ٹولز (Static analysis tools) اس وقت خبردار کر سکتے ہیں جب error پراپرٹی کو نظر انداز کیا گیا ہو، لیکن وہ رن ٹائم کنٹریکٹ (runtime contract) کو نافذ نہیں کر سکتے۔ ایک ڈویلپر اب بھی const _ = await … لکھ کر وارننگ کو خاموش کر سکتا ہے، یا وہ رول کو دبانے کے لیے کمنٹ شامل کر سکتا ہے۔ بنیادی مسئلہ یہ ہے کہ ٹائپ سسٹم (type system) error کا ذکر کیے بغیر کال کو کامیاب ہونے کی اجازت دیتا ہے۔

mutate() ہیلپر: error handling کو لازمی بنانا

مصنف نے mutate() نامی ایک چھوٹا ریپر (wrapper) بنایا ہے جو ریٹرن ٹائپ کی شکل کو تبدیل کر دیتا ہے۔ { data, error } کے بجائے، ہیلپر ایک ٹیپل (tuple) [data, error] واپس کرتا ہے جہاں error ایک لازمی فیلڈ ہے۔ TypeScript پھر ایسی کسی بھی کال کو کمپائل کرنے سے انکار کر دیتا ہے جو دوسرے عنصر کو نظر انداز کرتی ہے۔

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 instrumentation بھی شامل کرتا ہے، جس سے یہ یقینی بنایا جاتا ہے کہ ڈیٹا بیس کی ہر مسترد شدہ درخواست لاگ (log) ہو، چاہے کال کرنے والا بعد میں اسے نظر انداز ہی کیوں نہ کر دے۔

کیا خطرے میں ہے

  • Data integrity – خاموش ناکامیاں غلط اسٹیٹ کو پروڈکشن میں داخل ہونے دیتی ہیں۔ ایک آرکائیو شدہ رابطہ جو کبھی ایکٹو لسٹ سے نہیں نکلا، بعد میں رپورٹس کو غلط کر سکتا ہے۔
  • User trust – ایک ایسا UI جو کامیابی کا دعویٰ کرے جبکہ کچھ بھی تبدیل نہ ہوا ہو، اعتماد کو کمزور کرتا ہے۔ صارفین "archived" دیکھتے ہیں لیکن پھر بھی سرچ میں رابطہ پاتے ہیں۔
  • Developer time – فرضی بگ (phantom bugs) کا پیچھا کرنے میں گھنٹوں ضائع ہوتے ہیں۔ واضح ایرر ہینڈلنگ ناکامی کے مقام پر ہی مسئلے کو سامنے لاتی ہے، جس سے ڈی بگنگ کا دورانیہ کم ہو جاتا ہے۔
  • Operational cost – خاموش ڈیٹا کے نقصان کے واقعے کی لاگت کے مقابلے میں کوڈ کی چند اضافی لائنیں اور ایک چھوٹا ریپر لگانا نہ ہونے کے برابر ہے۔

توازن (The trade-off)

ہیلپر سے کوڈ میں طوالت (verbosity) بڑھ جاتی ہے: اب ہر میوٹیشن کال ایک ٹیپل واپس کرتی ہے، اور کال کرنے والوں کو if (err) بلاک لکھنا پڑتا ہے۔ کچھ ٹیمیں اسے اضافی بوجھ (boilerplate noise) سمجھ سکتی ہیں، خاص طور پر سادہ CRUD ایکشنز کے لیے جہاں وہ کامیابی کی توقع رکھتے ہیں۔ اس کا جواب یہ ہے کہ اضافی کوڈ ایک حفاظتی تدبیر ہے، کوئی اختیاری فیچر نہیں۔ ایسے ماحول میں جہاں ڈیٹا کی درستگی انتہائی اہم ہے—جیسے CRM سسٹمز، فنانس، ہیلتھ—وہاں چیک کرنے کو لازمی بنانا خود بخود فائدہ مند ثابت ہوتا ہے۔

آگے کیا ہے

مصنف نے تیسرے حصے کا وعدہ کیا ہے جس میں ان RLS پالیسیوں کا جائزہ لیا جائے گا جو بغیر کسی وضاحت کے صفر روز (zero rows) واپس کرتی ہیں۔ وہ پیٹرن، خاموش ایرر کے کیس کی طرح، بظاہر کامیاب کوئری کے پیچھے ناکامیوں کو چھپاتا ہے۔ یہ سیریز مل کر Supabase کے API کے "افواہی" (rumor) پہلو کو بے نقاب کرنے اور ڈویلپرز کو حقائق کے لیے مطالبہ کرنے کے ٹھوس ٹولز فراہم کرنے کا ہدف رکھتی ہے۔

نتیجہ (Takeaway): Supabase-JS کا ڈیزائن ایک ناکام رائٹ کو کامیابی کے طور پر دکھا سکتا ہے جب تک کہ آپ error فیلڈ کو پڑھنا نہ یاد رکھیں۔ TypeScript کے ذریعے نافذ کردہ ہیلپر میں کالز کو لپیٹ کر، جو error کو لازمی بناتا ہے اور اسے Sentry میں لاگ کرتا ہے، آپ خاموش ناکامیوں کو نظر آنے والے اور قابلِ عمل واقعات میں بدل دیتے ہیں۔ کوڈ کے سائز میں معمولی اضافہ آپ کو ڈیٹا کی بھروسہ مندی اور صارف کا اعتماد فراہم کرتا ہے—دو ایسی چیزیں جو کوئی بھی خاموش کامیابی کبھی فراہم نہیں کر سکتی۔