Ning Kuang
Ning Kuang4mo ago

Best practice to store "callbacks" in database?

For example, let's say I have a JS interface for game achievement:
type Achievement = {
title: string;

// based on user's game data, return whether the criteria of this achievement is met or not
isCriteriaMet: (ctx: UserGameData) => boolean;

// if criteria is met, reward user by running this function to mutate user's data
reward: () => void;
}
type Achievement = {
title: string;

// based on user's game data, return whether the criteria of this achievement is met or not
isCriteriaMet: (ctx: UserGameData) => boolean;

// if criteria is met, reward user by running this function to mutate user's data
reward: () => void;
}
What is the best practice to represent this in Convex DB? Or in any DB? My thought is to store reference to a query function (in my code) in isCriteriaMet field; store reference to a mutation function in reward field. Or maybe just references to helper functions with context? If implemented this way, is there a Convex internal API where I can sync the list of query/mutation functions I defined in my code to the Convex DB, so I can safely reference them in another table?
2 Replies
Convex Bot
Convex Bot4mo ago
Thanks for posting in <#1088161997662724167>. Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets. - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.) - Use search.convex.dev to search Docs, Stack, and Discord all at once. - Additionally, you can post your questions in the Convex Community's <#1228095053885476985> channel to receive a response from AI. - Avoid tagging staff unless specifically instructed. Thank you!
erquhart
erquhart4mo ago
Welcome! Apart from the specific approach you’ve outlined, this is a pretty simple use case - you would determine if criteria is met at runtime in your Convex function. Here's a contrived mutation that depicts what I think you're going for:
const createAchievement = mutation({
args: {
achievementId: v.id("achievements"),
},
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
throw new Error("User not found");
}
// Check for existing achievement
const existingAchievement = await ctx.db
.query("user_achievements")
.withIndex("by_user_achievement", (q) =>
q.eq("userId", userId).eq("achievementId", args.achievementId),
)
.first();
if (existingAchievement) {
return;
}
// Add logic here to determine if criteria is met
// This includes running any necessary queries
// If criteria is not met, return early

// Create user achievement
await ctx.db.insert("user_achievements", {
userId,
achievementId: args.achievementId,
});
},
});
const createAchievement = mutation({
args: {
achievementId: v.id("achievements"),
},
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
throw new Error("User not found");
}
// Check for existing achievement
const existingAchievement = await ctx.db
.query("user_achievements")
.withIndex("by_user_achievement", (q) =>
q.eq("userId", userId).eq("achievementId", args.achievementId),
)
.first();
if (existingAchievement) {
return;
}
// Add logic here to determine if criteria is met
// This includes running any necessary queries
// If criteria is not met, return early

// Create user achievement
await ctx.db.insert("user_achievements", {
userId,
achievementId: args.achievementId,
});
},
});

Did you find this page helpful?