mongoose 8.9.7 → 8.10.1

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
@@ -818,18 +818,71 @@ Connection.prototype.dropCollection = async function dropCollection(collection)
818
818
  /**
819
819
  * Waits for connection to be established, so the connection has a `client`
820
820
  *
821
+ * @param {Boolean} [noTimeout=false] if set, don't put a timeout on the operation. Used internally so `mongoose.model()` doesn't leave open handles.
821
822
  * @return Promise
822
823
  * @api private
823
824
  */
824
825
 
825
- Connection.prototype._waitForConnect = async function _waitForConnect() {
826
+ Connection.prototype._waitForConnect = async function _waitForConnect(noTimeout) {
826
827
  if ((this.readyState === STATES.connecting || this.readyState === STATES.disconnected) && this._shouldBufferCommands()) {
827
- await new Promise(resolve => {
828
- this._queue.push({ fn: resolve });
828
+ const bufferTimeoutMS = this._getBufferTimeoutMS();
829
+ let timeout = null;
830
+ let timedOut = false;
831
+ // The element that this function pushes onto `_queue`, stored to make it easy to remove later
832
+ const queueElement = {};
833
+
834
+ // Mongoose executes all elements in `_queue` when initial connection succeeds in `onOpen()`.
835
+ const waitForConnectPromise = new Promise(resolve => {
836
+ queueElement.fn = resolve;
837
+ this._queue.push(queueElement);
829
838
  });
839
+
840
+ if (noTimeout) {
841
+ await waitForConnectPromise;
842
+ } else {
843
+ await Promise.race([
844
+ waitForConnectPromise,
845
+ new Promise(resolve => {
846
+ timeout = setTimeout(
847
+ () => {
848
+ timedOut = true;
849
+ resolve();
850
+ },
851
+ bufferTimeoutMS
852
+ );
853
+ })
854
+ ]);
855
+ }
856
+
857
+ if (timedOut) {
858
+ const index = this._queue.indexOf(queueElement);
859
+ if (index !== -1) {
860
+ this._queue.splice(index, 1);
861
+ }
862
+ const message = 'Connection operation buffering timed out after ' + bufferTimeoutMS + 'ms';
863
+ throw new MongooseError(message);
864
+ } else if (timeout != null) {
865
+ // Not strictly necessary, but avoid the extra overhead of creating a new MongooseError
866
+ // in case of success
867
+ clearTimeout(timeout);
868
+ }
830
869
  }
831
870
  };
832
871
 
872
+ /*!
873
+ * Get the default buffer timeout for this connection
874
+ */
875
+
876
+ Connection.prototype._getBufferTimeoutMS = function _getBufferTimeoutMS() {
877
+ if (this.config.bufferTimeoutMS != null) {
878
+ return this.config.bufferTimeoutMS;
879
+ }
880
+ if (this.base != null && this.base.get('bufferTimeoutMS') != null) {
881
+ return this.base.get('bufferTimeoutMS');
882
+ }
883
+ return 10000;
884
+ };
885
+
833
886
  /**
834
887
  * Helper for MongoDB Node driver's `listCollections()`.
835
888
  * Returns an array of collection objects.
@@ -1156,6 +1209,10 @@ Connection.prototype.close = async function close(force) {
1156
1209
  this.$wasForceClosed = !!force;
1157
1210
  }
1158
1211
 
1212
+ if (this._lastHeartbeatAt != null) {
1213
+ this._lastHeartbeatAt = null;
1214
+ }
1215
+
1159
1216
  for (const model of Object.values(this.models)) {
1160
1217
  // If manually disconnecting, make sure to clear each model's `$init`
1161
1218
  // promise, so Mongoose knows to re-run `init()` in case the
@@ -1742,6 +1799,18 @@ Connection.prototype.syncIndexes = async function syncIndexes(options = {}) {
1742
1799
  * @api public
1743
1800
  */
1744
1801
 
1802
+ /**
1803
+ * Runs a [db-level aggregate()](https://www.mongodb.com/docs/manual/reference/method/db.aggregate/) on this connection's underlying `db`
1804
+ *
1805
+ * @method aggregate
1806
+ * @memberOf Connection
1807
+ * @param {Array} pipeline
1808
+ * @param {Object} [options]
1809
+ * @param {Boolean} [options.cursor=false] If true, make the Aggregate resolve to a Mongoose AggregationCursor rather than an array
1810
+ * @return {Aggregate} Aggregation wrapper
1811
+ * @api public
1812
+ */
1813
+
1745
1814
  /**
1746
1815
  * Removes the database connection with the given name created with with `useDb()`.
1747
1816
  *
@@ -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
@@ -741,15 +741,10 @@ function init(self, obj, doc, opts, prefix) {
741
741
  let schemaType;
742
742
  let path;
743
743
  let i;
744
- let index = 0;
745
744
  const strict = self.$__.strictMode;
746
745
  const docSchema = self.$__schema;
747
746
 
748
- while (index < len) {
749
- _init(index++);
750
- }
751
-
752
- function _init(index) {
747
+ for (let index = 0; index < len; ++index) {
753
748
  i = keys[index];
754
749
  // avoid prototype pollution
755
750
  if (i === '__proto__' || i === 'constructor') {
@@ -807,8 +802,8 @@ function init(self, obj, doc, opts, prefix) {
807
802
  reason: e
808
803
  }));
809
804
  }
810
- } else if (opts.hydratedPopulatedDocs) {
811
- doc[i] = schemaType.cast(value, self, true);
805
+ } else if (schemaType && opts.hydratedPopulatedDocs) {
806
+ doc[i] = schemaType.cast(value, self, true, undefined, { hydratedPopulatedDocs: true });
812
807
 
813
808
  if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
814
809
  self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
@@ -3558,8 +3553,10 @@ Document.prototype.$__undoReset = function $__undoReset() {
3558
3553
  }
3559
3554
  }
3560
3555
 
3561
- for (const subdoc of this.$getAllSubdocs()) {
3562
- subdoc.$__undoReset();
3556
+ if (!this.$isSubdocument) {
3557
+ for (const subdoc of this.$getAllSubdocs()) {
3558
+ subdoc.$__undoReset();
3559
+ }
3563
3560
  }
3564
3561
  };
3565
3562
 
@@ -4256,24 +4253,25 @@ function applySchemaTypeTransforms(self, json) {
4256
4253
 
4257
4254
  for (const path of paths) {
4258
4255
  const schematype = schema.paths[path];
4259
- if (typeof schematype.options.transform === 'function') {
4256
+ const topLevelTransformFunction = schematype.options.transform ?? schematype.constructor?.defaultOptions?.transform;
4257
+ const embeddedSchemaTypeTransformFunction = schematype.$embeddedSchemaType?.options?.transform
4258
+ ?? schematype.$embeddedSchemaType?.constructor?.defaultOptions?.transform;
4259
+ if (typeof topLevelTransformFunction === 'function') {
4260
4260
  const val = self.$get(path);
4261
4261
  if (val === undefined) {
4262
4262
  continue;
4263
4263
  }
4264
- const transformedValue = schematype.options.transform.call(self, val);
4264
+ const transformedValue = topLevelTransformFunction.call(self, val);
4265
4265
  throwErrorIfPromise(path, transformedValue);
4266
4266
  utils.setValue(path, transformedValue, json);
4267
- } else if (schematype.$embeddedSchemaType != null &&
4268
- typeof schematype.$embeddedSchemaType.options.transform === 'function') {
4267
+ } else if (typeof embeddedSchemaTypeTransformFunction === 'function') {
4269
4268
  const val = self.$get(path);
4270
4269
  if (val === undefined) {
4271
4270
  continue;
4272
4271
  }
4273
4272
  const vals = [].concat(val);
4274
- const transform = schematype.$embeddedSchemaType.options.transform;
4275
4273
  for (let i = 0; i < vals.length; ++i) {
4276
- const transformedValue = transform.call(self, vals[i]);
4274
+ const transformedValue = embeddedSchemaTypeTransformFunction.call(self, vals[i]);
4277
4275
  vals[i] = transformedValue;
4278
4276
  throwErrorIfPromise(path, transformedValue);
4279
4277
  }
@@ -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
+ };
@@ -82,6 +82,16 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
82
82
  schema = schema.discriminators[discriminatorValue] ||
83
83
  (byValue && byValue.schema) ||
84
84
  schema;
85
+ } else if (schema != null &&
86
+ options.overwriteDiscriminatorKey &&
87
+ obj.$set != null &&
88
+ utils.hasUserDefinedProperty(obj.$set, schema.options.discriminatorKey) &&
89
+ schema.discriminators != null) {
90
+ const discriminatorValue = obj.$set[schema.options.discriminatorKey];
91
+ const byValue = getDiscriminatorByValue(context.model.discriminators, discriminatorValue);
92
+ schema = schema.discriminators[discriminatorValue] ||
93
+ (byValue && byValue.schema) ||
94
+ schema;
85
95
  }
86
96
 
87
97
  if (options.upsert) {
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,20 +1103,28 @@ 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
- }
1056
- const autoCreate = utils.getOption(
1106
+ let autoCreate = utils.getOption(
1057
1107
  'autoCreate',
1058
1108
  this.schema.options,
1059
- conn.config,
1060
- conn.base.options
1109
+ conn.config
1110
+ // No base.options here because we don't want to take the base value if the connection hasn't
1111
+ // set it yet
1061
1112
  );
1113
+ if (autoCreate == null) {
1114
+ // `autoCreate` may later be set when the connection is opened, so wait for connect before checking
1115
+ await conn._waitForConnect(true);
1116
+ autoCreate = utils.getOption(
1117
+ 'autoCreate',
1118
+ this.schema.options,
1119
+ conn.config,
1120
+ conn.base.options
1121
+ );
1122
+ }
1123
+
1062
1124
  if (!autoCreate) {
1063
1125
  return;
1064
1126
  }
1127
+
1065
1128
  return await this.createCollection();
1066
1129
  };
1067
1130
 
@@ -1246,19 +1309,21 @@ Model.syncIndexes = async function syncIndexes(options) {
1246
1309
  throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
1247
1310
  }
1248
1311
 
1249
- const model = this;
1312
+ const autoCreate = options?.autoCreate ?? this.schema.options?.autoCreate ?? this.db.config.autoCreate ?? true;
1250
1313
 
1251
- try {
1252
- await model.createCollection();
1253
- } catch (err) {
1254
- if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
1255
- throw err;
1314
+ if (autoCreate) {
1315
+ try {
1316
+ await this.createCollection();
1317
+ } catch (err) {
1318
+ if (err != null && (err.name !== 'MongoServerError' || err.code !== 48)) {
1319
+ throw err;
1320
+ }
1256
1321
  }
1257
1322
  }
1258
1323
 
1259
- const diffIndexesResult = await model.diffIndexes();
1260
- const dropped = await model.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
1261
- await model.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
1324
+ const diffIndexesResult = await this.diffIndexes({ indexOptionsToCreate: true });
1325
+ const dropped = await this.cleanIndexes({ ...options, toDrop: diffIndexesResult.toDrop });
1326
+ await this.createIndexes({ ...options, toCreate: diffIndexesResult.toCreate });
1262
1327
 
1263
1328
  return dropped;
1264
1329
  };
@@ -1361,13 +1426,14 @@ Model.listSearchIndexes = async function listSearchIndexes(options) {
1361
1426
  *
1362
1427
  * const { toDrop, toCreate } = await Model.diffIndexes();
1363
1428
  * 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
1429
+ * toCreate; // Array of index specs containing the keys of indexes that `syncIndexes()` will create
1365
1430
  *
1366
1431
  * @param {Object} [options]
1432
+ * @param {Boolean} [options.indexOptionsToCreate=false] If true, `toCreate` will include both the index spec and the index options, not just the index spec
1367
1433
  * @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
1434
  */
1369
1435
 
1370
- Model.diffIndexes = async function diffIndexes() {
1436
+ Model.diffIndexes = async function diffIndexes(options) {
1371
1437
  if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
1372
1438
  throw new MongooseError('Model.syncIndexes() no longer accepts a callback');
1373
1439
  }
@@ -1389,13 +1455,14 @@ Model.diffIndexes = async function diffIndexes() {
1389
1455
  const schemaIndexes = getRelatedSchemaIndexes(model, schema.indexes());
1390
1456
 
1391
1457
  const toDrop = getIndexesToDrop(schema, schemaIndexes, dbIndexes);
1392
- const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop);
1458
+ const toCreate = getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options);
1393
1459
 
1394
1460
  return { toDrop, toCreate };
1395
1461
  };
1396
1462
 
1397
- function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
1463
+ function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop, options) {
1398
1464
  const toCreate = [];
1465
+ const indexOptionsToCreate = options?.indexOptionsToCreate ?? false;
1399
1466
 
1400
1467
  for (const [schemaIndexKeysObject, schemaIndexOptions] of schemaIndexes) {
1401
1468
  let found = false;
@@ -1416,7 +1483,11 @@ function getIndexesToCreate(schema, schemaIndexes, dbIndexes, toDrop) {
1416
1483
  }
1417
1484
 
1418
1485
  if (!found) {
1419
- toCreate.push(schemaIndexKeysObject);
1486
+ if (indexOptionsToCreate) {
1487
+ toCreate.push([schemaIndexKeysObject, schemaIndexOptions]);
1488
+ } else {
1489
+ toCreate.push(schemaIndexKeysObject);
1490
+ }
1420
1491
  }
1421
1492
  }
1422
1493
 
@@ -1465,7 +1536,7 @@ function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
1465
1536
  * @param {Object} [options]
1466
1537
  * @param {Array<String>} [options.toDrop] if specified, contains a list of index names to drop
1467
1538
  * @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
1539
+ * @return {Promise<Array<String>>} list of dropped or hidden index names
1469
1540
  * @api public
1470
1541
  */
1471
1542
 
@@ -1597,7 +1668,7 @@ Model.createIndexes = async function createIndexes(options) {
1597
1668
  */
1598
1669
 
1599
1670
  function _ensureIndexes(model, options, callback) {
1600
- const indexes = model.schema.indexes();
1671
+ const indexes = Array.isArray(options?.toCreate) ? options.toCreate : model.schema.indexes();
1601
1672
  let indexError;
1602
1673
 
1603
1674
  options = options || {};
@@ -1681,12 +1752,6 @@ function _ensureIndexes(model, options, callback) {
1681
1752
  indexOptions.background = options.background;
1682
1753
  }
1683
1754
 
1684
- if ('toCreate' in options) {
1685
- if (options.toCreate.length === 0) {
1686
- return done();
1687
- }
1688
- }
1689
-
1690
1755
  // Just in case `createIndex()` throws a sync error
1691
1756
  let promise = null;
1692
1757
  try {
@@ -2115,9 +2180,8 @@ Model.estimatedDocumentCount = function estimatedDocumentCount(options) {
2115
2180
  *
2116
2181
  * #### Example:
2117
2182
  *
2118
- * Adventure.countDocuments({ type: 'jungle' }, function (err, count) {
2119
- * console.log('there are %d jungle adventures', count);
2120
- * });
2183
+ * const count = await Adventure.countDocuments({ type: 'jungle' });
2184
+ * console.log('there are %d jungle adventures', count);
2121
2185
  *
2122
2186
  * If you want to count all documents in a large collection,
2123
2187
  * use the [`estimatedDocumentCount()` function](https://mongoosejs.com/docs/api/model.html#Model.estimatedDocumentCount())
@@ -2627,6 +2691,10 @@ Model.create = async function create(doc, options) {
2627
2691
 
2628
2692
  delete options.aggregateErrors; // dont pass on the option to "$save"
2629
2693
 
2694
+ if (options.session && !options.ordered && args.length > 1) {
2695
+ throw new MongooseError('Cannot call `create()` with a session and multiple documents unless `ordered: true` is set');
2696
+ }
2697
+
2630
2698
  if (options.ordered) {
2631
2699
  for (let i = 0; i < args.length; i++) {
2632
2700
  try {
@@ -2713,6 +2781,49 @@ Model.create = async function create(doc, options) {
2713
2781
  return res;
2714
2782
  };
2715
2783
 
2784
+ /**
2785
+ * Shortcut for saving one document to the database.
2786
+ * `MyModel.insertOne(obj, options)` is almost equivalent to `new MyModel(obj).save(options)`.
2787
+ * The difference is that `insertOne()` checks if `obj` is already a document, and checks for discriminators.
2788
+ *
2789
+ * This function triggers the following middleware.
2790
+ *
2791
+ * - `save()`
2792
+ *
2793
+ * #### Example:
2794
+ *
2795
+ * // Insert one new `Character` document
2796
+ * const character = await Character.insertOne({ name: 'Jean-Luc Picard' });
2797
+ * character.name; // 'Jean-Luc Picard'
2798
+ *
2799
+ * // Create a new character within a transaction.
2800
+ * await Character.insertOne({ name: 'Jean-Luc Picard' }, { session });
2801
+ *
2802
+ * @param {Object|Document} doc Document to insert, as a POJO or Mongoose document
2803
+ * @param {Object} [options] Options passed down to `save()`.
2804
+ * @return {Promise<Document>} resolves to the saved document
2805
+ * @api public
2806
+ */
2807
+
2808
+ Model.insertOne = async function insertOne(doc, options) {
2809
+ _checkContext(this, 'insertOne');
2810
+
2811
+ const discriminatorKey = this.schema.options.discriminatorKey;
2812
+ const Model = this.discriminators && doc[discriminatorKey] != null ?
2813
+ this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2814
+ this;
2815
+ if (Model == null) {
2816
+ throw new MongooseError(
2817
+ `Discriminator "${doc[discriminatorKey]}" not found for model "${this.modelName}"`
2818
+ );
2819
+ }
2820
+ if (!(doc instanceof Model)) {
2821
+ doc = new Model(doc);
2822
+ }
2823
+
2824
+ return await doc.$save(options);
2825
+ };
2826
+
2716
2827
  /**
2717
2828
  * _Requires a replica set running MongoDB >= 3.6.0._ Watches the
2718
2829
  * underlying collection for changes using
@@ -3878,6 +3989,10 @@ Model.hydrate = function(obj, projection, options) {
3878
3989
  * res.upsertedId; // null or an id containing a document that had to be upserted.
3879
3990
  * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
3880
3991
  *
3992
+ * // Other supported syntaxes
3993
+ * await Person.find({ name: /Stark$/ }).updateMany({ isDeleted: true }); // Using chaining syntax
3994
+ * await Person.find().updateMany({ isDeleted: true }); // Set `isDeleted` on _all_ Person documents
3995
+ *
3881
3996
  * This function triggers the following middleware.
3882
3997
  *
3883
3998
  * - `updateMany()`
@@ -3898,10 +4013,14 @@ Model.hydrate = function(obj, projection, options) {
3898
4013
  * @api public
3899
4014
  */
3900
4015
 
3901
- Model.updateMany = function updateMany(conditions, doc, options) {
4016
+ Model.updateMany = function updateMany(conditions, update, options) {
3902
4017
  _checkContext(this, 'updateMany');
3903
4018
 
3904
- return _update(this, 'updateMany', conditions, doc, options);
4019
+ if (update == null) {
4020
+ throw new MongooseError('updateMany `update` parameter cannot be nullish');
4021
+ }
4022
+
4023
+ return _update(this, 'updateMany', conditions, update, options);
3905
4024
  };
3906
4025
 
3907
4026
  /**
@@ -3918,6 +4037,10 @@ Model.updateMany = function updateMany(conditions, doc, options) {
3918
4037
  * res.upsertedId; // null or an id containing a document that had to be upserted.
3919
4038
  * res.upsertedCount; // Number indicating how many documents had to be upserted. Will either be 0 or 1.
3920
4039
  *
4040
+ * // Other supported syntaxes
4041
+ * await Person.findOne({ name: 'Jean-Luc Picard' }).updateOne({ ship: 'USS Enterprise' }); // Using chaining syntax
4042
+ * await Person.updateOne({ ship: 'USS Enterprise' }); // Updates first doc's `ship` property
4043
+ *
3921
4044
  * This function triggers the following middleware.
3922
4045
  *
3923
4046
  * - `updateOne()`