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
{{ message }}
Repository navigation
Add type inference to loadBeforeMutationEntities and loadAfterMutationEntities #2365
If the return data is typed, it becomes easier working with any follow up queries:
exportconstonAfterPostDelete=definePlugin<SchemaType>({id: "onAfterPostDelete",onEntityMutation: {asyncbeforeEntityMutation({ model, action, loadBeforeMutationEntities, client, queryId }){constdeletedPost=awaitloadBeforeMutationEntities()// <--- typed as Record<string, unknown>[] | undefined but its really a Post[]if(action==="delete"&&model==="Post"){// Do some logicconsttags=awaitclient.tag.findMany({where: {postId: deletedPost[0].id,// <-- This doesn't work unless we cast deletedPost to proper type like Post[]},})}},},})
After investigating the codebase, here is a potential strategy:
Why this is tricky
The hook callback receives model as a runtime value — a union of all model name strings (GetModels<Schema>). TypeScript can't use a runtime value to conditionally type another property in the same object. So when you write if (model === "Post"), TS narrows model to "Post" but has no way to simultaneously narrow the return type of loadBeforeMutationEntities().
All the building blocks for typed entities already exist internally (DefaultModelResult<Schema, Model> computes the plain entity shape for any given model), they just aren't wired up to the plugin hook types.
Possible strategy
Per-model hook registration (medium effort, best DX, this may be more in line with current patterns anyway)
Allow registering callbacks per model instead of (or in addition to) a single catch-all:
onEntityMutation: {Post: {asyncbeforeEntityMutation({ action, loadBeforeMutationEntities }){constposts=awaitloadBeforeMutationEntities();// ← typed as Post[]}}}
Personally I think this is the best balance of effort and DX — the type machinery already exists, it just needs to be wired into a per-model registration shape.
@ymc9 Any thoughts on the proposed solution above?
we can keep the global hooks as is (for backward compatibility), and add the per-model hook registration.
Hi @genu , I agree per-model registration is the solution to this issue. Probably in most cases people would be interested in hooking into a few models instead of all. If we make that change, it'll look nicer if the "all-model" reg is put under a special "$all" key, to be consistent with conventions in other places. But it'll incur a breaking change.
I'm wondering if this is a high priority. Are you implementing a complex hook?
Activity
genu commentedon Feb 10, 2026
After investigating the codebase, here is a potential strategy:
Why this is tricky
The hook callback receives
modelas a runtime value — a union of all model name strings (GetModels<Schema>). TypeScript can't use a runtime value to conditionally type another property in the same object. So when you writeif (model === "Post"), TS narrowsmodelto"Post"but has no way to simultaneously narrow the return type ofloadBeforeMutationEntities().All the building blocks for typed entities already exist internally (
DefaultModelResult<Schema, Model>computes the plain entity shape for any given model), they just aren't wired up to the plugin hook types.Possible strategy
Per-model hook registration (medium effort, best DX, this may be more in line with current patterns anyway)
Allow registering callbacks per model instead of (or in addition to) a single catch-all:
Personally I think this is the best balance of effort and DX — the type machinery already exists, it just needs to be wired into a per-model registration shape.
genu commentedon Mar 19, 2026
@ymc9 Any thoughts on the proposed solution above?
we can keep the global hooks as is (for backward compatibility), and add the
per-modelhook registration.ymc9 commentedon May 30, 2026
Hi @genu , I agree per-model registration is the solution to this issue. Probably in most cases people would be interested in hooking into a few models instead of all. If we make that change, it'll look nicer if the "all-model" reg is put under a special "$all" key, to be consistent with conventions in other places. But it'll incur a breaking change.
I'm wondering if this is a high priority. Are you implementing a complex hook?
genu commentedon Jun 4, 2026
Hi @ymc9
You're right, it would technically, be a breaking change. It isn't a high priority for me since I'm able to work around it.
I can create a PR for this that can be merged in at some point in the future along with other breaking changes if you'd like.
I just wanted to bump this, so it isn't forgotten.
feat(orm)!: Add typed typed mutation hooks #2365 (#2740)
ymc9 commentedon Oct 27, 2026
Released in v3.10.0 via #2740.