# CloudEvents

> Mutations as CloudEvents 1.0, for queues, buses and webhooks.

Source: https://bettersupabase.com/docs/standards/events

`better-supabase/events` turns repository mutations into
[CloudEvents](https://cloudevents.io) and sends them to an `EventSink`.

```ts
import { forwardMutations, httpSink } from "better-supabase/events";

const stop = forwardMutations(
  betterSupabase,
  httpSink("https://events.example.com/ingest"),
  {
    source: "https://crm.example.com",
    filter: (notice) => notice.table !== "auditLog",
  },
);
```

Each mutated row becomes one event:

```json
{
  "specversion": "1.0",
  "id": "5f0c…",
  "source": "https://crm.example.com",
  "type": "dev.better-supabase.row.created",
  "subject": "customers/5f0c…",
  "time": "2026-01-01T00:00:00.000Z",
  "datacontenttype": "application/json",
  "data": {
    "table": "customers",
    "row": { "id": "5f0c…", "name": "Acme" },
    "actorId": "user-1"
  },
  "partitionkey": "org-1"
}
```

The types are `row.created`, `row.updated`, `row.upserted`, `row.deleted` and
`row.softdeleted`. Use `typePrefix: 'com.acme.crm'` to use your own namespace.
`subject` is the primary key, `partitionkey` the tenant (also the one
`tenant()` resolved from the claims) and `data.actorId` the acting user. The
actor is in `data`, not in a context attribute, because intermediaries read
context attributes and they must not carry personal data. Writes
that return no rows send one event per known primary key, with the key as
`data.row`.

## Sinks [#sinks]

`EventSink` has one method, `send(events)`. Implement it for SQS, Pub/Sub,
Inngest or an outbox table. Sink failures go to `onError` and never fail
the mutation. Events are forwarded after the write, so use the SQL modules'
outbox when every event must arrive.

`httpSink(url, { mode })` POSTs in the HTTP binding's `batch` (default),
`structured` or `binary` mode. On the receiving side, `fromHttp(request)`
reads all three modes, and `isCloudEvent` validates the required attributes.
`toCloudEvents(notice, options)` builds events without a sink.

### Sends after the response [#sends-after-the-response]

A sink's `send` runs after the write, so a serverless function can stop
before it finishes. `forwardMutations` tracks each send on
`betterSupabase.events`: `events.pending` says whether one is running and
`events.settled()` resolves when they all have, failed ones included. The
adapters wait for them for you. `bs.route` and `bs.action` in Next.js pass
them to `after()`. The edge handlers, the Hono middleware, oRPC's
`fetchHandler` and both MCP adapters pass them to the `waitUntil` option (see
[Edge Functions](/docs/frameworks/edge#background-work)); Hono also uses
`c.executionCtx` on Workers. Elsewhere, await
`betterSupabase.events.settled()` before the process exits.