I pull a value from an array and immediately add it back to bring it to the front of the array.
await User.findByIdAndUpdate(authenticatedUserId,
{ $pull: { lastVisitedResources: resourceId } },
).exec();
await User.findByIdAndUpdate(authenticatedUserId,
{ $push: { lastVisitedResources: { $each: [resourceId], $position: 0, $slice: -50 } } },
).exec();
Is there a way to execute this more efficiently via a bulk operation?
I tried Mongoose' bulkWrite as well as MongoDB's lower-level db.collection.bulkWrite but TypeScript doesn't accept the $pull
and $push
operators for either of them:
await User.collection.bulkWrite([
{
updateOne: {
filter: { _id: authenticatedUserId },
update: {
$pull: { lastVisitedResources: resourceId }
}
}
},
{
updateOne: {
filter: { _id: authenticatedUserId },
update: {
$push: { lastVisitedResources: { $each: [resourceId], $position: 0, $slice: 50 } }
}
}
}
])
prints the following error:
Type '{ lastVisitedResources: string; }' is not assignable to type 'PullOperator<Document>'.
Type '{ lastVisitedResources: string; }' is not assignable to type '{ readonly [x: string]: Partial<any> | { [x: string]: FilterOperators<any> | undefined; } | FilterOperators<any> | undefined; }'.
Property 'lastVisitedResources' is incompatible with index signature.
Type 'string' is not assignable to type 'Partial<any> | { [x: string]: FilterOperators<any> | undefined; } | FilterOperators<any> | undefined'.ts(2322)
I also tried initializeOrderedBulkOp
but it has no effect:
const bulk = User.collection.initializeOrderedBulkOp();
bulk.find({ _id: authenticatedUserId })
.updateOne({ $pull: { lastVisitedResources: resourceId } });
bulk.find({ _id: authenticatedUserId })
.updateOne({ $push: { lastVisitedResources: { $each: [resourceId], $position: 0, $slice: -50 } } });
await bulk.execute();
CodePudding user response:
You can do using the aggregation pipeline updates syntax, like so:
User.findByIdAndUpdate(authenticatedUserId,
[
{
$set: {
lastVisitedResources: {
$slice: [
{
$concatArrays: [
[
resourceId
],
{
$filter: {
input: {
$ifNull: [
"$lastVisitedResources",
[]
]
},
cond: {
$ne: [
"$$this",
resourceId
]
}
}
}
]
},
50
]
}
}
}
])
CodePudding user response:
Turns out, this is actually a false positive by TypeScript. Following @Tom Slabbaert's suggestion, I added ts-ignore
to the $push
and $pull
operators and it works properly:
await User.bulkWrite([
{
updateOne: {
filter: { _id: authenticatedUserId },
update: {
// @ts-ignore
$pull: { lastVisitedResources: resourceId }
}
}
},
{
updateOne: {
filter: { _id: authenticatedUserId },
update: {
// @ts-ignore
$push: { lastVisitedResources: { $each: [resourceId], $position: 0, $slice: 50 } }
}
}
}
])
Note: If you use the native db.collection.bulkWrite
instead of Mongoose's Model.bulkWrite
, you have to cast the id
s from string
s to mongoose.Type.ObjectId
s.