mongoose 8.9.6 → 8.10.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.
package/lib/aggregate.js CHANGED
@@ -13,6 +13,7 @@ const getConstructorName = require('./helpers/getConstructorName');
13
13
  const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
14
14
  const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators');
15
15
  const utils = require('./utils');
16
+ const { modelSymbol } = require('./helpers/symbols');
16
17
  const read = Query.prototype.read;
17
18
  const readConcern = Query.prototype.readConcern;
18
19
 
@@ -46,13 +47,17 @@ const validRedactStringValues = new Set(['$$DESCEND', '$$PRUNE', '$$KEEP']);
46
47
  * @see MongoDB https://www.mongodb.com/docs/manual/applications/aggregation/
47
48
  * @see driver https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#aggregate
48
49
  * @param {Array} [pipeline] aggregation pipeline as an array of objects
49
- * @param {Model} [model] the model to use with this aggregate.
50
+ * @param {Model|Connection} [modelOrConn] the model or connection to use with this aggregate.
50
51
  * @api public
51
52
  */
52
53
 
53
- function Aggregate(pipeline, model) {
54
+ function Aggregate(pipeline, modelOrConn) {
54
55
  this._pipeline = [];
55
- this._model = model;
56
+ if (modelOrConn == null || modelOrConn[modelSymbol]) {
57
+ this._model = modelOrConn;
58
+ } else {
59
+ this._connection = modelOrConn;
60
+ }
56
61
  this.options = {};
57
62
 
58
63
  if (arguments.length === 1 && Array.isArray(pipeline)) {
@@ -1041,12 +1046,24 @@ Aggregate.prototype.pipeline = function() {
1041
1046
  */
1042
1047
 
1043
1048
  Aggregate.prototype.exec = async function exec() {
1044
- if (!this._model) {
1049
+ if (!this._model && !this._connection) {
1045
1050
  throw new Error('Aggregate not bound to any Model');
1046
1051
  }
1047
1052
  if (typeof arguments[0] === 'function') {
1048
1053
  throw new MongooseError('Aggregate.prototype.exec() no longer accepts a callback');
1049
1054
  }
1055
+
1056
+ if (this._connection) {
1057
+ if (!this._pipeline.length) {
1058
+ throw new MongooseError('Aggregate has empty pipeline');
1059
+ }
1060
+
1061
+ this._optionsForExec();
1062
+
1063
+ const cursor = await this._connection.client.db().aggregate(this._pipeline, this.options);
1064
+ return await cursor.toArray();
1065
+ }
1066
+
1050
1067
  const model = this._model;
1051
1068
  const collection = this._model.collection;
1052
1069
 
package/lib/collection.js CHANGED
@@ -29,7 +29,7 @@ function Collection(name, conn, opts) {
29
29
  this.collectionName = name;
30
30
  this.conn = conn;
31
31
  this.queue = [];
32
- this.buffer = true;
32
+ this.buffer = !conn?._hasOpened;
33
33
  this.emitter = new EventEmitter();
34
34
 
35
35
  if (STATES.connected === this.conn.readyState) {
@@ -311,13 +311,7 @@ Collection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
311
311
  if (opts && opts.schemaUserProvidedOptions != null && opts.schemaUserProvidedOptions.bufferTimeoutMS != null) {
312
312
  return opts.schemaUserProvidedOptions.bufferTimeoutMS;
313
313
  }
314
- if (conn.config.bufferTimeoutMS != null) {
315
- return conn.config.bufferTimeoutMS;
316
- }
317
- if (conn.base != null && conn.base.get('bufferTimeoutMS') != null) {
318
- return conn.base.get('bufferTimeoutMS');
319
- }
320
- return 10000;
314
+ return conn._getBufferTimeoutMS();
321
315
  };
322
316
 
323
317
  /*!
package/lib/connection.js CHANGED
@@ -824,12 +824,56 @@ Connection.prototype.dropCollection = async function dropCollection(collection)
824
824
 
825
825
  Connection.prototype._waitForConnect = async function _waitForConnect() {
826
826
  if ((this.readyState === STATES.connecting || this.readyState === STATES.disconnected) && this._shouldBufferCommands()) {
827
- await new Promise(resolve => {
828
- this._queue.push({ fn: resolve });
829
- });
827
+ const bufferTimeoutMS = this._getBufferTimeoutMS();
828
+ let timeout = null;
829
+ let timedOut = false;
830
+ // The element that this function pushes onto `_queue`, stored to make it easy to remove later
831
+ const queueElement = {};
832
+ await Promise.race([
833
+ new Promise(resolve => {
834
+ queueElement.fn = resolve;
835
+ this._queue.push(queueElement);
836
+ }),
837
+ new Promise(resolve => {
838
+ timeout = setTimeout(
839
+ () => {
840
+ timedOut = true;
841
+ resolve();
842
+ },
843
+ bufferTimeoutMS
844
+ );
845
+ })
846
+ ]);
847
+
848
+ if (timedOut) {
849
+ const index = this._queue.indexOf(queueElement);
850
+ if (index !== -1) {
851
+ this._queue.splice(index, 1);
852
+ }
853
+ const message = 'Connection operation buffering timed out after ' + bufferTimeoutMS + 'ms';
854
+ throw new MongooseError(message);
855
+ } else if (timeout != null) {
856
+ // Not strictly necessary, but avoid the extra overhead of creating a new MongooseError
857
+ // in case of success
858
+ clearTimeout(timeout);
859
+ }
830
860
  }
831
861
  };
832
862
 
863
+ /*!
864
+ * Get the default buffer timeout for this connection
865
+ */
866
+
867
+ Connection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
868
+ if (this.config.bufferTimeoutMS != null) {
869
+ return this.config.bufferTimeoutMS;
870
+ }
871
+ if (this.base != null && this.base.get('bufferTimeoutMS') != null) {
872
+ return this.base.get('bufferTimeoutMS');
873
+ }
874
+ return 10000;
875
+ };
876
+
833
877
  /**
834
878
  * Helper for MongoDB Node driver's `listCollections()`.
835
879
  * Returns an array of collection objects.
@@ -1156,6 +1200,10 @@ Connection.prototype.close = async function close(force) {
1156
1200
  this.$wasForceClosed = !!force;
1157
1201
  }
1158
1202
 
1203
+ if (this._lastHeartbeatAt != null) {
1204
+ this._lastHeartbeatAt = null;
1205
+ }
1206
+
1159
1207
  for (const model of Object.values(this.models)) {
1160
1208
  // If manually disconnecting, make sure to clear each model's `$init`
1161
1209
  // promise, so Mongoose knows to re-run `init()` in case the
@@ -1742,6 +1790,18 @@ Connection.prototype.syncIndexes = async function syncIndexes(options = {}) {
1742
1790
  * @api public
1743
1791
  */
1744
1792
 
1793
+ /**
1794
+ * Runs a [db-level aggregate()](https://www.mongodb.com/docs/manual/reference/method/db.aggregate/) on this connection's underlying `db`
1795
+ *
1796
+ * @method aggregate
1797
+ * @memberOf Connection
1798
+ * @param {Array} pipeline
1799
+ * @param {Object} [options]
1800
+ * @param {Boolean} [options.cursor=false] If true, make the Aggregate resolve to a Mongoose AggregationCursor rather than an array
1801
+ * @return {Aggregate} Aggregation wrapper
1802
+ * @api public
1803
+ */
1804
+
1745
1805
  /**
1746
1806
  * Removes the database connection with the given name created with with `useDb()`.
1747
1807
  *
@@ -41,11 +41,17 @@ function AggregationCursor(agg) {
41
41
  this.cursor = null;
42
42
  this.agg = agg;
43
43
  this._transforms = [];
44
+ const connection = agg._connection;
44
45
  const model = agg._model;
45
46
  delete agg.options.cursor.useMongooseAggCursor;
46
47
  this._mongooseOptions = {};
47
48
 
48
- _init(model, this, agg);
49
+ if (connection) {
50
+ this.cursor = connection.db.aggregate(agg._pipeline, agg.options || {});
51
+ setImmediate(() => this.emit('cursor', this.cursor));
52
+ } else {
53
+ _init(model, this, agg);
54
+ }
49
55
  }
50
56
 
51
57
  util.inherits(AggregationCursor, Readable);
package/lib/document.js CHANGED
@@ -807,8 +807,8 @@ function init(self, obj, doc, opts, prefix) {
807
807
  reason: e
808
808
  }));
809
809
  }
810
- } else if (opts.hydratedPopulatedDocs) {
811
- doc[i] = schemaType.cast(value, self, true);
810
+ } else if (schemaType && opts.hydratedPopulatedDocs) {
811
+ doc[i] = schemaType.cast(value, self, true, undefined, { hydratedPopulatedDocs: true });
812
812
 
813
813
  if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
814
814
  self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
@@ -4069,7 +4069,7 @@ Document.prototype.$__toObjectShallow = function $__toObjectShallow() {
4069
4069
  * @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`.
4070
4070
  * @param {Boolean} [options.flattenObjectIds=false] if true, convert any ObjectIds in the result to 24 character hex strings.
4071
4071
  * @param {Boolean} [options.useProjection=false] - If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema.
4072
- * @return {Object} js object (not a POJO)
4072
+ * @return {Object} document as a plain old JavaScript object (POJO). This object may contain ObjectIds, Maps, Dates, mongodb.Binary, Buffers, and other non-POJO values.
4073
4073
  * @see mongodb.Binary https://mongodb.github.io/node-mongodb-native/4.9/classes/Binary.html
4074
4074
  * @api public
4075
4075
  * @memberOf Document
@@ -4256,24 +4256,25 @@ function applySchemaTypeTransforms(self, json) {
4256
4256
 
4257
4257
  for (const path of paths) {
4258
4258
  const schematype = schema.paths[path];
4259
- if (typeof schematype.options.transform === 'function') {
4259
+ const topLevelTransformFunction = schematype.options.transform ?? schematype.constructor?.defaultOptions?.transform;
4260
+ const embeddedSchemaTypeTransformFunction = schematype.$embeddedSchemaType?.options?.transform
4261
+ ?? schematype.$embeddedSchemaType?.constructor?.defaultOptions?.transform;
4262
+ if (typeof topLevelTransformFunction === 'function') {
4260
4263
  const val = self.$get(path);
4261
4264
  if (val === undefined) {
4262
4265
  continue;
4263
4266
  }
4264
- const transformedValue = schematype.options.transform.call(self, val);
4267
+ const transformedValue = topLevelTransformFunction.call(self, val);
4265
4268
  throwErrorIfPromise(path, transformedValue);
4266
4269
  utils.setValue(path, transformedValue, json);
4267
- } else if (schematype.$embeddedSchemaType != null &&
4268
- typeof schematype.$embeddedSchemaType.options.transform === 'function') {
4270
+ } else if (typeof embeddedSchemaTypeTransformFunction === 'function') {
4269
4271
  const val = self.$get(path);
4270
4272
  if (val === undefined) {
4271
4273
  continue;
4272
4274
  }
4273
4275
  const vals = [].concat(val);
4274
- const transform = schematype.$embeddedSchemaType.options.transform;
4275
4276
  for (let i = 0; i < vals.length; ++i) {
4276
- const transformedValue = transform.call(self, vals[i]);
4277
+ const transformedValue = embeddedSchemaTypeTransformFunction.call(self, vals[i]);
4277
4278
  vals[i] = transformedValue;
4278
4279
  throwErrorIfPromise(path, transformedValue);
4279
4280
  }
@@ -132,6 +132,17 @@ NativeConnection.prototype.useDb = function(name, options) {
132
132
  return newConn;
133
133
  };
134
134
 
135
+ /**
136
+ * Runs a [db-level aggregate()](https://www.mongodb.com/docs/manual/reference/method/db.aggregate/) on this connection's underlying `db`
137
+ *
138
+ * @param {Array} pipeline
139
+ * @param {Object} [options]
140
+ */
141
+
142
+ NativeConnection.prototype.aggregate = function aggregate(pipeline, options) {
143
+ return new this.base.Aggregate(null, this).append(pipeline).option(options ?? {});
144
+ };
145
+
135
146
  /**
136
147
  * Removes the database connection with the given name created with `useDb()`.
137
148
  *
@@ -270,6 +281,11 @@ NativeConnection.prototype.createClient = async function createClient(uri, optio
270
281
  delete options.autoSearchIndex;
271
282
  }
272
283
 
284
+ if ('bufferTimeoutMS' in options) {
285
+ this.config.bufferTimeoutMS = options.bufferTimeoutMS;
286
+ delete options.bufferTimeoutMS;
287
+ }
288
+
273
289
  // Backwards compat
274
290
  if (options.user || options.pass) {
275
291
  options.auth = options.auth || {};
@@ -415,6 +431,9 @@ function _setClient(conn, client, options, dbName) {
415
431
  }
416
432
  });
417
433
  }
434
+
435
+ conn._lastHeartbeatAt = null;
436
+
418
437
  client.on('serverHeartbeatSucceeded', () => {
419
438
  conn._lastHeartbeatAt = Date.now();
420
439
  });
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Handles creating `{ type: 'object' }` vs `{ bsonType: 'object' }` vs `{ bsonType: ['object', 'null'] }`
5
+ *
6
+ * @param {String} type
7
+ * @param {String} bsonType
8
+ * @param {Boolean} useBsonType
9
+ * @param {Boolean} isRequired
10
+ */
11
+
12
+ module.exports = function createJSONSchemaTypeArray(type, bsonType, useBsonType, isRequired) {
13
+ if (useBsonType) {
14
+ if (isRequired) {
15
+ return { bsonType };
16
+ }
17
+ return { bsonType: [bsonType, 'null'] };
18
+ } else {
19
+ if (isRequired) {
20
+ return { type };
21
+ }
22
+ return { type: [type, 'null'] };
23
+ }
24
+ };
@@ -19,6 +19,9 @@ module.exports = function applyDefaults(doc, fields, exclude, hasIncludedChildre
19
19
  const type = doc.$__schema.paths[p];
20
20
  const path = type.splitPath();
21
21
  const len = path.length;
22
+ if (path[len - 1] === '$*') {
23
+ continue;
24
+ }
22
25
  let included = false;
23
26
  let doc_ = doc._doc;
24
27
  for (let j = 0; j < len; ++j) {
package/lib/model.js CHANGED
@@ -64,7 +64,6 @@ const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscrim
64
64
  const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
65
65
  const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
66
66
  const setDottedPath = require('./helpers/path/setDottedPath');
67
- const STATES = require('./connectionState');
68
67
  const util = require('util');
69
68
  const utils = require('./utils');
70
69
  const minimize = require('./helpers/minimize');
@@ -151,6 +150,62 @@ Model.prototype.$isMongooseModelPrototype = true;
151
150
 
152
151
  Model.prototype.db;
153
152
 
153
+ /**
154
+ * Changes the Connection instance this model uses to make requests to MongoDB.
155
+ * This function is most useful for changing the Connection that a Model defined using `mongoose.model()` uses
156
+ * after initialization.
157
+ *
158
+ * #### Example:
159
+ *
160
+ * await mongoose.connect('mongodb://127.0.0.1:27017/db1');
161
+ * const UserModel = mongoose.model('User', mongoose.Schema({ name: String }));
162
+ * UserModel.connection === mongoose.connection; // true
163
+ *
164
+ * const conn2 = await mongoose.createConnection('mongodb://127.0.0.1:27017/db2').asPromise();
165
+ * UserModel.useConnection(conn2); // `UserModel` now stores documents in `db2`, not `db1`
166
+ *
167
+ * UserModel.connection === mongoose.connection; // false
168
+ * UserModel.connection === conn2; // true
169
+ *
170
+ * conn2.model('User') === UserModel; // true
171
+ * mongoose.model('User'); // Throws 'MissingSchemaError'
172
+ *
173
+ * Note: `useConnection()` does **not** apply any [connection-level plugins](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.plugin()) from the new connection.
174
+ * If you use `useConnection()` to switch a model's connection, the model will still have the old connection's plugins.
175
+ *
176
+ * @function useConnection
177
+ * @param [Connection] connection The new connection to use
178
+ * @return [Model] this
179
+ * @api public
180
+ */
181
+
182
+ Model.useConnection = function useConnection(connection) {
183
+ if (!connection) {
184
+ throw new Error('Please provide a connection.');
185
+ }
186
+ if (this.db) {
187
+ delete this.db.models[this.modelName];
188
+ delete this.prototype.db;
189
+ delete this.prototype[modelDbSymbol];
190
+ delete this.prototype.collection;
191
+ delete this.prototype.$collection;
192
+ delete this.prototype[modelCollectionSymbol];
193
+ }
194
+
195
+ this.db = connection;
196
+ const collection = connection.collection(this.modelName, connection.options);
197
+ this.prototype.collection = collection;
198
+ this.prototype.$collection = collection;
199
+ this.prototype[modelCollectionSymbol] = collection;
200
+ this.prototype.db = connection;
201
+ this.prototype[modelDbSymbol] = connection;
202
+ this.collection = collection;
203
+ this.$__collection = collection;
204
+ connection.models[this.modelName] = this;
205
+
206
+ return this;
207
+ };
208
+
154
209
  /**
155
210
  * The collection instance this model uses.
156
211
  * A Mongoose collection is a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)).
@@ -1048,11 +1103,7 @@ Model.init = function init() {
1048
1103
  return results;
1049
1104
  };
1050
1105
  const _createCollection = async() => {
1051
- if ((conn.readyState === STATES.connecting || conn.readyState === STATES.disconnected) && conn._shouldBufferCommands()) {
1052
- await new Promise(resolve => {
1053
- conn._queue.push({ fn: resolve });
1054
- });
1055
- }
1106
+ await conn._waitForConnect();
1056
1107
  const autoCreate = utils.getOption(
1057
1108
  'autoCreate',
1058
1109
  this.schema.options,
@@ -1246,19 +1297,21 @@ Model.syncIndexes = async function syncIndexes(options) {
1246
1297
  throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
1247
1298
  }
1248
1299
 
1249
- const model = this;
1300
+ const autoCreate = options?.autoCreate ?? this.schema.options?.autoCreate ?? this.db.config.autoCreate ?? true;
1250
1301
 
1251
- try {
1252
- await model.createCollection();
1253
- } catch (err) {
1254
- if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
1255
- throw err;
1302
+ if (autoCreate) {
1303
+ try {
1304
+ await this.createCollection();
1305
+ } catch (err) {
1306
+ if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
1307
+ throw err;
1308
+ }
1256
1309
  }
1257
1310
  }
1258
1311
 
1259
- const diffIndexesResult = await model.diffIndexes();
1260
- const dropped = await model.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
1261
- await model.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
1312
+ const diffIndexesResult = await this.diffIndexes({ indexOptionsToCreate: true });
1313
+ const dropped = await this.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
1314
+ await this.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
1262
1315
 
1263
1316
  return dropped;
1264
1317
  };
@@ -1361,13 +1414,14 @@ Model.listSearchIndexes = async function listSearchIndexes(options) {
1361
1414
  *
1362
1415
  * const { toDrop, toCreate } = await Model.diffIndexes();
1363
1416
  * toDrop; // Array of strings containing names of indexes that `syncIndexes()` will drop
1364
- * toCreate; // Array of strings containing names of indexes that `syncIndexes()` will create
1417
+ * toCreate; // Array of index specs containing the keys of indexes that `syncIndexes()` will create
1365
1418
  *
1366
1419
  * @param {Object} [options]
1420
+ * @param {Boolean} [options.indexOptionsToCreate=false] If true, `toCreate` will include both the index spec and the index options, not just the index spec
1367
1421
  * @return {Promise<Object>} contains the indexes that would be dropped in MongoDB and indexes that would be created in MongoDB as `{ toDrop: string[], toCreate: string[] }`.
1368
1422
  */
1369
1423
 
1370
- Model.diffIndexes = async function diffIndexes() {
1424
+ Model.diffIndexes = async function diffIndexes(options) {
1371
1425
  if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
1372
1426
  throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
1373
1427
  }
@@ -1389,13 +1443,14 @@ Model.diffIndexes = async function diffIndexes() {
1389
1443
  const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes());
1390
1444
 
1391
1445
  const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes);
1392
- const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop);
1446
+ const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options);
1393
1447
 
1394
1448
  return { toDrop, toCreate };
1395
1449
  };
1396
1450
 
1397
- function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
1451
+ function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options) {
1398
1452
  const toCreate = [];
1453
+ const indexOptionsToCreate = options?.indexOptionsToCreate ?? false;
1399
1454
 
1400
1455
  for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
1401
1456
  let found = false;
@@ -1416,7 +1471,11 @@ function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
1416
1471
  }
1417
1472
 
1418
1473
  if (!found) {
1419
- toCreate.push(schemaIndexKeysObject);
1474
+ if (indexOptionsToCreate) {
1475
+ toCreate.push([schemaIndexKeysObject, schemaIndexOptions]);
1476
+ } else {
1477
+ toCreate.push(schemaIndexKeysObject);
1478
+ }
1420
1479
  }
1421
1480
  }
1422
1481
 
@@ -1465,7 +1524,7 @@ function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
1465
1524
  * @param {Object} [options]
1466
1525
  * @param {Array<String>} [options.toDrop] if specified, contains a list of index names to drop
1467
1526
  * @param {Boolean} [options.hideIndexes=false] set to `true` to hide indexes instead of dropping. Requires MongoDB server 4.4 or higher
1468
- * @return {Promise<String>} list of dropped or hidden index names
1527
+ * @return {Promise<Array<String>>} list of dropped or hidden index names
1469
1528
  * @api public
1470
1529
  */
1471
1530
 
@@ -1597,7 +1656,7 @@ Model.createIndexes = async function createIndexes(options) {
1597
1656
  */
1598
1657
 
1599
1658
  function _ensureIndexes(model, options, callback) {
1600
- const indexes = model.schema.indexes();
1659
+ const indexes = Array.isArray(options?.toCreate) ? options.toCreate : model.schema.indexes();
1601
1660
  let indexError;
1602
1661
 
1603
1662
  options = options || {};
@@ -1681,12 +1740,6 @@ function _ensureIndexes(model, options, callback) {
1681
1740
  indexOptions.background = options.background;
1682
1741
  }
1683
1742
 
1684
- if ('toCreate' in options) {
1685
- if (options.toCreate.length === 0) {
1686
- return done();
1687
- }
1688
- }
1689
-
1690
1743
  // Just in case `createIndex()` throws a sync error
1691
1744
  let promise = null;
1692
1745
  try {
@@ -2115,9 +2168,8 @@ Model.estimatedDocumentCount = function estimatedDocumentCount(options) {
2115
2168
  *
2116
2169
  * #### Example:
2117
2170
  *
2118
- * Adventure.countDocuments({ type: 'jungle' }, function (err, count) {
2119
- * console.log('there are %d jungle adventures', count);
2120
- * });
2171
+ * const count = await Adventure.countDocuments({ type: 'jungle' });
2172
+ * console.log('there are %d jungle adventures', count);
2121
2173
  *
2122
2174
  * If you want to count all documents in a large collection,
2123
2175
  * use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount())
@@ -2627,6 +2679,10 @@ Model.create = async function create(doc, options) {
2627
2679
 
2628
2680
  delete options.aggregateErrors; // dont pass on the option to "$save"
2629
2681
 
2682
+ if (options.session && !options.ordered && args.length > 1) {
2683
+ throw new MongooseError('Cannot call `create()` with a session and multiple documents unless `ordered: true` is set');
2684
+ }
2685
+
2630
2686
  if (options.ordered) {
2631
2687
  for (let i = 0; i < args.length; i++) {
2632
2688
  try {
@@ -2713,6 +2769,49 @@ Model.create = async function create(doc, options) {
2713
2769
  return res;
2714
2770
  };
2715
2771
 
2772
+ /**
2773
+ * Shortcut for saving one document to the database.
2774
+ * `MyModel.insertOne(obj, options)` is almost equivalent to `new MyModel(obj).save(options)`.
2775
+ * The difference is that `insertOne()` checks if `obj` is already a document, and checks for discriminators.
2776
+ *
2777
+ * This function triggers the following middleware.
2778
+ *
2779
+ * - `save()`
2780
+ *
2781
+ * #### Example:
2782
+ *
2783
+ * // Insert one new `Character` document
2784
+ * const character = await Character.insertOne({ name: 'Jean-Luc Picard' });
2785
+ * character.name; // 'Jean-Luc Picard'
2786
+ *
2787
+ * // Create a new character within a transaction.
2788
+ * await Character.insertOne({ name: 'Jean-Luc Picard' }, { session });
2789
+ *
2790
+ * @param {Object|Document} doc Document to insert, as a POJO or Mongoose document
2791
+ * @param {Object} [options] Options passed down to `save()`.
2792
+ * @return {Promise<Document>} resolves to the saved document
2793
+ * @api public
2794
+ */
2795
+
2796
+ Model.insertOne = async function insertOne(doc, options) {
2797
+ _checkContext(this, 'insertOne');
2798
+
2799
+ const discriminatorKey = this.schema.options.discriminatorKey;
2800
+ const Model = this.discriminators && doc[discriminatorKey] != null ?
2801
+ this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2802
+ this;
2803
+ if (Model == null) {
2804
+ throw new MongooseError(
2805
+ `Discriminator "${doc[discriminatorKey]}" not found for model "${this.modelName}"`
2806
+ );
2807
+ }
2808
+ if (!(doc instanceof Model)) {
2809
+ doc = new Model(doc);
2810
+ }
2811
+
2812
+ return await doc.$save(options);
2813
+ };
2814
+
2716
2815
  /**
2717
2816
  * _Requires a replica set running MongoDB >= 3.6.0._ Watches the
2718
2817
  * underlying collection for changes using
@@ -3878,6 +3977,10 @@ Model.hydrate = function(obj, projection, options) {
3878
3977
  * res.upsertedId; // null or an id containing a document that had to be upserted.
3879
3978
  * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
3880
3979
  *
3980
+ * // Other supported syntaxes
3981
+ * await Person.find({ name: /Stark$/ }).updateMany({ isDeleted: true }); // Using chaining syntax
3982
+ * await Person.find().updateMany({ isDeleted: true }); // Set `isDeleted` on _all_ Person documents
3983
+ *
3881
3984
  * This function triggers the following middleware.
3882
3985
  *
3883
3986
  * - `updateMany()`
@@ -3898,10 +4001,14 @@ Model.hydrate = function(obj, projection, options) {
3898
4001
  * @api public
3899
4002
  */
3900
4003
 
3901
- Model.updateMany = function updateMany(conditions, doc, options) {
4004
+ Model.updateMany = function updateMany(conditions, update, options) {
3902
4005
  _checkContext(this, 'updateMany');
3903
4006
 
3904
- return _update(this, 'updateMany', conditions, doc, options);
4007
+ if (update == null) {
4008
+ throw new MongooseError('updateMany `update` parameter cannot be nullish');
4009
+ }
4010
+
4011
+ return _update(this, 'updateMany', conditions, update, options);
3905
4012
  };
3906
4013
 
3907
4014
  /**
@@ -3918,6 +4025,10 @@ Model.updateMany = function updateMany(conditions, doc, options) {
3918
4025
  * res.upsertedId; // null or an id containing a document that had to be upserted.
3919
4026
  * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
3920
4027
  *
4028
+ * // Other supported syntaxes
4029
+ * await Person.findOne({ name: 'Jean-Luc Picard' }).updateOne({ ship: 'USS Enterprise' }); // Using chaining syntax
4030
+ * await Person.updateOne({ ship: 'USS Enterprise' }); // Updates first doc's `ship` property
4031
+ *
3921
4032
  * This function triggers the following middleware.
3922
4033
  *
3923
4034
  * - `updateOne()`
package/lib/query.js CHANGED
@@ -3992,6 +3992,10 @@ Query.prototype._replaceOne = async function _replaceOne() {
3992
3992
  * res.n; // Number of documents matched
3993
3993
  * res.nModified; // Number of documents modified
3994
3994
  *
3995
+ * // Other supported syntaxes
3996
+ * await Person.find({ name: /Stark$/ }).updateMany({ isDeleted: true }); // Using chaining syntax
3997
+ * await Person.find().updateMany({ isDeleted: true }); // Set `isDeleted` on _all_ Person documents
3998
+ *
3995
3999
  * This function triggers the following middleware.
3996
4000
  *
3997
4001
  * - `updateMany()`
@@ -4062,6 +4066,10 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
4062
4066
  * res.upsertedCount; // Number of documents that were upserted
4063
4067
  * res.upsertedId; // Identifier of the inserted document (if an upsert took place)
4064
4068
  *
4069
+ * // Other supported syntaxes
4070
+ * await Person.findOne({ name: 'Jean-Luc Picard' }).updateOne({ ship: 'USS Enterprise' }); // Using chaining syntax
4071
+ * await Person.updateOne({ ship: 'USS Enterprise' }); // Updates first doc's `ship` property
4072
+ *
4065
4073
  * This function triggers the following middleware.
4066
4074
  *
4067
4075
  * - `updateOne()`