491f495c66
- Updated `tibi-project-setup` skill to clarify project initialization goals and steps. - Improved `tibi-ssr-caching` skill to detail SSR architecture, responsibilities, and caching mechanisms. - Introduced `website-solution-architecture` skill for translating website requirements into coherent solutions. - Refined `AGENTS.md` to provide a structured roadmap for project development phases. - Added `ADMIN_ASSET_VERSION` to `api/config.yml.env` for asset versioning. - Updated SSR request flow and cache invalidation logic in `api/hooks/ssr/AGENTS.md`. - Removed obsolete `esbuild.config.admin.js` and integrated asset versioning into the main `esbuild.config.js`. - Adjusted `api/collections/content.yml` to utilize asset versioning for admin scripts.
133 lines
5.0 KiB
Markdown
133 lines
5.0 KiB
Markdown
---
|
|
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.
|