mongoose 7.3.3 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,13 @@
5
5
  'use strict';
6
6
 
7
7
  const MongooseConnection = require('../../connection');
8
+ const MongooseError = require('../../error/index');
8
9
  const STATES = require('../../connectionstate');
10
+ const mongodb = require('mongodb');
11
+ const pkg = require('../../../package.json');
12
+ const processConnectionOptions = require('../../helpers/processConnectionOptions');
9
13
  const setTimeout = require('../../helpers/timers').setTimeout;
14
+ const utils = require('../../utils');
10
15
 
11
16
  /**
12
17
  * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation.
@@ -121,6 +126,44 @@ NativeConnection.prototype.useDb = function(name, options) {
121
126
  return newConn;
122
127
  };
123
128
 
129
+ /**
130
+ * Removes the database connection with the given name created with `useDb()`.
131
+ *
132
+ * Throws an error if the database connection was not found.
133
+ *
134
+ * #### Example:
135
+ *
136
+ * // Connect to `initialdb` first
137
+ * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/initialdb').asPromise();
138
+ *
139
+ * // Creates an un-cached connection to `mydb`
140
+ * const db = conn.useDb('mydb');
141
+ *
142
+ * // Closes `db`, and removes `db` from `conn.relatedDbs` and `conn.otherDbs`
143
+ * await conn.removeDb('mydb');
144
+ *
145
+ * @method removeDb
146
+ * @memberOf Connection
147
+ * @param {String} name The database name
148
+ * @return {Connection} this
149
+ */
150
+
151
+ NativeConnection.prototype.removeDb = function removeDb(name) {
152
+ const dbs = this.otherDbs.filter(db => db.name === name);
153
+ if (!dbs.length) {
154
+ throw new MongooseError(`No connections to database "${name}" found`);
155
+ }
156
+
157
+ for (const db of dbs) {
158
+ db._closeCalled = true;
159
+ db._destroyCalled = true;
160
+ db._readyState = STATES.disconnected;
161
+ db.$wasForceClosed = true;
162
+ }
163
+ delete this.relatedDbs[name];
164
+ this.otherDbs = this.otherDbs.filter(db => db.name !== name);
165
+ };
166
+
124
167
  /**
125
168
  * Closes the connection
126
169
  *
@@ -154,6 +197,210 @@ NativeConnection.prototype.doClose = async function doClose(force) {
154
197
  return this;
155
198
  };
156
199
 
200
+ /*!
201
+ * ignore
202
+ */
203
+
204
+ NativeConnection.prototype.createClient = async function createClient(uri, options) {
205
+ if (typeof uri !== 'string') {
206
+ throw new MongooseError('The `uri` parameter to `openUri()` must be a ' +
207
+ `string, got "${typeof uri}". Make sure the first parameter to ` +
208
+ '`mongoose.connect()` or `mongoose.createConnection()` is a string.');
209
+ }
210
+
211
+ if (this._destroyCalled) {
212
+ throw new MongooseError(
213
+ 'Connection has been closed and destroyed, and cannot be used for re-opening the connection. ' +
214
+ 'Please create a new connection with `mongoose.createConnection()` or `mongoose.connect()`.'
215
+ );
216
+ }
217
+
218
+ if (this.readyState === STATES.connecting || this.readyState === STATES.connected) {
219
+ if (this._connectionString !== uri) {
220
+ throw new MongooseError('Can\'t call `openUri()` on an active connection with ' +
221
+ 'different connection strings. Make sure you aren\'t calling `mongoose.connect()` ' +
222
+ 'multiple times. See: https://mongoosejs.com/docs/connections.html#multiple_connections');
223
+ }
224
+ }
225
+
226
+ options = processConnectionOptions(uri, options);
227
+
228
+ if (options) {
229
+
230
+ const autoIndex = options.config && options.config.autoIndex != null ?
231
+ options.config.autoIndex :
232
+ options.autoIndex;
233
+ if (autoIndex != null) {
234
+ this.config.autoIndex = autoIndex !== false;
235
+ delete options.config;
236
+ delete options.autoIndex;
237
+ }
238
+
239
+ if ('autoCreate' in options) {
240
+ this.config.autoCreate = !!options.autoCreate;
241
+ delete options.autoCreate;
242
+ }
243
+
244
+ if ('sanitizeFilter' in options) {
245
+ this.config.sanitizeFilter = options.sanitizeFilter;
246
+ delete options.sanitizeFilter;
247
+ }
248
+
249
+ // Backwards compat
250
+ if (options.user || options.pass) {
251
+ options.auth = options.auth || {};
252
+ options.auth.username = options.user;
253
+ options.auth.password = options.pass;
254
+
255
+ this.user = options.user;
256
+ this.pass = options.pass;
257
+ }
258
+ delete options.user;
259
+ delete options.pass;
260
+
261
+ if (options.bufferCommands != null) {
262
+ this.config.bufferCommands = options.bufferCommands;
263
+ delete options.bufferCommands;
264
+ }
265
+ } else {
266
+ options = {};
267
+ }
268
+
269
+ this._connectionOptions = options;
270
+ const dbName = options.dbName;
271
+ if (dbName != null) {
272
+ this.$dbName = dbName;
273
+ }
274
+ delete options.dbName;
275
+
276
+ if (!utils.hasUserDefinedProperty(options, 'driverInfo')) {
277
+ options.driverInfo = {
278
+ name: 'Mongoose',
279
+ version: pkg.version
280
+ };
281
+ }
282
+
283
+ this.readyState = STATES.connecting;
284
+ this._connectionString = uri;
285
+
286
+ let client;
287
+ try {
288
+ client = new mongodb.MongoClient(uri, options);
289
+ } catch (error) {
290
+ this.readyState = STATES.disconnected;
291
+ throw error;
292
+ }
293
+ this.client = client;
294
+
295
+ client.setMaxListeners(0);
296
+ await client.connect();
297
+
298
+ _setClient(this, client, options, dbName);
299
+
300
+ for (const db of this.otherDbs) {
301
+ _setClient(db, client, {}, db.name);
302
+ }
303
+ return this;
304
+ };
305
+
306
+ /*!
307
+ * ignore
308
+ */
309
+
310
+ NativeConnection.prototype.setClient = function setClient(client) {
311
+ if (!(client instanceof mongodb.MongoClient)) {
312
+ throw new MongooseError('Must call `setClient()` with an instance of MongoClient');
313
+ }
314
+ if (this.readyState !== STATES.disconnected) {
315
+ throw new MongooseError('Cannot call `setClient()` on a connection that is already connected.');
316
+ }
317
+ if (client.topology == null) {
318
+ throw new MongooseError('Cannot call `setClient()` with a MongoClient that you have not called `connect()` on yet.');
319
+ }
320
+
321
+ this._connectionString = client.s.url;
322
+ _setClient(this, client, {}, client.s.options.dbName);
323
+
324
+ for (const model of Object.values(this.models)) {
325
+ // Errors handled internally, so safe to ignore error
326
+ model.init().catch(function $modelInitNoop() {});
327
+ }
328
+
329
+ return this;
330
+ };
331
+
332
+ /*!
333
+ * ignore
334
+ */
335
+
336
+ function _setClient(conn, client, options, dbName) {
337
+ const db = dbName != null ? client.db(dbName) : client.db();
338
+ conn.db = db;
339
+ conn.client = client;
340
+ conn.host = client &&
341
+ client.s &&
342
+ client.s.options &&
343
+ client.s.options.hosts &&
344
+ client.s.options.hosts[0] &&
345
+ client.s.options.hosts[0].host || void 0;
346
+ conn.port = client &&
347
+ client.s &&
348
+ client.s.options &&
349
+ client.s.options.hosts &&
350
+ client.s.options.hosts[0] &&
351
+ client.s.options.hosts[0].port || void 0;
352
+ conn.name = dbName != null ? dbName : client && client.s && client.s.options && client.s.options.dbName || void 0;
353
+ conn._closeCalled = client._closeCalled;
354
+
355
+ const _handleReconnect = () => {
356
+ // If we aren't disconnected, we assume this reconnect is due to a
357
+ // socket timeout. If there's no activity on a socket for
358
+ // `socketTimeoutMS`, the driver will attempt to reconnect and emit
359
+ // this event.
360
+ if (conn.readyState !== STATES.connected) {
361
+ conn.readyState = STATES.connected;
362
+ conn.emit('reconnect');
363
+ conn.emit('reconnected');
364
+ conn.onOpen();
365
+ }
366
+ };
367
+
368
+ const type = client &&
369
+ client.topology &&
370
+ client.topology.description &&
371
+ client.topology.description.type || '';
372
+
373
+ if (type === 'Single') {
374
+ client.on('serverDescriptionChanged', ev => {
375
+ const newDescription = ev.newDescription;
376
+ if (newDescription.type === 'Unknown') {
377
+ conn.readyState = STATES.disconnected;
378
+ } else {
379
+ _handleReconnect();
380
+ }
381
+ });
382
+ } else if (type.startsWith('ReplicaSet')) {
383
+ client.on('topologyDescriptionChanged', ev => {
384
+ // Emit disconnected if we've lost connectivity to the primary
385
+ const description = ev.newDescription;
386
+ if (conn.readyState === STATES.connected && description.type !== 'ReplicaSetWithPrimary') {
387
+ // Implicitly emits 'disconnected'
388
+ conn.readyState = STATES.disconnected;
389
+ } else if (conn.readyState === STATES.disconnected && description.type === 'ReplicaSetWithPrimary') {
390
+ _handleReconnect();
391
+ }
392
+ });
393
+ }
394
+
395
+ conn.onOpen();
396
+
397
+ for (const i in conn.collections) {
398
+ if (utils.object.hasOwnProperty(conn.collections, i)) {
399
+ conn.collections[i].onOpen();
400
+ }
401
+ }
402
+ }
403
+
157
404
 
