Automattic / mongoose

MongoDB object modeling designed to work in an asynchronous environment.
https://mongoosejs.com
MIT License
26.95k stars 3.84k forks source link

TypeScript error when passing `{ _id: ids }` or `{ _id: arrayOfDocs }` to `FilterQuery` #10826

Closed ahmedelshenawy25 closed 3 years ago

ahmedelshenawy25 commented 3 years ago

Do you want to request a feature or report a bug? Bug

What is the current behavior? TypeScript compilation error if _id is given an array of ids or documents in FilterQuery

Type '(Document<any, any, IBook> & IBook & { _id: string | ObjectId; })[]' is not assignable to type 'Condition<string | ObjectId> | undefined'.
  Type '(Document<any, any, IBook> & IBook & { _id: string | ObjectId; })[]' is not assignable to type 'string'
Type '(string | ObjectId)[]' is not assignable to type 'Condition<string | ObjectId> | undefined'.
  Type '(string | ObjectId)[]' is not assignable to type 'string'.

If the current behavior is a bug, please provide the steps to reproduce.

import mongoose, { Types } from 'mongoose';

interface IBook {
  _id: Types.ObjectId | string;
  name: string;
}

async function run() {
  await mongoose.connect('mongodb://localhost:27017/test');

  const bookSchema = new mongoose.Schema<IBook>({
    name: { type: String, required: true }
  });

  const Book = mongoose.model<IBook>('Book', bookSchema);

  await Book.create([{ name: 'aaa' }, { name: 'aaa' }]);

  const books = await Book.find({});
  const booksIds = books.map((book: IBook) => book._id);

  await Book.updateMany({ _id: books }, { name: 'bbb' });
  await Book.updateMany({ _id: booksIds }, { name: 'ccc' });

  console.log('Done');
}

run().catch(console.error);
{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

What is the expected behavior? It should pass compilation since mongoose transforms FilterQuery into { _id :{ $in: ids} }

What are the versions of Node.js, Mongoose and MongoDB you are using? Note that "latest" is not a version. Node.js: v14.15.5 Mongoose: v6.0.8 MongoDB: v4.4.3

IslandRhythms commented 3 years ago

So this isn't a bug. The way updateMany() works is that it will find all documents that match a filter, the 1st parameter, and change it to match what was given in the 2nd parameter. When you do {_id: books}, what you're doing is passing in an array because that is what find() returns when given no arguments. This is why if you were to do {_id: books[0]._id} you would get no error. It seems from the script you want to update all the docs with the new name so just omit the 1st parameter from the updateMany() call and it will do what you want. So do updateMany({}, {name: 'bbb'})

ahmedelshenawy25 commented 3 years ago

I think there's a bit of misunderstanding. So if you add // @ts-expect-error before each update and run the script with mongoose.set('debug', true), you will find that mongoose transforms both of these filter queries into { _id: { $in: [objectIds] } } and since mongoose allows this kind of behavior TypeScript should compile without any errors

import mongoose, { Types } from 'mongoose';

interface IBook {
  _id: Types.ObjectId | string;
  name: string;
}

async function run() {
  await mongoose.connect('mongodb://localhost:27017/test');

  mongoose.set('debug', true);

  const bookSchema = new mongoose.Schema<IBook>({
    name: { type: String, required: true }
  });

  const Book = mongoose.model<IBook>('Book', bookSchema);

  await Book.create([{ name: 'aaa' }, { name: 'aaa' }, { name: 'aaa' }, { name: 'aaa' }]);

  const books = await Book.find({}).limit(2);
  const booksIds = books.map((book: IBook) => book._id);

  // @ts-expect-error
  await Book.updateMany({ _id: books }, { name: 'bbb' });
  // @ts-expect-error
  await Book.updateMany({ _id: booksIds }, { name: 'ccc' });

  console.log('Done');
}

run().catch(console.error);

image