> For the complete documentation index, see [llms.txt](https://bucketdb.sullux.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bucketdb.sullux.com/usage/triggers.md).

# Triggers

BucketDB supports registering runtime triggers that fire synchronously during batch operations (`insert`, `update`, `delete`). Triggers allow you to enforce business logic, maintain materialized views, or cascade changes automatically within the same atomic transaction.

## Registering Triggers

Triggers are registered directly on the `db` instance and are evaluated during the operation (e.g. `batch.insert()`), *before* the row is encoded and flushed.

```javascript
db.registerTrigger('orders', 'insert', async (row, batch, dbApi, operation) => {
  // Validate data
  if (row.amount < 0) throw new Error('Invalid order amount');

  // Maintain a materialized view synchronously
  const results = await batch.query('daily_sales').where('date', '=', row.date).execute();
  if (results[0]) {
    await batch.update('daily_sales', { date: row.date, total: results[0].total + row.amount });
  } else {
    await batch.insert('daily_sales', { date: row.date, total: row.amount });
  }
});
```

### Trigger Handler Signature

`async (row, batch, dbApi, operation) => void`

* **`row`** (Object): The data object being mutated. For `insert` and `update`, this contains the fields passed to the method. For `delete`, it contains the primary key (e.g., `{ pk: '123' }`).
* **`batch`** (Batch): The current transaction context. Queries and mutations made on this batch will see the unflushed state of the transaction.
* **`dbApi`** (Database): The main database instance API, used to access utility functions like `getBlob`.
* **`operation`** (String): The operation type (`'insert'`, `'update'`, `'delete'`).

## Working with Managed Blobs (typeId: 11)

When your schema includes a Managed Blob field (`typeId: 11`), handling it inside a trigger requires understanding how BucketDB processes blobs.

Blobs are stored externally in S3, while the database row simply stores a UUID reference to the blob. Because triggers fire *before* encoding, the shape of the blob data in `row` depends on the operation and how the user passed it.

### Scenario A: Reading a Newly Uploaded Blob (Insert / Update)

If the user is inserting or updating the blob field, they pass a raw `Buffer` or a `db.Blob(buffer)` wrapper to the batch operation. Because the trigger fires before the encoder replaces the buffer with a UUID, **the blob is available directly in memory on the `row` object**.

```javascript
db.registerTrigger('users', 'insert', async (row, batch, dbApi) => {
  const avatarBlob = row.avatar; // The raw Buffer or db.Blob() object

  if (avatarBlob) {
    // Extract the raw buffer safely
    const buffer = Buffer.isBuffer(avatarBlob) ? avatarBlob : avatarBlob.value;
    
    if (buffer.length > 5 * 1024 * 1024) {
      throw new Error('Avatar cannot exceed 5MB');
    }
  }
});
```

*Note: Do NOT attempt to use `dbApi.getBlob()` on a newly inserted blob, as the UUID has not yet been generated and the blob has not been uploaded to S3.*

### Scenario B: Reading an Existing Blob from Storage (Update / Delete)

If a trigger fires on an `update` or `delete` operation, the user's `row` object might only contain the fields they are changing. If you need to inspect an *existing* blob that was saved previously, you must query the database to get its UUID reference, then use `dbApi.getBlob(reference)` to fetch it from S3.

```javascript
db.registerTrigger('users', 'delete', async (row, batch, dbApi) => {
  // `row` only contains the primary key for a delete: { pk: 'user_123' }
  const existingUsers = await batch.query('users').where('id', '=', row.pk).execute();
  const existingUser = existingUsers[0];

  if (existingUser && existingUser.avatar) {
    // The query returns the UUID string for the blob
    const blobRef = dbApi.getBlob(existingUser.avatar);

    // Download the blob into memory (or string, depending on your needs)
    const buffer = await blobRef.getBuffer();
    
    // ... do something with the existing blob ...
  }
});
```

### Summary of Blob Handling in Triggers

1. **New blobs (Insert/Update):** Access the buffer directly via `row.fieldName`. It is fully in memory.
2. **Existing blobs (Update/Delete):** Query the row to get the UUID, then use `dbApi.getBlob(uuid).getBuffer()` to download it.
3. **Recursion Limit:** Triggers have a maximum call-stack depth of 10 to prevent infinite recursion loops. Exceeding this throws a `TriggerRecursionError`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bucketdb.sullux.com/usage/triggers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