158
405
  /*!
159
406
  * Module exports.
package/lib/error/cast.js CHANGED
@@ -20,10 +20,9 @@ class CastError extends MongooseError {
20
20
  constructor(type, value, path, reason, schemaType) {
21
21
  // If no args, assume we'll `init()` later.
22
22
  if (arguments.length > 0) {
23
- const stringValue = getStringValue(value);
24
23
  const valueType = getValueType(value);
25
24
  const messageFormat = getMessageFormat(schemaType);
26
- const msg = formatMessage(null, type, stringValue, path, messageFormat, valueType, reason);
25
+ const msg = formatMessage(null, type, value, path, messageFormat, valueType, reason);
27
26
  super(msg);
28
27
  this.init(type, value, path, reason, schemaType);
29
28
  } else {
@@ -77,7 +76,7 @@ class CastError extends MongooseError {
77
76
  */
78
77
  setModel(model) {
79
78
  this.model = model;
80
- this.message = formatMessage(model, this.kind, this.stringValue, this.path,
79
+ this.message = formatMessage(model, this.kind, this.value, this.path,
81
80
  this.messageFormat, this.valueType);
82
81
  }
83
82
  }
@@ -111,10 +110,8 @@ function getValueType(value) {
111
110
  }
112
111
 
113
112
  function getMessageFormat(schemaType) {
114
- const messageFormat = schemaType &&
115
- schemaType.options &&
116
- schemaType.options.cast || null;
117
- if (typeof messageFormat === 'string') {
113
+ const messageFormat = schemaType && schemaType._castErrorMessage || null;
114
+ if (typeof messageFormat === 'string' || typeof messageFormat === 'function') {
118
115
  return messageFormat;
119
116
  }
120
117
  }
@@ -123,8 +120,9 @@ function getMessageFormat(schemaType) {
123
120
  * ignore
124
121
  */
125
122
 
126
- function formatMessage(model, kind, stringValue, path, messageFormat, valueType, reason) {
127
- if (messageFormat != null) {
123
+ function formatMessage(model, kind, value, path, messageFormat, valueType, reason) {
124
+ if (typeof messageFormat === 'string') {
125
+ const stringValue = getStringValue(value);
128
126
  let ret = messageFormat.
129
127
  replace('{KIND}', kind).
130
128
  replace('{VALUE}', stringValue).
@@ -134,7 +132,10 @@ function formatMessage(model, kind, stringValue, path, messageFormat, valueType,
134
132
  }
135
133
 
136
134
  return ret;
135
+ } else if (typeof messageFormat === 'function') {
136
+ return messageFormat(value, path, model, kind);
137
137
  } else {
138
+ const stringValue = getStringValue(value);
138
139
  const valueTypeMsg = valueType ? ' (type ' + valueType + ')' : '';
139
140
  let ret = 'Cast to ' + kind + ' failed for value ' +
140
141
  stringValue + valueTypeMsg + ' at path "' + path + '"';
@@ -6,7 +6,7 @@
6
6
  * const mongoose = require('mongoose');
7
7
  * mongoose.Error.messages.String.enum = "Your custom message for {PATH}.";
8
8
  *
9
- * As you might have noticed, error messages support basic templating
9
+ * Error messages support basic templating. Mongoose will replace the following strings with the corresponding value.
10
10
  *
11
11
  * - `{PATH}` is replaced with the invalid document path
12
12
  * - `{VALUE}` is replaced with the invalid value
@@ -13,7 +13,6 @@ module.exports = function getPath(schema, path) {
13
13
  if (schematype != null) {
14
14
  return schematype;
15
15
  }
16
-
17
16
  const pieces = path.split('.');
18
17
  let cur = '';
19
18
  let isArray = false;
@@ -12,8 +12,8 @@ module.exports = function addIdGetter(schema) {
12
12
  if (!autoIdGetter) {
13
13
  return schema;
14
14
  }
15
-
16
15
  schema.virtual('id').get(idGetter);
16
+ schema.virtual('id').set(idSetter);
17
17
 
18
18
  return schema;
19
19
  };
@@ -30,3 +30,14 @@ function idGetter() {
30
30
 
31
31
  return null;
32
32
  }
33
+
34
+ /**
35
+ *
36
+ * @param {String} v the id to set
37
+ * @api private
38
+ */
39
+
40
+ function idSetter(v) {
41
+ this._id = v;
42
+ return v;
43
+ }
package/lib/model.js CHANGED
@@ -320,7 +320,6 @@ Model.prototype.$__handleSave = function(options, callback) {
320
320
  _setIsNew(this, false);
321
321
  // Make it possible to retry the insert
322
322
  this.$__.inserting = true;
323
-
324
323
  return;
325
324
  }
326
325
 
@@ -479,7 +478,6 @@ function generateVersionError(doc, modifiedPaths) {
479
478
  * newProduct === product; // true
480
479
  *
481
480
  * @param {Object} [options] options optional options
482
- * @param {Boolean} [options.ordered] saves the docs in series rather than parallel.
483
481
  * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.session()).
484
482
  * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead.
485
483
  * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
@@ -1372,6 +1370,14 @@ Model.createCollection = async function createCollection(options) {
1372
1370
  throw new MongooseError('Model.createCollection() no longer accepts a callback');
1373
1371
  }
1374
1372
 
1373
+ const collectionOptions = this &&
1374
+ this.schema &&
1375
+ this.schema.options &&
1376
+ this.schema.options.collectionOptions;
1377
+ if (collectionOptions != null) {
1378
+ options = Object.assign({}, collectionOptions, options);
1379
+ }
1380
+
1375
1381
  const schemaCollation = this &&
1376
1382
  this.schema &&
1377
1383
  this.schema.options &&
@@ -2802,6 +2808,8 @@ Model.findByIdAndRemove = function(id, options) {
2802
2808
  *
2803
2809
  * @param {Array|Object} docs Documents to insert, as a spread or array
2804
2810
  * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. See [Model.save](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) for available options.
2811
+ * @param {Boolean} [options.ordered] saves the docs in series rather than parallel.
2812
+ * @param {Boolean} [options.aggregateErrors] Aggregate Errors instead of throwing the first one that occurs. Default: false
2805
2813
  * @return {Promise}
2806
2814
  * @api public
2807
2815
  */
@@ -2858,27 +2866,41 @@ Model.create = async function create(doc, options) {
2858
2866
  return Array.isArray(doc) ? [] : null;
2859
2867
  }
2860
2868
  let res = [];
2869
+ const immediateError = typeof options.aggregateErrors === 'boolean' ? !options.aggregateErrors : true;
2870
+
2871
+ delete options.aggregateErrors; // dont pass on the option to "$save"
2872
+
2861
2873
  if (options.ordered) {
2862
2874
  for (let i = 0; i < args.length; i++) {
2863
- const doc = args[i];
2864
- const Model = this.discriminators && doc[discriminatorKey] != null ?
2865
- this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2866
- this;
2867
- if (Model == null) {
2868
- throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
2875
+ try {
2876
+ const doc = args[i];
2877
+ const Model = this.discriminators && doc[discriminatorKey] != null ?
2878
+ this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2879
+ this;
2880
+ if (Model == null) {
2881
+ throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
2869
2882
  `found for model "${this.modelName}"`);
2870
- }
2871
- let toSave = doc;
2872
- if (!(toSave instanceof Model)) {
2873
- toSave = new Model(toSave);
2874
- }
2883
+ }
2884
+ let toSave = doc;
2885
+ if (!(toSave instanceof Model)) {
2886
+ toSave = new Model(toSave);
2887
+ }
2875
2888
 
2876
- await toSave.$save(options);
2877
- res.push(toSave);
2889
+ await toSave.$save(options);
2890
+ res.push(toSave);
2891
+ } catch (err) {
2892
+ if (!immediateError) {
2893
+ res.push(err);
2894
+ } else {
2895
+ throw err;
2896
+ }
2897
+ }
2878
2898
  }
2879
2899
  return res;
2880
2900
  } else {
2881
- res = await Promise.all(args.map(async doc => {
2901
+ // ".bind(Promise)" is required, otherwise results in "TypeError: Promise.allSettled called on non-object"
2902
+ const promiseType = !immediateError ? Promise.allSettled.bind(Promise) : Promise.all.bind(Promise);
2903
+ let p = promiseType(args.map(async doc => {
2882
2904
  const Model = this.discriminators && doc[discriminatorKey] != null ?
2883
2905
  this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2884
2906
  this;
@@ -2896,6 +2918,13 @@ Model.create = async function create(doc, options) {
2896
2918
 
2897
2919
  return toSave;
2898
2920
  }));
2921
+
2922
+ // chain the mapper, only if "allSettled" is used
2923
+ if (!immediateError) {
2924
+ p = p.then(presult => presult.map(v => v.status === 'fulfilled' ? v.value : v.reason));
2925
+ }
2926
+
2927
+ res = await p;
2899
2928
  }
2900
2929
 
2901
2930
 
package/lib/query.js CHANGED
@@ -24,6 +24,7 @@ const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminato
24
24
  const hasDollarKeys = require('./helpers/query/hasDollarKeys');
25
25
  const helpers = require('./queryhelpers');
26
26
  const immediate = require('./helpers/immediate');
27
+ const internalToObjectOptions = require('./options').internalToObjectOptions;
27
28
  const isExclusive = require('./helpers/projection/isExclusive');
28
29
  const isInclusive = require('./helpers/projection/isInclusive');
29
30
  const isPathSelectedInclusive = require('./helpers/projection/isPathSelectedInclusive');
@@ -1635,6 +1636,10 @@ Query.prototype.setOptions = function(options, overwrite) {
1635
1636
  delete options.translateAliases;
1636
1637
  }
1637
1638
 
1639
+ if ('rawResult' in options) {
1640
+ printRawResultDeprecationWarning();
1641
+ }
1642
+
1638
1643
  if (options.lean == null && this.schema && 'lean' in this.schema.options) {
1639
1644
  this._mongooseOptions.lean = this.schema.options.lean;
1640
1645
  }
@@ -1669,6 +1674,15 @@ Query.prototype.setOptions = function(options, overwrite) {
1669
1674
  return this;
1670
1675
  };
1671
1676
 
1677
+ /*!
1678
+ * ignore
1679
+ */
1680
+
1681
+ const printRawResultDeprecationWarning = util.deprecate(
1682
+ function printRawResultDeprecationWarning() {},
1683
+ 'The `rawResult` option for Mongoose queries is deprecated. Use `includeResultMetadata: false` as a replacement for `rawResult: true`.'
1684
+ );
1685
+
1672
1686
  /**
1673
1687
  * Sets the [`explain` option](https://www.mongodb.com/docs/manual/reference/method/cursor.explain/),
1674
1688
  * which makes this query return detailed execution stats instead of the actual
@@ -2319,8 +2333,6 @@ Query.prototype.find = function(conditions) {
2319
2333
 
2320
2334
  this.op = 'find';
2321
2335
 
2322
- conditions = utils.toObject(conditions);
2323
-
2324
2336
  if (mquery.canMerge(conditions)) {
2325
2337
  this.merge(conditions);
2326
2338
 
@@ -2392,6 +2404,8 @@ Query.prototype.merge = function(source) {
2392
2404
  utils.merge(this._conditions, { _id: source }, opts);
2393
2405
 
2394
2406
  return this;
2407
+ } else if (source && source.$__) {
2408
+ source = source.toObject(internalToObjectOptions);
2395
2409
  }
2396
2410
 
2397
2411
  opts.omit = {};
@@ -2434,7 +2448,7 @@ Query.prototype.collation = function(value) {
2434
2448
  */
2435
2449
 
2436
2450
  Query.prototype._completeOne = function(doc, res, callback) {
2437
- if (!doc && !this.options.rawResult) {
2451
+ if (!doc && !this.options.rawResult && !this.options.includeResultMetadata) {
2438
2452
  return callback(null, null);
2439
2453
  }
2440
2454
 
@@ -2560,9 +2574,6 @@ Query.prototype.findOne = function(conditions, projection, options) {
2560
2574
  this.op = 'findOne';
2561
2575
  this._validateOp();
2562
2576
 
2563
- // make sure we don't send in the whole Document to merge()
2564
- conditions = utils.toObject(conditions);
2565
-
2566
2577
  if (options) {
2567
2578
  this.setOptions(options);
2568
2579
  }
@@ -2726,8 +2737,6 @@ Query.prototype.count = function(filter) {
2726
2737
  this.op = 'count';
2727
2738
  this._validateOp();
2728
2739
 
2729
- filter = utils.toObject(filter);
2730
-
2731
2740
  if (mquery.canMerge(filter)) {
2732
2741
  this.merge(filter);
2733
2742
  }
@@ -2822,8 +2831,6 @@ Query.prototype.countDocuments = function(conditions, options) {
2822
2831
  this.op = 'countDocuments';
2823
2832
  this._validateOp();
2824
2833
 
2825
- conditions = utils.toObject(conditions);
2826
-
2827
2834
  if (mquery.canMerge(conditions)) {
2828
2835
  this.merge(conditions);
2829
2836
  }
@@ -2886,7 +2893,6 @@ Query.prototype.distinct = function(field, conditions) {
2886
2893
 
2887
2894
  this.op = 'distinct';
2888
2895
  this._validateOp();
2889
- conditions = utils.toObject(conditions);
2890
2896
 
2891
2897
  if (mquery.canMerge(conditions)) {
2892
2898
  this.merge(conditions);
@@ -2981,8 +2987,6 @@ Query.prototype.deleteOne = function deleteOne(filter, options) {
2981
2987
  this.op = 'deleteOne';
2982
2988
  this.setOptions(options);
2983
2989
 
2984
- filter = utils.toObject(filter);
2985
-
2986
2990
  if (mquery.canMerge(filter)) {
2987
2991
  this.merge(filter);
2988
2992
 
@@ -3058,8 +3062,6 @@ Query.prototype.deleteMany = function(filter, options) {
3058
3062
  this.setOptions(options);
3059
3063
  this.op = 'deleteMany';
3060
3064
 
3061
- filter = utils.toObject(filter);
3062
-
3063
3065
  if (mquery.canMerge(filter)) {
3064
3066
  this.merge(filter);
3065
3067
 
@@ -3110,7 +3112,7 @@ Query.prototype._deleteMany = async function _deleteMany() {
3110
3112
  */
3111
3113
 
3112
3114
  function completeOne(model, doc, res, options, fields, userProvidedFields, pop, callback) {
3113
- if (options.rawResult && doc == null) {
3115
+ if ((options.rawResult || options.includeResultMetadata) && doc == null) {
3114
3116
  _init(null);
3115
3117
  return null;
3116
3118
  }
@@ -3123,7 +3125,7 @@ function completeOne(model, doc, res, options, fields, userProvidedFields, pop,
3123
3125
  }
3124
3126
 
3125
3127
 
3126
- if (options.rawResult) {
3128
+ if (options.rawResult || options.includeResultMetadata) {
3127
3129
  if (doc && casted) {
3128
3130
  if (options.session != null) {
3129
3131
  casted.$session(options.session);
@@ -3298,6 +3300,10 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
3298
3300
  applyGlobalMaxTimeMS(this.options, this.model);
3299
3301
  applyGlobalDiskUse(this.options, this.model);
3300
3302
 
3303
+ if (this.options.rawResult && this.options.includeResultMetadata === false) {
3304
+ throw new MongooseError('Cannot set `rawResult` option when `includeResultMetadata` is false');
3305
+ }
3306
+
3301
3307
  if ('strict' in this.options) {
3302
3308
  this._mongooseOptions.strict = this.options.strict;
3303
3309
  }
@@ -3348,7 +3354,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
3348
3354
  for (const fn of this._transforms) {
3349
3355
  res = fn(res);
3350
3356
  }
3351
- const doc = res.value;
3357
+ const doc = options.includeResultMetadata === false ? res : res.value;
3352
3358
 
3353
3359
  return new Promise((resolve, reject) => {
3354
3360
  this._completeOne(doc, res, _wrapThunkCallback(this, (err, res) => {
@@ -3489,6 +3495,11 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
3489
3495
  throw this.error();
3490
3496
  }
3491
3497
 
3498
+ const includeResultMetadata = this.options.includeResultMetadata;
3499
+ if (this.options.rawResult && includeResultMetadata === false) {
3500
+ throw new MongooseError('Cannot set `rawResult` option when `includeResultMetadata` is false');
3501
+ }
3502
+
3492
3503
  const filter = this._conditions;
3493
3504
  const options = this._optionsForExec(this.model);
3494
3505
  this._applyTranslateAliases(options);
@@ -3497,7 +3508,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
3497
3508
  for (const fn of this._transforms) {
3498
3509
  res = fn(res);
3499
3510
  }
3500
- const doc = res.value;
3511
+ const doc = includeResultMetadata === false ? res : res.value;
3501
3512
 
3502
3513
  return new Promise((resolve, reject) => {
3503
3514
  this._completeOne(doc, res, _wrapThunkCallback(this, (err, res) => {
@@ -3624,6 +3635,11 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
3624
3635
  this._applyTranslateAliases(options);
3625
3636
  convertNewToReturnDocument(options);
3626
3637
 
3638
+ const includeResultMetadata = this.options.includeResultMetadata;
3639
+ if (this.options.rawResult && includeResultMetadata === false) {
3640
+ throw new MongooseError('Cannot set `rawResult` option when `includeResultMetadata` is false');
3641
+ }
3642
+
3627
3643
  const modelOpts = { skipId: true };
3628
3644
  if ('strict' in this._mongooseOptions) {
3629
3645
  modelOpts.strict = this._mongooseOptions.strict;
@@ -3654,7 +3670,7 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
3654
3670
  res = fn(res);
3655
3671
  }
3656
3672
 
3657
- const doc = res.value;
3673
+ const doc = includeResultMetadata === false ? res : res.value;
3658
3674
  return new Promise((resolve, reject) => {
3659
3675
  this._completeOne(doc, res, _wrapThunkCallback(this, (err, res) => {
3660
3676
  if (err) {
@@ -4168,7 +4184,6 @@ function _update(query, op, filter, doc, options, callback) {
4168
4184
  // make sure we don't send in the whole Document to merge()
4169
4185
  query.op = op;
4170
4186
  query._validateOp();
4171
- filter = utils.toObject(filter);
4172
4187
  doc = doc || {};
4173
4188
 
4174
4189
  // strict is an option used in the update checking, make sure it gets set