skip to Main Content

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.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();

2

Answers


  1. Chosen as BEST ANSWER

    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 ids from strings to mongoose.Type.ObjectIds.


  2. 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
            ]
          }
        }
      }
    ])
    

    Mongo Playground

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search