You should cut out your steps in a transaction to delete the tags not in the query before inserting the new ones. Then you can create and update your data and return your post
With interactiveTransactions
In your schema.prisma you should enable interactiveTransactions, for this:
generator client {
provider = "prisma-client-js"
previewFeatures = ["interactiveTransactions"]
}
Then cut out your steps in a transaction
// Remove <Post> if you aren't in typescript
await prisma.$transaction<Post>(async (trx) => {
const existingTags = tags.filter(({ id }) => id);
const newTags = tags.filter(({ id }) => !id);
await trx.tag.deleteMany({
where: {
id: {
notIn: existingTags.map(({ id }) => id),
},
},
});
// Update the existing tags
await Promise.all(
tags.map((tag) =>
trx.tag.update({
where: {
id: tag.id,
},
data: {
color: tag.color,
name: tag.name,
},
})
)
);
await trx.tag.createMany({
data: newTags.map((tag) => ({
...tag,
creator: {
connect: {
id: session.user.id,
},
},
})),
});
return trx.post.update({
where: {
id: postId,
},
data: {
title,
content,
updatedAt: new Date(),
},
include: {
tags: true,
},
});
});
Answer from Pompedup on Stack Overflow`upsert()` `where` is defined as `number | undefined` but does not allow `undefined` to not select anything
How is .upsertMany() implemented in Prisma ORM?
`<Tablename>UpsertArgs` select field does not match type for `db.<tablename>.upsert(item)`
Unsafe return of any typed value, using prisma upsert
Prisma doesn't natively support upsertMany.
There is a Feature Request to provide the upsertMany method.
As of now the best approach would be to loop over the data and invoke upsert in the loop along with using $transaction.
Example:
const collection = await prisma.$transaction(
userData.map(cur =>
prisma.cur.upsert({
where: { id: cur.id },
update: {},
create: { id: cur.id },
})
)
)
Here's a reference to $transaction API which should be helpful.
You can use upsert to update multiple objects like the following: In the following example, I am updating a user's profile and upserting multiple addresses by using the map function.
await this.prisma.user.update({
where: {
id: user.id
},
data:{
fullName: saveUserProfileDto?.fullName,
email: saveUserProfileDto?.email,
userAddresses: {
upsert: saveUserProfileDto?.addresses?.map(address => ({
where: {
uuid: address.uuid || ""
},
create: {
uuid: uuidv4(),
country: address.country,
cityTown: address.cityTown,
streetAddress: address.streetAddress,
apartmentSuit: address.apartmentSuit,
},
update: {
country: address.country,
cityTown: address.cityTown,
streetAddress: address.streetAddress,
apartmentSuit: address.apartmentSuit,
}
}))
}
}
});
So you don't need to delete anything before upserting.
I'm providing my solution based on the clarifications you provided in the comments. First I would make the following changes to your Schema.
Changing the schema
model A_User {
id Int @id
username String
age Int
bio String @db.VarChar(1000)
createdOn DateTime @default(now())
features A_Features[]
}
model A_Features {
id Int @id @default(autoincrement())
description String @unique
users A_User[]
}
Notably, the relationship between A_User and A_Features is now many-to-many. So a single A_Features record can be connected to many A_User records (as well as the opposite).
Additionally, A_Features.description is now unique, so it's possible to uniquely search for a certain feature using just it's description.
You can read the Prisma Guide on Relations to learn more about many-to-many relations.
Writing the update query
Again, based on the clarification you provided in the comments, the update operation will do the following:
Overwrite existing
featuresin aA_Userrecord. So any previousfeatureswill be disconnected and replaced with the newly provided ones. Note that the previousfeatureswill not be deleted fromA_Featurestable, but they will simply be disconnected from theA_User.featuresrelation.Create the newly provided features that do not yet exist in the
A_Featurestable, and Connect the provided features that already exist in theA_Featurestable.
You can perform this operation using two separate update queries. The first update will Disconnect all previously connected features for the provided A_User. The second query will Connect or Create the newly provided features in the A_Features table. Finally, you can use the transactions API to ensure that both operations happen in order and together. The transactions API will ensure that if there is an error in any one of the two updates, then both will fail and be rolled back by the database.
//inside async function
const disconnectPreviouslyConnectedFeatures = prisma.a_User.update({
where: {id: 1},
data: {
features: {
set: [] // disconnecting all previous features
}
}
})
const connectOrCreateNewFeatures = prisma.a_User.update({
where: {id: 1},
data: {
features: {
// connect or create the new features
connectOrCreate: [
{
where: {
description: "'first feature'"
}, create: {
description: "'first feature'"
}
},
{
where: {
description: "second feature"
}, create: {
description: "second feature"
}
}
]
}
}
})
// transaction to ensure either BOTH operations happen or NONE of them happen.
await prisma.$transaction([disconnectPreviouslyConnectedFeatures, connectOrCreateNewFeatures ])
If you want a better idea of how connect, disconnect and connectOrCreate works, read the Nested Writes section of the Prisma Relation queries article in the docs.
The TypeScript definitions of prisma.a_User.update can tell you exactly what options it takes. That will tell you why the 'features' does not exist in type error is occurring. I imagine the object you're passing to data takes a different set of options than you are specifying; if you can inspect the TypeScript types, Prisma will tell you exactly what options are available.
If you're trying to add new features, and update specific ones, you would need to specify how Prisma can find an old feature (if it exists) to update that one. Upsert won't work in the way that you're currently using it; you need to provide some kind of identifier to the upsert call in order to figure out if the feature you're adding already exists.
https://www.prisma.io/docs/reference/api-reference/prisma-client-reference/#upsert
You need at least create (what data to pass if the feature does NOT exist), update (what data to pass if the feature DOES exist), and where (how Prisma can find the feature that you want to update or create.)
You also need to call upsert multiple times; one for each feature you're looking to update or create. You can batch the calls together with Promise.all in that case.
const upsertFeature1Promise = prisma.a_User.update({
data: {
// upsert call goes here, with "create", "update", and "where"
}
});
const upsertFeature2Promise = prisma.a_User.update({
data: {
// upsert call goes here, with "create", "update", and "where"
}
});
const [results1, results2] = await Promise.all([
upsertFeaturePromise1,
upsertFeaturePromise2
]);