Problem
`#[entity(table = "…", hooks)]` generates a `{Entity}Hooks` trait with default `Ok(())` methods and a doc comment promising "If a `before_*` hook returns an error, the operation is aborted." Users reading the docs reasonably expect generated `create` / `update` / `delete` to invoke those hooks automatically.
They don't. `crates/entity-derive-impl/src/entity/hooks.rs` emits the trait; the CRUD generators in `sql/postgres/crud.rs` never call it. The `examples/hooks/src/main.rs` invokes hooks manually from HTTP handlers — that is the only path that actually fires them today.
Root cause
Generated CRUD lives in `impl {Entity}Repository for sqlx::PgPool`. The hooks trait would have to be implemented for `PgPool` to be invokable from there, but:
- Orphan rule blocks user crates from writing `impl {Entity}Hooks for sqlx::PgPool` — both type and trait are foreign.
- A blanket `impl {Entity}Hooks for sqlx::PgPool` emitted by the macro pins all users to the no-op default; specialization is unstable.
Real auto-invocation needs a wrapper type owned by the user crate (e.g. `pub struct UserRepo<H: UserHooks> { pool: PgPool, hooks: H }`) and a parallel `Repository` impl on that wrapper. That is a meaningful API surface change.
Scope of this PR
Honest documentation while the architecture is fixed:
- `crates/entity-derive-impl/src/entity/hooks.rs` module docs — add a "Status" block at the top stating clearly that traits are emitted for the user to implement and invoke manually; auto-invocation is tracked in the follow-up issue.
- `README.md` Features table — soften the "Lifecycle Hooks" entry to reflect that hooks are an opt-in user contract, not auto-fired, and link to the follow-up issue.
- `examples/hooks/src/main.rs` — add a short top-of-file comment naming the manual-wiring pattern explicitly so users don't waste time looking for the missing wiring.
Out of scope (separate follow-up issue, opened on merge)
Auto-invocation in generated CRUD via a wrapper struct or a generic on the trait — pick one of:
- `pub struct UserRepo<H: UserHooks = NoopUserHooks> { pool: PgPool, hooks: H }` + `impl<H: UserHooks> UserRepository for UserRepo`. Backward compatible: pool-as-repo keeps working without hooks; `UserRepo::new(pool, MyHooks)` opts in.
- Hook trait method on `Repository` (`fn hooks(&self) -> &impl …Hooks`) with a no-op default impl — needs GATs cleanup.
Either path is a minor-version bump and deserves its own RFC.
Acceptance
After this PR, a user reading the README / module docs / example can no longer be misled into thinking generated CRUD invokes their hooks. The follow-up issue carries the design discussion for the real fix.
Problem
`#[entity(table = "…", hooks)]` generates a `{Entity}Hooks` trait with default `Ok(())` methods and a doc comment promising "If a `before_*` hook returns an error, the operation is aborted." Users reading the docs reasonably expect generated `create` / `update` / `delete` to invoke those hooks automatically.
They don't. `crates/entity-derive-impl/src/entity/hooks.rs` emits the trait; the CRUD generators in `sql/postgres/crud.rs` never call it. The `examples/hooks/src/main.rs` invokes hooks manually from HTTP handlers — that is the only path that actually fires them today.
Root cause
Generated CRUD lives in `impl {Entity}Repository for sqlx::PgPool`. The hooks trait would have to be implemented for `PgPool` to be invokable from there, but:
Real auto-invocation needs a wrapper type owned by the user crate (e.g. `pub struct UserRepo<H: UserHooks> { pool: PgPool, hooks: H }`) and a parallel `Repository` impl on that wrapper. That is a meaningful API surface change.
Scope of this PR
Honest documentation while the architecture is fixed:
Out of scope (separate follow-up issue, opened on merge)
Auto-invocation in generated CRUD via a wrapper struct or a generic on the trait — pick one of:
Either path is a minor-version bump and deserves its own RFC.
Acceptance
After this PR, a user reading the README / module docs / example can no longer be misled into thinking generated CRUD invokes their hooks. The follow-up issue carries the design discussion for the real fix.