You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The HookEvent enum declared 18 events but the engine only ever dispatches 8.
The 10 dead ones (beforeFindOne/afterFindOne, beforeCount/afterCount,
beforeAggregate/afterAggregate, beforeUpdateMany/afterUpdateMany,
beforeDeleteMany/afterDeleteMany) mirrored the engine method table rather than
domain events, so a hook subscribing to them registered fine and silently
never fired — the same declared≠enforced failure mode as the validation
delete-event gap (#3184), and the skills even shipped a copy-pasteable
afterFindOne masking example that no-ops.
Design decision (see issue): collapse the taxonomy, don't build out dispatch.
Peer platforms (Salesforce, Postgres, Rails) don't expose per-method read
triggers or single/bulk splits — reads are one materialization event, writes
are bulk-first. So:
- findOne now fires the SAME beforeFind/afterFind hooks as find (the read
event attaches to record materialization, not the method) — one subscription
covers every read shape.
- Bulk (multi:true) writes already fire the singular before/after write events
with the row-scoping predicate in ctx.input.ast; documented, no *Many event.
- Removed the 10 dead enum values. Read authz/row-filtering is the RLS/
permission layer; field masking is field metadata — not per-author hooks.
- engine.registerHook warns when a hook subscribes to a non-dispatched event,
so enum-vs-dispatch drift can't recur silently.
No shipped hook or authored metadata used any removed event. Skills and docs
(hooks.md, data-hooks.md, kernel/events.mdx, api/data-flow.mdx,
protocol/objectql/schema.mdx) updated to teach 8 events + the declarative
alternatives; regenerated content/docs/references/data/hook.mdx. Added tests
for findOne firing find hooks and the registration guard.
Closes#3195
Claude-Session: https://claude.ai/code/session_01VCUSMJBsX14C3RQdWFw7N7
Co-authored-by: Claude <noreply@anthropic.com>
Collapse the hook event taxonomy from 18 declared events to the 8 the engine actually dispatches (#3195). The removed 10 (`beforeFindOne`/`afterFindOne`, `beforeCount`/`afterCount`, `beforeAggregate`/`afterAggregate`, `beforeUpdateMany`/`afterUpdateMany`, `beforeDeleteMany`/`afterDeleteMany`) were declared in `HookEvent` but never fired — the enum mirrored the engine method table instead of domain events, so a hook subscribing to them registered fine and then silently no-op'd.
7
+
8
+
-`findOne` now fires the same `beforeFind`/`afterFind` hooks as `find` — the read event attaches to record materialization, not the engine method, so one subscription covers every read shape (no separate `beforeFindOne`/`afterFindOne`).
9
+
- Bulk (`multi: true`) updates/deletes already fire the singular `beforeUpdate`/`beforeDelete`/`afterUpdate`/`afterDelete` events with the row-scoping predicate in `ctx.input.ast`; this is now documented, and there is no `*Many` event.
10
+
- Read authorization / row filtering is the RLS/permission-rule layer's job and field masking is field-level metadata — neither is a hook every author must re-attach.
11
+
-`engine.registerHook` now warns when a hook subscribes to an event the engine never dispatches, so enum-vs-dispatch drift can't recur silently.
12
+
13
+
No shipped hook or authored metadata used any of the removed events; authoring one now fails loudly at parse/validate time instead of registering a dead hook. Skills and docs updated to teach the 8 events and the declarative alternatives.
Copy file name to clipboardExpand all lines: content/docs/api/data-flow.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -96,7 +96,7 @@ sequenceDiagram
96
96
97
97
QE-->>K: DataRecord
98
98
99
-
K->>H: afterFindOne hooks
99
+
K->>H: afterFind hooks (fire for findOne too)
100
100
H-->>K: Transformed record
101
101
102
102
K->>Sec: applyFieldSecurity(record, user)
@@ -310,7 +310,7 @@ flowchart TD
310
310
end
311
311
```
312
312
313
-
The lifecycle events are defined by the `HookEvent` enum. Reads also have hooks: `beforeFind/afterFind`, `beforeFindOne/afterFindOne`, `beforeCount/afterCount`, and `beforeAggregate/afterAggregate`. Bulk writes add `beforeUpdateMany/afterUpdateMany` and `beforeDeleteMany/afterDeleteMany`.
313
+
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.
Copy file name to clipboardExpand all lines: content/docs/kernel/events.mdx
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -58,10 +58,10 @@ The Data Engine runs hooks around record reads and mutations. Write operations f
58
58
| Event | When |
59
59
| :--- | :--- |
60
60
|`beforeInsert` / `afterInsert`| Around record creation. |
61
-
|`beforeUpdate` / `afterUpdate`| Around record update. |
62
-
|`beforeDelete` / `afterDelete`| Around record deletion. |
61
+
|`beforeUpdate` / `afterUpdate`| Around record update — single-id **and** bulk (`multi: true`). |
62
+
|`beforeDelete` / `afterDelete`| Around record deletion — single-id **and** bulk (`multi: true`). |
63
63
64
-
Read and bulk variants (`beforeFind`/`afterFind`, `beforeCount`, `beforeAggregate`, `beforeUpdateMany`, `beforeDeleteMany`, …) also exist.
64
+
Reads fire `beforeFind` / `afterFind`, for **both**`find` and `findOne` (one read event covers every read shape). There are no per-method (`findOne`/`count`/`aggregate`) or `*Many` events: read authorization and row filtering are handled by RLS/permission rules, field masking by field-level metadata, and bulk writes by the same `before*`/`after*` write events (with the row-scoping predicate in `ctx.input.ast`).
0 commit comments