עשרים אנשי קשר היו אמורים להיות מארכיבים ב-CRM, אך ממשק המשתמש הציג תגית הצלחה ירוקה והפעולה לא עשתה דבר בשקט. פונקציית עזר ב-TypeScript שהופכת את שדה ה-error לחובה במוטציות של Supabase-JS מאלצת כעת מפתחים להתמודד עם הכישלון הזה במקום לטאטא אותו מתחת לשטיח.

למה מבנה ההחזרה של Supabase-JS הוא מלכודת

הלקוח של Supabase ב-JavaScript (@supabase/supabase-js) אינו זורק חריגות (exceptions) כאשר כתיבה למסד הנתונים נדחית. במקום זאת, הוא מבצע resolve ל-promise עם אובייקט { data, error }. אם מדיניות אבטחת רמת שורה (RLS), הפרת מפתח ייחודי (unique-key violation), או כל אילוץ אחר חוסמים את השאילתה, ה-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, ה-promise עדיין מבצע resolve. מכיוון שהמפתח לעולם אינו מבצע destructuring ל-{ error }, הכישלון נשאר בלתי נראה. הפונקציה מחזירה { ok: true }, ממשק המשתמש מציג הצלחה, והנתונים נותרים ללא שינוי. לא מופיעים לוגים, לא נשלחת התרעה ב-Sentry, והבאג יכול לשבת שם ימים.

Linter אינו מספיק

כלי ניתוח סטטי יכולים להזהיר כאשר מאפיין error זוכה להתעלמות, אך הם אינם יכולים לאכוף חוזה בזמן ריצה (runtime contract). מפתח עדיין יכול לכתוב const _ = await … ולהשתיק את האזהרה, או להוסיף הערה כדי לדכא את הכלל. הבעיה הבסיסית היא שמערכת הטיפוסים מאפשרת לקריאה להצליח מבלי להזכיר את ה-error.

פונקציית העזר mutate(): הפיכת טיפול בשגיאות לחובה

המחבר בנה עטיפה (wrapper) קטנה בשם mutate() שמשנה את מבנה הטיפוס המוחזר. במקום { 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, מה שמבטיח שכל דחייה של מסד הנתונים תתועד בלוגים, גם אם הקורא יבלע אותה מאוחר יותר.

מה עומד על הפרק

  • שלמות הנתונים (Data integrity) – כישלונות שקטים מאפשרים למצבים לא תקינים להשתחל לסביבת הייצור. איש קשר שעבר ארכיון אך מעולם לא עזב את הרשימה הפעילה עלול לגרום לדוחות המשכיים להיות שגויים.
  • אמון משתמשים – ממשק משתמש שטוען להצלחה בזמן ששום דבר לא השתנה שוחק את הביטחון. לקוחות רואים "אורכב" אך עדיין מוצאים את איש הקשר בחיפושים.
  • זמן מפתחים – מרדף אחרי באגים רפאים צורך שעות. טיפול מפורש בשגיאות חושף את הבעיה בנקודת הכישלון, ומקצר את לולאת הדיבאגינג.
  • עלות תפעולית – הוספת כמה שורות קוד ועטיפה קטנה היא זניחה לעומת העלות של תקרית אובדן נתונים שקטה.

הפשרה (The trade-off)

פונקציית העזר מוסיפה פירוט (verbosity): כל קריאת מוטציה מחזירה כעת tuple, והקוראים חייבים לכתוב בלוק if (err). צוותים מסוימים עשויים לראות בכך רעש של boilerplate, במיוחד עבור פעולות CRUD פשוטות שבהן הם מצפים להצלחה. הטיעון הנגדי הוא שהקוד הנוסף הוא מנגנון הגנה, לא פיצ'ר אופציונלי. בסביבות שבהן נכונות הנתונים היא בעלת חשיבות עליונה — מערכות CRM, פיננסים, בריאות — אכיפת הבדיקה משתלמת.

מה הלאה

המחבר מבטיח חלק שלישי שיבחן מדיניות RLS שמחזירה אפס שורות ללא הסבר. דפוס זה, בדומה למקרה של שגיאה שקטה, מסתיר כישלונות מאחורי שאילתה שנראית מוצלחת. יחד, הסדרה שואפת לחשוף את צד ה"שמועות" של ה-API של Supabase ולתת למפתחים כלים קונקרטיים לדרוש עובדות.

שורה תחתונה: העיצוב של Supabase-JS מאפשר לכתיבה שנכשלה להיראות כמו הצלחה, אלא אם כן אתם זוכרים לקרוא את שדה ה-error. על ידי עטיפת קריאות בפונקציית עזר המאולצת על ידי TypeScript, שהופכת את ה-error לחובה ומתעדת אותו ב-Sentry, אתם הופכים כישלונות שקטים לאירועים גלויים וניתנים לטיפול. העלייה המתונה בגודל הקוד קונה לכם אמינות נתונים ואמון משתמשים — שני דברים ששום הצלחה שקטה לעולם לא תוכל לספק.