# Báo lỗi từ client { data, error }

> captureResult() báo lỗi mà Supabase, PostgREST và các client khác trả về thay vì ném ra: một event có kiểu theo mã Postgres, gom theo kiểu và thao tác, không gửi SQL.

Supabase, PostgREST và nhiều client cơ sở dữ liệu khác không ném lỗi: chúng trả về `{ data, error }`. Không có gì tới Belvador nếu code của bạn không báo `error` đó — nên một lỗi thiếu quyền hay một truy vấn bị huỷ sẽ hỏng âm thầm với mọi người dùng. `captureResult()` báo nó trong một dòng và trả lại kết quả y nguyên. Có từ SDK 0.6.0 trong mọi gói `@belvador/*`.

## Cách dùng

```ts
import { captureResult } from "@belvador/browser"; // every @belvador/* package exports it

const { data, error } = captureResult(
  await supabase.from("invoices").select("id, total").eq("customer_id", customerId),
  { operation: "select invoices" },
);
if (error) return showRetry(); // the result is unchanged; the error is already reported
```

`captureResult(result, { operation })` không bao giờ ném lỗi và không đổi kết quả: đoạn `if (error)` sẵn có của bạn vẫn chạy như cũ. Khi lệnh gọi thành công thì nó không làm gì.

`operation` đặt tên cho lệnh gọi — `select invoices`, `rpc create_invoice` — không bao giờ là câu SQL. Tên được gộp thành một dòng và cắt còn 100 ký tự.

## Những gì được gửi

Mỗi lệnh gọi hỏng là một event lỗi:

| Trường | Giá trị |
|---|---|
| Kiểu lỗi và tiêu đề | kiểu ở bảng dưới, ví dụ `db.permission_denied`, kèm `operation: message` |
| Message | thông báo của cơ sở dữ liệu, được che như mọi văn bản khác và cắt còn 300 ký tự |
| Tag | `error.type`, `db.system.name` (`postgresql`), `db.operation.name`, `db.response.status_code` — tên theo quy ước cơ sở dữ liệu của OpenTelemetry |
| Gom nhóm | theo kiểu và thao tác: mọi lỗi thiếu quyền của `select invoices` là một issue, dù message nói gì |

Không bao giờ gửi: câu SQL, `details` và `hint` của lỗi (có thể chứa giá trị của dòng dữ liệu), và `data` của bạn.

## Các kiểu lỗi

| `error.type` | Mã | Mặc định |
|---|---|---|
| `db.permission_denied` | `42501` | một event |
| `db.unique_violation` | `23505` | breadcrumb, không event |
| `db.foreign_key_violation` | `23503` | một event |
| `db.query_canceled` | `57014` | một event |
| `db.serialization_failure` | `40001`, `40P01` | một event |
| `db.no_rows` | `PGRST116` | breadcrumb, không event |
| `db.jwt_expired` | `PGRST301` | một event |
| `db.request_invalid` | `PGRST2xx` | một event |
| `db.error` | mọi mã khác | một event |

`db.unique_violation` và `db.no_rows` thường là luồng bình thường — đăng ký trùng, tra cứu không thấy gì — nên mặc định chúng chỉ để lại một breadcrumb đi kèm lỗi thật kế tiếp thay vì tạo event. Muốn báo mọi kiểu cho một lệnh gọi, truyền danh sách của riêng bạn:

```ts
// Here a duplicate email is a bug, not normal flow: report every error type.
captureResult(await supabase.from("subscribers").insert({ email }), {
  operation: "insert subscribers",
  expected: [],
});
```

## Trên một SDK Sentry chính thức

Máy chủ dùng `@sentry/node`, `@sentry/bun` hoặc `@sentry/deno` (cả Supabase Edge Functions) gắn cùng `captureResult` đó vào SDK của chính mình qua `@belvador/core/sentry` (từ SDK 0.6.1):

```js title="db.mjs"
// captureResult on the app's own Sentry SDK (@sentry/node, @sentry/bun or @sentry/deno).
import * as Sentry from "@sentry/node";
import { captureResultWith } from "@belvador/core/sentry";

export const captureResult = captureResultWith(Sentry);
```

Nó gọi các hàm của chính SDK Sentry của bạn, nên chạy được với bất kỳ phiên bản `@sentry/*` nào bạn đang dùng; Belvador kiểm nó với 11.9.0. Mọi điều trên trang này giữ nguyên. Với Supabase Edge Functions, tệp dùng chung đã export sẵn — xem [Supabase Edge Functions](https://belvador.dev/vi/docs/sdk/supabase-edge).

## Nên biết

- `captureResult` cần SDK đã khởi động (`init`, `withBelvador`, hoặc `Sentry.init` trên SDK chính thức). Nếu chưa, nó trả lại kết quả và báo `BLV_NOT_INITIALIZED` một lần — xem [Kiểm tra kết nối](https://belvador.dev/vi/docs/sdk/verify).
- Exception bị ném ra (lỗi mạng bên trong client, bug trong code) không phải kết quả: nó được báo theo cách thường lệ, bởi bộ bắt lỗi của SDK hoặc `captureException`.
- Lỗi bạn đã tự báo bằng `captureException` sẽ bị báo hai lần nếu bạn bọc thêm kết quả — chọn một trong hai.
