motz
motz2mo ago

Upsert feature

Hi, I'm currently doing the following:
export const setBalance = internalMutation({
args: {
minecraftUuid: v.string(),
balance: v.number(),
},
handler: async (ctx, args) => {
const player = await ctx.db
.query("players")
.withIndex("by_minecraft_uuid", (q) =>
q.eq("minecraftUuid", args.minecraftUuid),
)
.unique()
if (!player) {
await ctx.db.insert("players", {
minecraftUuid: args.minecraftUuid,
balance: args.balance,
})
return
}
await ctx.db.patch(player._id, {
balance: args.balance,
})
},
})
export const setBalance = internalMutation({
args: {
minecraftUuid: v.string(),
balance: v.number(),
},
handler: async (ctx, args) => {
const player = await ctx.db
.query("players")
.withIndex("by_minecraft_uuid", (q) =>
q.eq("minecraftUuid", args.minecraftUuid),
)
.unique()
if (!player) {
await ctx.db.insert("players", {
minecraftUuid: args.minecraftUuid,
balance: args.balance,
})
return
}
await ctx.db.patch(player._id, {
balance: args.balance,
})
},
})
But I was wondering if there was a simpler way to achieve this UPSERT-like behaviour in Convex? I mean it should be possible to automatically create a new document if none matches, right? Or could I create my own helper function for that? Anyone have an idea of how to go about doing that?
2 Replies
Convex Bot
Convex Bot2mo 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
erquhart2mo ago
That's exactly the right way to do an upsert. Having built with a Convex a decent amount, I can tell you this abstraction is probably not worth worrying about - Convex api's are low level, there are many things like this that you're going to do in a somewhat manual way. Almost better to just get used to the mechanics. But that's just my personal opinion. If you do want a reusable upsert helper, it'd look something like this:
import { MutationCtx } from "./_generated/server";
import { Doc, Id, TableNames } from "./_generated/dataModel";
import { WithoutSystemFields } from "convex/server";

const upsert = async <T extends TableNames>(
ctx: MutationCtx,
table: T,
id: Id<T>,
data: WithoutSystemFields<Doc<T>>,
) => {
const existing = await ctx.db.get(id);
if (existing) {
await ctx.db.patch(id, data as Partial<Doc<T>>);
} else {
await ctx.db.insert(table, data);
}
};
import { MutationCtx } from "./_generated/server";
import { Doc, Id, TableNames } from "./_generated/dataModel";
import { WithoutSystemFields } from "convex/server";

const upsert = async <T extends TableNames>(
ctx: MutationCtx,
table: T,
id: Id<T>,
data: WithoutSystemFields<Doc<T>>,
) => {
const existing = await ctx.db.get(id);
if (existing) {
await ctx.db.patch(id, data as Partial<Doc<T>>);
} else {
await ctx.db.insert(table, data);
}
};

Did you find this page helpful?