Dua puluh kenalan sepatutnya diarkibkan dalam CRM, namun UI memaparkan lencana kejayaan hijau dan operasi tersebut tidak melakukan apa-apa secara senyap. Pembantu (helper) TypeScript yang menjadikan medan error wajib dalam mutasi Supabase-JS kini memaksa pembangun untuk berhadapan dengan kegagalan tersebut dan bukannya menyembunyikannya.

Mengapa bentuk pulangan Supabase-JS adalah satu perangkap

Klien JavaScript Supabase (@supabase/supabase-js) tidak melontarkan pengecualian (exceptions) apabila penulisan pangkalan data ditolak. Sebaliknya, ia menyelesaikan janji (promise) dengan objek { data, error }. Jika polisi keselamatan peringkat baris (RLS), pelanggaran kunci unik, atau sebarang kekangan lain menyekat pertanyaan (query), data akan dikembalikan sebagai null dan error mengandungi mesej pangkalan data. Klien mengandaikan pemanggil akan memeriksa error; ia tidak pernah membatalkan panggilan tersebut.

Dalam praktiknya, banyak kod asas (codebases) menganggap panggilan tersebut sebagai operasi "fire-and-forget":

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

return { ok: true }

Apabila update disekat oleh peraturan RLS, janji tersebut tetap diselesaikan. Oleh kerana pembangun tidak pernah melakukan destrukturisasi terhadap { error }, kegagalan tersebut tidak kelihatan. Fungsi tersebut mengembalikan { ok: true }, UI menunjukkan kejayaan, dan data kekal tidak berubah. Tiada log muncul, tiada amaran Sentry dicetuskan, dan pepijat (bug) tersebut boleh dibiarkan selama berhari-hari.

Linter sahaja tidak mencukupi

Alat analisis statik boleh memberi amaran apabila harta (property) error diabaikan, tetapi ia tidak dapat menguatkuasakan kontrak semasa masa larian (runtime). Seorang pembangun masih boleh menulis const _ = await … dan mendiamkan amaran tersebut, atau mereka boleh menambah komen untuk menyekat peraturan itu. Masalah utamanya ialah sistem taip membenarkan panggilan berjaya tanpa pernah menyebut error.

Pembantu mutate(): menjadikan pengendalian ralat adalah wajib

Penulis membina satu pembungkus (wrapper) kecil yang dipanggil mutate() yang mengubah bentuk jenis pulangan. Berbanding { data, error }, pembantu tersebut mengembalikan tuple [data, error] di mana error adalah medan yang wajib. TypeScript kemudian enggan menyusun (compile) mana-mana panggilan yang membuang elemen kedua.

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
}

Penggunaan menjadi eksplisit:

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 }

Jika pembangun terlupa untuk menangkap err, TypeScript akan mengeluarkan ralat: “Tuple type [T, any] of length 2 has no element at index 1.” Kod tersebut tidak akan disusun sehingga ralat tersebut ditangani. Pembantu ini juga menyuntik instrumentasi Sentry, menjamin bahawa setiap penolakan pangkalan data akan direkodkan walaupun pemanggil kemudiannya mengabaikannya.

Apa yang dipertaruhkan

  • Integriti data – Kegagalan senyap membiarkan keadaan tidak sah menyusup ke dalam pengeluaran (production). Kenalan yang diarkibkan yang tidak pernah keluar dari senarai aktif boleh menyebabkan laporan hiliran menjadi salah.
  • Kepercayaan pengguna – UI yang mendakwa kejayaan sedangkan tiada apa yang berubah akan menghakis keyakinan. Pelanggan melihat status "diarkibkan" tetapi masih menemui kenalan tersebut dalam carian.
  • Masa pembangun – Mengejar pepijat halimunan memakan masa berjam-jam. Pengendalian ralat yang eksplisit mendedahkan masalah pada titik kegagalan, memendekkan kitaran penyahpepijatan (debugging).
  • Kos operasi – Menambah beberapa baris kod tambahan dan pembungkus kecil adalah remeh berbanding kos insiden kehilangan data secara senyap.

Imbangan (The trade-off)

Pembantu ini menambah keverbositi (verbosity): setiap panggilan mutasi kini mengembalikan tuple, dan pemanggil mesti menulis blok if (err). Sesetengah pasukan mungkin menganggap ini sebagai gangguan boilerplate, terutamanya untuk tindakan CRUD ringkas di mana mereka menjangkakan kejayaan. Hujah balasnya ialah kod tambahan tersebut adalah pelindung, bukannya ciri pilihan. Dalam persekitaran di mana ketepatan data adalah sangat penting—sistem CRM, kewangan, kesihatan—memaksa semakan tersebut akan memberikan nilai yang setimpal.

Apa yang seterusnya

Penulis menjanjikan bahagian ketiga yang akan meneliti polisi RLS yang mengembalikan sifar baris tanpa penjelasan. Corak tersebut, seperti kes ralat senyap, menyembunyikan kegagalan di sebalik pertanyaan yang kelihatan berjaya. Bersama-sama, siri ini bertujuan untuk mendedahkan sisi "khabar angin" API Supabase dan memberikan alat konkrit kepada pembangun untuk menuntut fakta.

Rumusan: Reka bentuk Supabase-JS membolehkan penulisan yang gagal kelihatan seperti kejayaan melainkan anda ingat untuk membaca medan error. Dengan membungkus panggilan dalam pembantu yang dikuatkuasakan oleh TypeScript yang menjadikan error wajib dan merekodkannya ke Sentry, anda mengubah kegagalan senyap kepada peristiwa yang nyata dan boleh diambil tindakan. Peningkatan kecil dalam saiz kod memberikan anda kebolehpercayaan data dan kepercayaan pengguna—dua perkara yang tidak dapat diberikan oleh sebarang kejayaan senyap.