- 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.
5.0 KiB
name, description
| name | description |
|---|---|
| tibi-hook-authoring | 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:
;(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
constandletinstead ofvar— 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():
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.returnhooks for stripping sensitive data from audit outputactions: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.datacan be an array for bulk operations — always guard with!Array.isArray(context.data).- For POST hooks,
context.data.idmay 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,
bindstill 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.bindpost.validatepost.handlepost.returnget.handleget.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.