---
name: tibi-hook-authoring
description: Write and debug server-side hooks for tibi-server (goja Go JS runtime). Covers IIFE structure, HookResponse/HookException types, context.filter Go-object quirk, single-item vs list retrieval, and MongoDB filter patterns. Use when creating or modifying files in api/hooks/.
---

# tibi-hook-authoring

Use this skill for **current tibi-server hook architecture**, not just simple CRUD filters. A real website project on this starter typically needs hooks for public filtering, SSR invalidation, action endpoints, validation, and editor safety.

## Hook file structure

Wrap every hook in an IIFE:

```js
;(function () {
    /** @type {HookResponse} */
    const response = { status: 200 }

    // ... hook logic ...

    return response
})()
```

Always return a `HookResponse` or throw a `HookException`.

For many hooks, throwing is the normal control flow, especially in SSR hooks where HTML/status are returned via a thrown object.

## Type safety

- Use inline JSDoc type casting: `/** @type {TypeName} */ (value)`.
- Reference typed collection entries from `types/global.d.ts`.
- Avoid `@ts-ignore`; use proper casting instead.
- Use `const` and `let` instead of `var` — the goja runtime supports them.

## context.filter — Go object quirk

`context.filter` is a Go object, not a regular JS object. Even when empty, it is **truthy**.

Always check with `Object.keys()`:

```js
const requestedFilter =
    context.filter &&
    typeof context.filter === "object" &&
    !Array.isArray(context.filter) &&
    Object.keys(context.filter).length > 0
        ? context.filter
        : null
```

**Never** use `context.filter || null` — it is always truthy and produces an empty filter inside `$and`, which crashes the Go server.

## Single-item vs. list retrieval

For `GET /:collection/:id`, the Go server sets `_id` automatically from the URL parameter.

GET read hooks should **not** set their own `_id` filter for `req.param("id")`. Only add authorization filters (e.g. `{ userId: userId }`).

## Current hook surfaces that matter for website projects

- Collection CRUD hooks under `get`, `post`, `put`, `delete`
- Bulk hooks for optimized bulk operations
- `audit.return` hooks for stripping sensitive data from audit output
- `actions:` hook chains for endpoint-like behavior without a backing CRUD collection

For website builds on this starter, do not force everything into collections. Contact forms, newsletter signups, webhook receivers, import jobs, calculators, or other endpoint-style logic often belong into `actions:` instead.

## HookResponse fields (GET hooks)

| Field         | Purpose                                                             |
| ------------- | ------------------------------------------------------------------- |
| `filter`      | MongoDB filter (list retrieval, or restrict single-item)            |
| `selector`    | MongoDB projection (`{ field: 0 }` exclude, `{ field: 1 }` include) |
| `offset`      | Pagination offset                                                   |
| `limit`       | Pagination limit                                                    |
| `sort`        | Sort specification                                                  |
| `pipelineMod` | Function to manipulate the aggregation pipeline                     |

## context.data for write hooks

- `context.data` can be an array for bulk operations — always guard with `!Array.isArray(context.data)`.
- For POST hooks, `context.data.id` may contain the new entry ID.
- For PUT, `req.param("id")` gives the entry ID.

## Bulk and optimized paths

- tibi-server supports optimized bulk paths.
- In bulk scenarios, `bind` still runs once at the start.
- Per-document validation/update/delete hooks may be skipped depending on the chosen bulk path.

If a website feature depends on per-entry logic, do not assume a bulk update behaves exactly like N single updates. Check whether a dedicated bulk hook exists or whether the optimized path changes the behavior you rely on.

## Action hooks

Actions are first-class endpoints and should be part of the skill set for complete website builds.

Typical action steps:

- `post.bind`
- `post.validate`
- `post.handle`
- `post.return`
- `get.handle`
- `get.return`

Use actions when the website needs business logic without a CRUD collection.

Typical website use cases:

- contact forms
- newsletter signups
- quote/order requests
- webhook receivers
- utility endpoints
- AI-assisted helper endpoints

## Practical hook patterns for this starter family

- public read filtering for `active`/publication state
- SSR cache invalidation after writes
- route-level SSR validation
- mutation safeguards for readonly/system-managed fields
- custom form/action validation
- audit-output sanitizing for sensitive fields

## Common pitfalls

- Do not assume browser/Node APIs in hooks. The runtime is goja-based server-side JS.
- Do not treat actions as fake collections unless there is a good reason.
- Do not assume bulk hooks run per document.
- Do not build SSR/cache logic into frontend code when the invalidation belongs in hooks.
