Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion content/docs/api/data-flow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ flowchart TD
end
```

The lifecycle events are defined by the `HookEvent` enum (8 events). Reads fire `beforeFind`/`afterFind` — for **both** `find` and `findOne`, so one subscription covers every read shape. Bulk writes (`multi: true`) fire the **same** `beforeUpdate`/`beforeDelete`/`afterUpdate`/`afterDelete` events as single-id writes, with the row-scoping predicate in `ctx.input.ast`. There are deliberately no per-method (`findOne`/`count`/`aggregate`) or `*Many` events: read authorization and row filtering are RLS/permission-rule concerns, and field masking is field-level metadata.
The lifecycle events are defined by the `HookEvent` enum (8 events). Reads fire `beforeFind`/`afterFind` — for **both** `find` and `findOne`, so one subscription covers every read shape. Bulk writes (`multi: true`) fire the **same** `beforeUpdate`/`beforeDelete`/`afterUpdate`/`afterDelete` events as single-id writes. A bulk write hands hooks **no** row-scoping predicate: it lives on the engine-internal `OperationContext.ast`, so the RLS / sharing filters composed onto it bind the driver call itself, where no handler can widen them — scope a batch through `options.where` at the caller. The `after*` events instead dispatch **once per matched row**, each on a single-record-shaped context whose `input.id` names that row. There are deliberately no per-method (`findOne`/`count`/`aggregate`) or `*Many` events: read authorization and row filtering are RLS/permission-rule concerns, and field masking is field-level metadata.

| Hook | Phase | Can Modify? | Can Abort? |
|:---|:---|:---|:---|
Expand Down
15 changes: 10 additions & 5 deletions skills/objectstack-data/references/data-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ ObjectStack provides **8 lifecycle events** organized by operation type:
> **Why only 8?** The read events fire for `findOne` as well as `find` (the event
> attaches to record materialization, not the engine method), so one subscription
> covers every read shape — there is no `beforeFindOne`/`afterFindOne`. Likewise the
> write events fire on bulk `multi:true` operations (the row-scoping predicate is in
> `ctx.input.ast`), so there is no `*Many` event. And there is no `beforeCount`/
> `beforeAggregate`: read authorization and row filtering belong to **RLS / permission
> rules**, and field masking to **field-level metadata** — declarative mechanisms that
> apply everywhere, rather than a hook every author must remember to re-attach.
> write events fire on bulk `multi:true` operations, so there is no `*Many` event. A
> bulk write hands hooks **no** row-scoping predicate: it lives on the engine-internal
> `OperationContext.ast` (#2982), so the RLS / sharing filters composed onto it bind
> the driver call itself, where no handler can widen them — scope a batch through
> `options.where` at the caller. The `after*` events instead dispatch **once per
> matched row**, each on a single-record-shaped context whose `input.id` names that
> row (#5038). And there is no `beforeCount`/`beforeAggregate`: read authorization and
> row filtering belong to **RLS / permission rules**, and field masking to
> **field-level metadata** — declarative mechanisms that apply everywhere, rather than
> a hook every author must remember to re-attach.

### Before vs After Hooks

Expand Down
11 changes: 8 additions & 3 deletions skills/objectstack-data/rules/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,14 @@ Sandbox essentials (full contract in

> **One read event, one write event per kind.** `beforeFind`/`afterFind` fire for
> `findOne` too (the event attaches to record materialization, not the method), and
> the write events fire on bulk `multi:true` operations as well — the row-scoping
> predicate is in `ctx.input.ast`. There is no `beforeFindOne`, `beforeCount`,
> `beforeAggregate`, or `*Many` event.
> the write events fire on bulk `multi:true` operations as well. A bulk write hands
> hooks **no** row-scoping predicate: it lives on the engine-internal
> `OperationContext.ast` (#2982), so the RLS / sharing filters composed onto it bind
> the driver call itself, where no handler can widen them — scope a batch through
> `options.where` at the caller. The `after*` events instead dispatch **once per
> matched row**, each on a single-record-shaped context whose `input.id` names that
> row (#5038). There is no `beforeFindOne`, `beforeCount`, `beforeAggregate`, or
> `*Many` event.
>
> **Don't reach for a hook when a declarative mechanism already fits:**
> - Read authorization / row filtering → **RLS / permission rules**, not a `beforeFind` hook.
Expand Down
Loading