Describe the bug
The documentation for entity mutation hooks states:
mutation operations initiated with this client will not trigger the entity mutation hooks again
However, calling client.$setAuth(...) inside afterEntityMutation creates a new ZenStackClient instance (due to immutability) that loses the no-hook-trigger flag. Any mutation performed through this derived client re-triggers the entity mutation hooks, leading to infinite recursion.
To Reproduce
import { definePlugin } from '@zenstackhq/orm'
export const myPlugin = definePlugin({
id: 'my-plugin',
onEntityMutation: {
runAfterMutationWithinTransaction: true,
async beforeEntityMutation({ model, action, queryId, loadBeforeMutationEntities }) {
if (model !== 'Order') return
if (action !== 'update') return
const entities = (await loadBeforeMutationEntities()) || []
cache.set(queryId, entities)
},
async afterEntityMutation({ model, action, queryId, loadAfterMutationEntities, client }) {
if (model !== 'Order') return
if (action !== 'update') return
const afterEntities = (await loadAfterMutationEntities()) || []
const beforeEntities = cache.get(queryId) || []
for (const after of afterEntities) {
const before = beforeEntities.find(b => b.id === after.id)
if (!before || after.amount === before.amount) continue
// WORKS: using client directly (no-hook-trigger preserved)
await client.order.update({
where: { id: after.id },
data: { status: after.amount === 0 ? 'completed' : 'active' },
})
// BUG: using $setAuth creates a NEW client that re-triggers hooks
const adminClient = client.$setAuth({ role: 'admin' })
await adminClient.order.update({
where: { id: after.id },
data: { status: after.amount === 0 ? 'completed' : 'active' },
})
// ^ This triggers beforeEntityMutation + afterEntityMutation again
// causing infinite recursion
}
},
},
})
const cache = new Map<any, any[]>()
Expected behavior
client.$setAuth(...) should return a derived client that preserves the no-hook-trigger behavior of the original hook client. The auth context should change, but the hook suppression should be inherited.
Actual behavior
client.$setAuth(...) returns a new ZenStackClient instance that re-triggers entity mutation hooks, causing infinite recursion when the hook performs mutations on the same model it listens to.
Describe the bug
The documentation for entity mutation hooks states:
mutation operations initiated with this client will not trigger the entity mutation hooks againHowever, calling client.$setAuth(...) inside afterEntityMutation creates a new ZenStackClient instance (due to immutability) that loses the no-hook-trigger flag. Any mutation performed through this derived client re-triggers the entity mutation hooks, leading to infinite recursion.
To Reproduce
Expected behavior
client.$setAuth(...)should return a derived client that preserves the no-hook-trigger behavior of the original hook client. The auth context should change, but the hook suppression should be inherited.Actual behavior
client.$setAuth(...)returns a new ZenStackClient instance that re-triggers entity mutation hooks, causing infinite recursion when the hook performs mutations on the same model it listens to.