Convex Triggers + Aggregates Pattern Question
We're using Convex triggers with TableAggregate to maintain aggregated data. We're encountering a pattern issue with mixing direct DB operations and trigger-wrapped mutations.
Setup:
Current Problem:
Setup:
// itemsTrigger.ts
const triggers = new Triggers<DataModel>()
export const aggregateItemScores = new TableAggregate<...>(
components.aggregateItemScores, {
namespace: (doc) => `${doc.groupId}-${doc.typeId}`,
sumValue: (doc) => doc.amount
}
)
triggers.register("items", aggregateItemScores.trigger())
export const itemsMutation = customMutation(
rawMutation,
customCtx(triggers.wrapDB)
)
export const itemsInternalMutation = customMutation(
rawInternalMutation,
customCtx(triggers.wrapDB)
)// itemsTrigger.ts
const triggers = new Triggers<DataModel>()
export const aggregateItemScores = new TableAggregate<...>(
components.aggregateItemScores, {
namespace: (doc) => `${doc.groupId}-${doc.typeId}`,
sumValue: (doc) => doc.amount
}
)
triggers.register("items", aggregateItemScores.trigger())
export const itemsMutation = customMutation(
rawMutation,
customCtx(triggers.wrapDB)
)
export const itemsInternalMutation = customMutation(
rawInternalMutation,
customCtx(triggers.wrapDB)
)Current Problem:
// items.ts
export const updateBatch = itemsMutation({ // <-- Wrapped with trigger
handler: async (ctx, args) => {
// Delete existing items
const existing = await getItems(ctx, args.groupId)
for (const item of existing) {
await ctx.db.delete(item._id) // :white_check_mark: Triggers aggregate update
}
// Add new items
for (const newItem of args.items) {
await ItemsHelper.upsertItem(ctx, { // :x: Does NOT trigger aggregate
groupId: args.groupId,
amount: newItem.amount
})
}
}
})
// Separate wrapped mutation for external calls
export const upsertItem = itemsInternalMutation({
handler: async (ctx, args) => {
return await ItemsHelper.upsertItem(ctx, args) // :white_check_mark: Triggers aggregate
}
})// items.ts
export const updateBatch = itemsMutation({ // <-- Wrapped with trigger
handler: async (ctx, args) => {
// Delete existing items
const existing = await getItems(ctx, args.groupId)
for (const item of existing) {
await ctx.db.delete(item._id) // :white_check_mark: Triggers aggregate update
}
// Add new items
for (const newItem of args.items) {
await ItemsHelper.upsertItem(ctx, { // :x: Does NOT trigger aggregate
groupId: args.groupId,
amount: newItem.amount
})
}
}
})
// Separate wrapped mutation for external calls
export const upsertItem = itemsInternalMutation({
handler: async (ctx, args) => {
return await ItemsHelper.upsertItem(ctx, args) // :white_check_mark: Triggers aggregate
}
})