Introduction
I use Prisma ORM for database access in my TypeScript projects. I recently ran into an issue where I needed to skip optional fields in an upsert query. Setting undefined is not allowed in Prisma to avoid unexpected results. The prescribed solution - to set Prisma.skip - does not work in the version I’m using. Upgrading Prisma was not an option as I was in the middle of a feature.
So here’s what worked for me.
tldr; I wrote a function that creates args objects which have only the fields that are present in the incoming data object.
The schema
model PSP {
id Int @id @default(autoincrement())
userId String
pageId String @unique
pageTitle String
companyURL String?
supportEmail String?
}
The last 3 fields are optional.
The query
A Prisma query for upserting in the case where none of the fields are optional would look like:
const pspData = ....//Data object with values from the user
....
const psp = await db.pSP.upsert({
where: {
pageId: update.pageId,
userId: update.userId
},
create: {
pageTitle: pspData.pageTitle,
companyURL: pspData.companyURL,//Optional
supportEmail: pspData.supportEmail,//Optional
},
update: {
pageTitle: pspData.pageTitle,
companyURL: pspData.companyURL,//Optional
supportEmail: pspData.supportEmail,//Optional
}
});
Obviously, this does not work as the optional fields would be undefined, and Prisma does not allow that.
The first approach is a brute force way where I created the args objects based on which fields were present in the incoming data object.
const getCreateUpdateArgs = (update: PSPUpdate): { createArgs: any, updateArgs: any } => {
const createArgs: any = {}
const updateArgs: any = {}
if (update.pageTitle) {
createArgs.pageTitle = update.pageTitle;
updateArgs.pageTitle = update.pageTitle;
}
if (update.companyURL) {
createArgs.companyURL = update.companyURL;
updateArgs.companyURL = update.companyURL;
}
if (update.supportEmail) {
createArgs.supportEmail = update.supportEmail;
updateArgs.supportEmail = update.supportEmail;
}
createArgs.pageId = update.pageId;
createArgs.userId = update.userId;
return { createArgs, updateArgs };
}
I then used the function’s return values in the upsert query.
const { createArgs, updateArgs } = getCreateUpdateArgs(pspData);
const psp = await db.pSP.upsert({
where: {
pageId: update.pageId,
userId: update.userId
},
create: {
...createArgs
},
update: {
...updateArgs
}
});
This works, but I did not like the idea of checking each field in the incoming data object. My JavaScript skills are not great, so after a bit of searching this is what I came up with:
const getCreateUpdateArgs2 = (update: PSPUpdate): { createArgs: any, updateArgs: any } => {
const createArgs: any = {}
const updateArgs: any = {}
if (typeof update !== 'object' || update === null) {
throw new Error("Invalid update object");//Unexpected
}
if (Object.getOwnPropertyNames(update).length === 0) {
return { createArgs, updateArgs };
}
Object.entries(update).forEach(([key, value]) => {
if (value && !immutables.includes(key)) {
updateArgs[key] = value;
createArgs[key] = value;
}
});
return { createArgs, updateArgs };
}
This assumes a few things:
- That there are no nested objects.
- There are no fields we want to exclude from the update.
I’m sure there are edge cases I’m not considering, but it works for now.