एका CRM मध्ये वीस कॉन्टॅक्ट्स (contacts) आर्काइव्ह (archive) करायचे होते, तरीही UI वर हिरवा सक्सेस बॅज दिसला आणि ही प्रक्रिया शांतपणे काहीही न करता संपली. Supabase-JS मधील mutations मध्ये error फील्ड अनिवार्य (mandatory) करणारा एक TypeScript helper आता डेव्हलपर्सना ही चूक लपवून ठेवण्याऐवजी तिचा सामना करण्यास भाग पाडतो.
Supabase-JS चे return shape एक सापळा का आहे
जेव्हा डेटाबेसमध्ये काही लिहिण्यास (write) नकार दिला जातो, तेव्हा Supabase चा JavaScript client (@supabase/supabase-js) exceptions थ्रो (throw) करत नाही. त्याऐवजी, तो { data, error } या ऑब्जेक्टसह promise resolve करतो. जर row-level security (RLS) पॉलिसी, unique-key violation किंवा इतर कोणत्याही constraint मुळे क्वेरी ब्लॉक झाली, तर data हा null म्हणून येतो आणि error मध्ये डेटाबेसचा मेसेज असतो. क्लायंट असे गृहीत धरतो की कॉलर (caller) error तपासतील; तो कॉल कधीही रद्द (abort) करत नाही.
प्रत्यक्षात, अनेक कोडबेसेसमध्ये या कॉलला 'fire-and-forget' ऑपरेशन मानले जाते:
await supabase
.from('contacts')
.update({ statut: 'ancien', archived_at: now })
.eq('id', id)
return { ok: true }
जेव्हा update RLS नियमामुळे ब्लॉक होतो, तेव्हाही promise resolve होतो. डेव्हलपरने { error } destructure केले नसल्यामुळे, ही त्रुटी (failure) अदृश्य राहते. फंक्शन { ok: true } रिटर्न करते, UI मध्ये यश (success) दिसते आणि डेटा तसाच राहतो. कोणतेही logs दिसत नाहीत, Sentry अलर्ट येत नाही आणि हा बग कित्येक दिवस तसेच राहू शकतो.
फक्त linter पुरेसा नाही
जेव्हा error प्रॉपर्टीकडे दुर्लक्ष केले जाते, तेव्हा static analysis टूल्स चेतावणी (warn) देऊ शकतात, परंतु ते runtime contract लागू करू शकत नाहीत. डेव्हलपर अजूनही const _ = await … लिहून चेतावणी शांत करू शकतो किंवा नियम दाबण्यासाठी (suppress) कमेंट जोडू शकतो. मूळ समस्या ही आहे की, type system अशा प्रकारे कॉल यशस्वी होऊ देते की ज्यामध्ये error चा उल्लेखही नसतो.
mutate() helper: error handling अनिवार्य करणे
लेखकाने mutate() नावाचा एक छोटा wrapper तयार केला आहे जो return type चा आकार बदलतो. { data, error } ऐवजी, हा helper एक tuple [data, error] रिटर्न करतो, जिथे error हे एक आवश्यक (required) फील्ड आहे. त्यानंतर TypeScript असा कोणताही कॉल कंपाईल करण्यास नकार देते जो दुसरा घटक (element) सोडून देतो.
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
}
वापर आता स्पष्ट (explicit) होतो:
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.” जोपर्यंत ही त्रुटी दूर केली जात नाही, तोपर्यंत कोड कंपाईल होणार नाही. हा helper Sentry instrumentation देखील समाविष्ट करतो, ज्यामुळे खात्री मिळते की प्रत्येक डेटाबेस रिजेक्शन लॉग केले जाते, जरी कॉलरने नंतर ते दुर्लक्षित केले तरीही.
काय धोक्यात आहे
- Data integrity – शांत अपयश (Silent failures) चुकीची स्थिती (invalid state) प्रोडक्शनमध्ये येऊ देते. एखादा कॉन्टॅक्ट जो आर्काइव्ह झाला नाही पण ॲक्टिव्ह लिस्टमध्येच राहिला, त्यामुळे पुढील रिपोर्ट्समध्ये चुका होऊ शकतात.
- User trust – काहीही बदल न होता यश दाखवणारे UI ग्राहकांचा विश्वास कमी करते. ग्राहकांना “archived” दिसते, पण शोधताना तो कॉन्टॅक्ट अजूनही सापडतो.
- Developer time – काल्पनिक (phantom) बग्स शोधण्यात तासनतास वाया जातात. स्पष्ट error handling मुळे त्रुटी जिथे आहे तिथेच समोर येते, ज्यामुळे debugging चा वेळ वाचतो.
- Operational cost – डेटा गमावण्याच्या (data loss) शांत घटनेच्या खर्चाच्या तुलनेत कोडच्या काही जास्तीच्या ओळी आणि एक छोटा wrapper वापरणे नगण्य आहे.
तडजोड (The trade-off)
या helper मुळे कोडमध्ये थोडी वाढ (verbosity) होते: आता प्रत्येक mutation कॉल एक tuple रिटर्न करतो आणि कॉलरला if (err) ब्लॉक लिहावा लागतो. काही टीम्स याला 'boilerplate noise' मानू शकतात, विशेषतः साध्या CRUD ॲक्शन्ससाठी जिथे त्यांना यशाची अपेक्षा असते. यावर असा प्रतिवाद आहे की, हा अतिरिक्त कोड एक सुरक्षा कवच (safeguard) आहे, कोणताही ऐच्छिक (optional) फीचर नाही. ज्या वातावरणात डेटाची अचूकता अत्यंत महत्त्वाची असते—जसे की CRM सिस्टम्स, फायनान्स, हेल्थ—तिथे ही तपासणी अनिवार्य करणे फायदेशीर ठरते.
पुढे काय
लेखक तिसऱ्या भागात RLS पॉलिसीजचा अभ्यास करण्याचे आश्वासन देत आहेत, ज्या कोणत्याही स्पष्टीकरणाशिवाय शून्य रोज (zero rows) रिटर्न करतात. ही पद्धत, 'silent-error' केसप्रमाणेच, यशस्वी क्वेरीच्या मागे त्रुटी लपवते. या मालिकेचे उद्दिष्ट Supabase च्या API मधील "अफवा" (rumor) बाजू उघड करणे आणि डेव्हलपर्सना वस्तुस्थिती जाणून घेण्यासाठी ठोस साधने देणे हे आहे.
Takeaway: Supabase-JS ची रचना अशा प्रकारे आहे की जोपर्यंत तुम्ही error फील्ड वाचायला विसरत नाही, तोपर्यंत अयशस्वी 'write' सुद्धा यशस्वी वाटू शकते. TypeScript-enforced helper वापरून आणि error अनिवार्य करून व तो Sentry मध्ये लॉग करून, तुम्ही शांत अपयशाचे रूपांतर दृश्य आणि कृती करण्यायोग्य (actionable) घटनांमध्ये करू शकता. कोडच्या आकारात होणारी ही थोडीशी वाढ तुम्हाला डेटाची विश्वासार्हता आणि युजरचा विश्वास मिळवून देते—या दोन गोष्टी कोणतेही 'silent success' कधीही देऊ शकत नाही.
