mongoose 8.8.4 → 8.9.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.
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ const assert = require('assert');
4
+ const BSON = require('bson');
5
+ const isBsonType = require('../helpers/isBsonType');
6
+
7
+ /**
8
+ * Given a value, cast it to a IEEE 754-2008 floating point, or throw an `Error` if the value
9
+ * cannot be casted. `null`, `undefined`, and `NaN` are considered valid inputs.
10
+ *
11
+ * @param {Any} value
12
+ * @return {Number}
13
+ * @throws {Error} if `value` does not represent a IEEE 754-2008 floating point. If casting from a string, see [BSON Double.fromString API documentation](https://mongodb.github.io/node-mongodb-native/Next/classes/BSON.Double.html#fromString)
14
+ * @api private
15
+ */
16
+
17
+ module.exports = function castDouble(val) {
18
+ if (val == null || val === '') {
19
+ return null;
20
+ }
21
+
22
+ let coercedVal;
23
+ if (isBsonType(val, 'Long')) {
24
+ coercedVal = val.toNumber();
25
+ } else if (typeof val === 'string') {
26
+ try {
27
+ coercedVal = BSON.Double.fromString(val);
28
+ return coercedVal;
29
+ } catch {
30
+ assert.ok(false);
31
+ }
32
+ } else if (typeof val === 'object') {
33
+ const tempVal = val.valueOf() ?? val.toString();
34
+ // ex: { a: 'im an object, valueOf: () => 'helloworld' } // throw an error
35
+ if (typeof tempVal === 'string') {
36
+ try {
37
+ coercedVal = BSON.Double.fromString(val);
38
+ return coercedVal;
39
+ } catch {
40
+ assert.ok(false);
41
+ }
42
+ } else {
43
+ coercedVal = Number(tempVal);
44
+ }
45
+ } else {
46
+ coercedVal = Number(val);
47
+ }
48
+
49
+ return new BSON.Double(coercedVal);
50
+ };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ const isBsonType = require('../helpers/isBsonType');
4
+ const assert = require('assert');
5
+
6
+ /**
7
+ * Given a value, cast it to a Int32, or throw an `Error` if the value
8
+ * cannot be casted. `null` and `undefined` are considered valid.
9
+ *
10
+ * @param {Any} value
11
+ * @return {Number}
12
+ * @throws {Error} if `value` does not represent an integer, or is outside the bounds of an 32-bit integer.
13
+ * @api private
14
+ */
15
+
16
+ module.exports = function castInt32(val) {
17
+ if (val == null) {
18
+ return val;
19
+ }
20
+ if (val === '') {
21
+ return null;
22
+ }
23
+
24
+ const coercedVal = isBsonType(val, 'Long') ? val.toNumber() : Number(val);
25
+
26
+ const INT32_MAX = 0x7FFFFFFF;
27
+ const INT32_MIN = -0x80000000;
28
+
29
+ if (coercedVal === (coercedVal | 0) &&
30
+ coercedVal >= INT32_MIN &&
31
+ coercedVal <= INT32_MAX
32
+ ) {
33
+ return coercedVal;
34
+ }
35
+ assert.ok(false);
36
+ };
package/lib/connection.js CHANGED
@@ -8,6 +8,7 @@ const ChangeStream = require('./cursor/changeStream');
8
8
  const EventEmitter = require('events').EventEmitter;
9
9
  const Schema = require('./schema');
10
10
  const STATES = require('./connectionState');
11
+ const MongooseBulkWriteError = require('./error/bulkWriteError');
11
12
  const MongooseError = require('./error/index');
12
13
  const ServerSelectionError = require('./error/serverSelection');
13
14
  const SyncIndexesError = require('./error/syncIndexes');
@@ -15,9 +16,13 @@ const applyPlugins = require('./helpers/schema/applyPlugins');
15
16
  const clone = require('./helpers/clone');
16
17
  const driver = require('./driver');
17
18
  const get = require('./helpers/get');
19
+ const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
18
20
  const immediate = require('./helpers/immediate');
19
21
  const utils = require('./utils');
20
22
  const CreateCollectionsError = require('./error/createCollectionsError');
23
+ const castBulkWrite = require('./helpers/model/castBulkWrite');
24
+ const { modelSymbol } = require('./helpers/symbols');
25
+ const isPromise = require('./helpers/isPromise');
21
26
 
22
27
  const arrayAtomicsSymbol = require('./helpers/symbols').arrayAtomicsSymbol;
23
28
  const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
@@ -111,6 +116,9 @@ Object.defineProperty(Connection.prototype, 'readyState', {
111
116
  if (
112
117
  this._readyState === STATES.connected &&
113
118
  this._lastHeartbeatAt != null &&
119
+ // LoadBalanced topology (behind haproxy, including Atlas serverless instances) don't use heartbeats,
120
+ // so we can't use this check in that case.
121
+ this.client?.topology?.s?.description?.type !== 'LoadBalanced' &&
114
122
  typeof this.client?.topology?.s?.description?.heartbeatFrequencyMS === 'number' &&
115
123
  Date.now() - this._lastHeartbeatAt >= this.client.topology.s.description.heartbeatFrequencyMS * 2) {
116
124
  return STATES.disconnected;
@@ -150,7 +158,7 @@ Object.defineProperty(Connection.prototype, 'readyState', {
150
158
  * @api public
151
159
  */
152
160
 
153
- Connection.prototype.get = function(key) {
161
+ Connection.prototype.get = function getOption(key) {
154
162
  if (this.config.hasOwnProperty(key)) {
155
163
  return this.config[key];
156
164
  }
@@ -178,7 +186,7 @@ Connection.prototype.get = function(key) {
178
186
  * @api public
179
187
  */
180
188
 
181
- Connection.prototype.set = function(key, val) {
189
+ Connection.prototype.set = function setOption(key, val) {
182
190
  if (this.config.hasOwnProperty(key)) {
183
191
  this.config[key] = val;
184
192
  return val;
@@ -416,6 +424,178 @@ Connection.prototype.createCollection = async function createCollection(collecti
416
424
  return this.db.createCollection(collection, options);
417
425
  };
418
426
 
427
+ /**
428
+ * _Requires MongoDB Server 8.0 or greater_. Executes bulk write operations across multiple models in a single operation.
429
+ * You must specify the `model` for each operation: Mongoose will use `model` for casting and validation, as well as
430
+ * determining which collection to apply the operation to.
431
+ *
432
+ * #### Example:
433
+ * const Test = mongoose.model('Test', new Schema({ name: String }));
434
+ *
435
+ * await db.bulkWrite([
436
+ * { model: Test, name: 'insertOne', document: { name: 'test1' } }, // Can specify model as a Model class...
437
+ * { model: 'Test', name: 'insertOne', document: { name: 'test2' } } // or as a model name
438
+ * ], { ordered: false });
439
+ *
440
+ * @method bulkWrite
441
+ * @param {Array} ops
442
+ * @param {Object} [options]
443
+ * @param {Boolean} [options.ordered] If false, perform unordered operations. If true, perform ordered operations.
444
+ * @param {Session} [options.session] The session to use for the operation.
445
+ * @return {Promise}
446
+ * @see MongoDB https://www.mongodb.com/docs/manual/reference/command/bulkWrite/#mongodb-dbcommand-dbcmd.bulkWrite
447
+ * @api public
448
+ */
449
+
450
+
451
+ Connection.prototype.bulkWrite = async function bulkWrite(ops, options) {
452
+ await this._waitForConnect();
453
+ options = options || {};
454
+
455
+ const ordered = options.ordered == null ? true : options.ordered;
456
+ const asyncLocalStorage = this.base.transactionAsyncLocalStorage?.getStore();
457
+ if ((!options || !options.hasOwnProperty('session')) && asyncLocalStorage?.session != null) {
458
+ options = { ...options, session: asyncLocalStorage.session };
459
+ }
460
+
461
+ const now = this.base.now();
462
+
463
+ let res = null;
464
+ if (ordered) {
465
+ const opsToSend = [];
466
+ for (const op of ops) {
467
+ if (typeof op.model !== 'string' && !op.model?.[modelSymbol]) {
468
+ throw new MongooseError('Must specify model in Connection.prototype.bulkWrite() operations');
469
+ }
470
+ const Model = op.model[modelSymbol] ? op.model : this.model(op.model);
471
+
472
+ if (op.name == null) {
473
+ throw new MongooseError('Must specify operation name in Connection.prototype.bulkWrite()');
474
+ }
475
+ if (!castBulkWrite.cast.hasOwnProperty(op.name)) {
476
+ throw new MongooseError(`Unrecognized bulkWrite() operation name ${op.name}`);
477
+ }
478
+
479
+ await castBulkWrite.cast[op.name](Model, op, options, now);
480
+ opsToSend.push({ ...op, namespace: Model.namespace() });
481
+ }
482
+
483
+ res = await this.client.bulkWrite(opsToSend, options);
484
+ } else {
485
+ const validOps = [];
486
+ const validOpIndexes = [];
487
+ let validationErrors = [];
488
+ const asyncValidations = [];
489
+ const results = [];
490
+ for (let i = 0; i < ops.length; ++i) {
491
+ const op = ops[i];
492
+ if (typeof op.model !== 'string' && !op.model?.[modelSymbol]) {
493
+ const error = new MongooseError('Must specify model in Connection.prototype.bulkWrite() operations');
494
+ validationErrors.push({ index: i, error: error });
495
+ results[i] = error;
496
+ continue;
497
+ }
498
+ let Model;
499
+ try {
500
+ Model = op.model[modelSymbol] ? op.model : this.model(op.model);
501
+ } catch (error) {
502
+ validationErrors.push({ index: i, error: error });
503
+ continue;
504
+ }
505
+ if (op.name == null) {
506
+ const error = new MongooseError('Must specify operation name in Connection.prototype.bulkWrite()');
507
+ validationErrors.push({ index: i, error: error });
508
+ results[i] = error;
509
+ continue;
510
+ }
511
+ if (!castBulkWrite.cast.hasOwnProperty(op.name)) {
512
+ const error = new MongooseError(`Unrecognized bulkWrite() operation name ${op.name}`);
513
+ validationErrors.push({ index: i, error: error });
514
+ results[i] = error;
515
+ continue;
516
+ }
517
+
518
+ let maybePromise = null;
519
+ try {
520
+ maybePromise = castBulkWrite.cast[op.name](Model, op, options, now);
521
+ } catch (error) {
522
+ validationErrors.push({ index: i, error: error });
523
+ results[i] = error;
524
+ continue;
525
+ }
526
+ if (isPromise(maybePromise)) {
527
+ asyncValidations.push(
528
+ maybePromise.then(
529
+ () => {
530
+ validOps.push({ ...op, namespace: Model.namespace() });
531
+ validOpIndexes.push(i);
532
+ },
533
+ error => {
534
+ validationErrors.push({ index: i, error: error });
535
+ results[i] = error;
536
+ }
537
+ )
538
+ );
539
+ } else {
540
+ validOps.push({ ...op, namespace: Model.namespace() });
541
+ validOpIndexes.push(i);
542
+ }
543
+ }
544
+
545
+ if (asyncValidations.length > 0) {
546
+ await Promise.all(asyncValidations);
547
+ }
548
+
549
+ validationErrors = validationErrors.
550
+ sort((v1, v2) => v1.index - v2.index).
551
+ map(v => v.error);
552
+
553
+ if (validOps.length === 0) {
554
+ if (options.throwOnValidationError && validationErrors.length) {
555
+ throw new MongooseBulkWriteError(
556
+ validationErrors,
557
+ results,
558
+ res,
559
+ 'bulkWrite'
560
+ );
561
+ }
562
+ return getDefaultBulkwriteResult();
563
+ }
564
+
565
+ let error;
566
+ [res, error] = await this.client.bulkWrite(validOps, options).
567
+ then(res => ([res, null])).
568
+ catch(err => ([null, err]));
569
+
570
+ if (error) {
571
+ if (validationErrors.length > 0) {
572
+ error.mongoose = error.mongoose || {};
573
+ error.mongoose.validationErrors = validationErrors;
574
+ }
575
+ }
576
+
577
+ for (let i = 0; i < validOpIndexes.length; ++i) {
578
+ results[validOpIndexes[i]] = null;
579
+ }
580
+ if (validationErrors.length > 0) {
581
+ if (options.throwOnValidationError) {
582
+ throw new MongooseBulkWriteError(
583
+ validationErrors,
584
+ results,
585
+ res,
586
+ 'bulkWrite'
587
+ );
588
+ } else {
589
+ res.mongoose = res.mongoose || {};
590
+ res.mongoose.validationErrors = validationErrors;
591
+ res.mongoose.results = results;
592
+ }
593
+ }
594
+ }
595
+
596
+ return res;
597
+ };
598
+
419
599
  /**
420
600
  * Calls `createCollection()` on a models in a series.
421
601
  *
@@ -745,7 +925,7 @@ Connection.prototype._shouldBufferCommands = function _shouldBufferCommands() {
745
925
  * @api private
746
926
  */
747
927
 
748
- Connection.prototype.error = function(err, callback) {
928
+ Connection.prototype.error = function error(err, callback) {
749
929
  if (callback) {
750
930
  callback(err);
751
931
  return null;
@@ -759,6 +939,7 @@ Connection.prototype.error = function(err, callback) {
759
939
  /**
760
940
  * Called when the connection is opened
761
941
  *
942
+ * @emits "open"
762
943
  * @api private
763
944
  */
764
945
 
@@ -863,11 +1044,21 @@ Connection.prototype.openUri = async function openUri(uri, options) {
863
1044
  return this;
864
1045
  };
865
1046
 
866
- /*!
867
- * Treat `on('error')` handlers as handling the initialConnection promise
868
- * to avoid uncaught exceptions when using `on('error')`. See gh-14377.
1047
+ /**
1048
+ * Listen to events in the Connection
1049
+ *
1050
+ * @param {String} event The event to listen on
1051
+ * @param {Function} callback
1052
+ * @see Connection#readyState https://mongoosejs.com/docs/api/connection.html#Connection.prototype.readyState
1053
+ *
1054
+ * @method on
1055
+ * @instance
1056
+ * @memberOf Connection
1057
+ * @api public
869
1058
  */
870
1059
 
1060
+ // Treat `on('error')` handlers as handling the initialConnection promise
1061
+ // to avoid uncaught exceptions when using `on('error')`. See gh-14377.
871
1062
  Connection.prototype.on = function on(event, callback) {
872
1063
  if (event === 'error' && this.$initialConnection) {
873
1064
  this.$initialConnection.catch(() => {});
@@ -875,11 +1066,21 @@ Connection.prototype.on = function on(event, callback) {
875
1066
  return EventEmitter.prototype.on.call(this, event, callback);
876
1067
  };
877
1068
 
878
- /*!
879
- * Treat `once('error')` handlers as handling the initialConnection promise
880
- * to avoid uncaught exceptions when using `on('error')`. See gh-14377.
1069
+ /**
1070
+ * Listen to a event once in the Connection
1071
+ *
1072
+ * @param {String} event The event to listen on
1073
+ * @param {Function} callback
1074
+ * @see Connection#readyState https://mongoosejs.com/docs/api/connection.html#Connection.prototype.readyState
1075
+ *
1076
+ * @method once
1077
+ * @instance
1078
+ * @memberOf Connection
1079
+ * @api public
881
1080
  */
882
1081
 
1082
+ // Treat `on('error')` handlers as handling the initialConnection promise
1083
+ // to avoid uncaught exceptions when using `on('error')`. See gh-14377.
883
1084
  Connection.prototype.once = function on(event, callback) {
884
1085
  if (event === 'error' && this.$initialConnection) {
885
1086
  this.$initialConnection.catch(() => {});
@@ -1040,17 +1241,18 @@ Connection.prototype._close = async function _close(force, destroy) {
1040
1241
  * @api private
1041
1242
  */
1042
1243
 
1043
- Connection.prototype.doClose = function() {
1244
+ Connection.prototype.doClose = function doClose() {
1044
1245
  throw new Error('Connection#doClose unimplemented by driver');
1045
1246
  };
1046
1247
 
1047
1248
  /**
1048
1249
  * Called when the connection closes
1049
1250
  *
1251
+ * @emits "close"
1050
1252
  * @api private
1051
1253
  */
1052
1254
 
1053
- Connection.prototype.onClose = function(force) {
1255
+ Connection.prototype.onClose = function onClose(force) {
1054
1256
  this.readyState = STATES.disconnected;
1055
1257
 
1056
1258
  // avoid having the collection subscribe to our event emitter
@@ -1154,7 +1356,7 @@ Connection.prototype.plugin = function(fn, opts) {
1154
1356
  * @api public
1155
1357
  */
1156
1358
 
1157
- Connection.prototype.model = function(name, schema, collection, options) {
1359
+ Connection.prototype.model = function model(name, schema, collection, options) {
1158
1360
  if (!(this instanceof Connection)) {
1159
1361
  throw new MongooseError('`connection.model()` should not be run with ' +
1160
1362
  '`new`. If you are doing `new db.model(foo)(bar)`, use ' +
@@ -1274,7 +1476,7 @@ Connection.prototype.model = function(name, schema, collection, options) {
1274
1476
  * @return {Connection} this
1275
1477
  */
1276
1478
 
1277
- Connection.prototype.deleteModel = function(name) {
1479
+ Connection.prototype.deleteModel = function deleteModel(name) {
1278
1480
  if (typeof name === 'string') {
1279
1481
  const model = this.model(name);
1280
1482
  if (model == null) {
@@ -1330,7 +1532,7 @@ Connection.prototype.deleteModel = function(name) {
1330
1532
  * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
1331
1533
  */
1332
1534
 
1333
- Connection.prototype.watch = function(pipeline, options) {
1535
+ Connection.prototype.watch = function watch(pipeline, options) {
1334
1536
  const changeStreamThunk = cb => {
1335
1537
  immediate(() => {
1336
1538
  if (this.readyState === STATES.connecting) {
@@ -1379,7 +1581,7 @@ Connection.prototype.asPromise = async function asPromise() {
1379
1581
  * @return {String[]}
1380
1582
  */
1381
1583
 
1382
- Connection.prototype.modelNames = function() {
1584
+ Connection.prototype.modelNames = function modelNames() {
1383
1585
  return Object.keys(this.models);
1384
1586
  };
1385
1587
 
@@ -1391,7 +1593,7 @@ Connection.prototype.modelNames = function() {
1391
1593
  * @api private
1392
1594
  * @return {Boolean} true if the connection should be authenticated after it is opened, otherwise false.
1393
1595
  */
1394
- Connection.prototype.shouldAuthenticate = function() {
1596
+ Connection.prototype.shouldAuthenticate = function shouldAuthenticate() {
1395
1597
  return this.user != null &&
1396
1598
  (this.pass != null || this.authMechanismDoesNotRequirePassword());
1397
1599
  };
@@ -1404,7 +1606,7 @@ Connection.prototype.shouldAuthenticate = function() {
1404
1606
  * @return {Boolean} true if the authentication mechanism specified in the options object requires
1405
1607
  * a password, otherwise false.
1406
1608
  */
1407
- Connection.prototype.authMechanismDoesNotRequirePassword = function() {
1609
+ Connection.prototype.authMechanismDoesNotRequirePassword = function authMechanismDoesNotRequirePassword() {
1408
1610
  if (this.options && this.options.auth) {
1409
1611
  return noPasswordAuthMechanisms.indexOf(this.options.auth.authMechanism) >= 0;
1410
1612
  }
@@ -1422,7 +1624,7 @@ Connection.prototype.authMechanismDoesNotRequirePassword = function() {
1422
1624
  * @return {Boolean} true if the provided options object provides enough data to authenticate with,
1423
1625
  * otherwise false.
1424
1626
  */
1425
- Connection.prototype.optionsProvideAuthenticationData = function(options) {
1627
+ Connection.prototype.optionsProvideAuthenticationData = function optionsProvideAuthenticationData(options) {
1426
1628
  return (options) &&
1427
1629
  (options.user) &&
1428
1630
  ((options.pass) || this.authMechanismDoesNotRequirePassword());
@@ -175,11 +175,10 @@ AggregationCursor.prototype._markError = function(error) {
175
175
  * Marks this cursor as closed. Will stop streaming and subsequent calls to
176
176
  * `next()` will error.
177
177
  *
178
- * @param {Function} callback
179
178
  * @return {Promise}
180
179
  * @api public
181
180
  * @method close
182
- * @emits close
181
+ * @emits "close"
183
182
  * @see AggregationCursor.close https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html#close
184
183
  */
185
184
 
package/lib/document.js CHANGED
@@ -624,16 +624,17 @@ Document.prototype.toBSON = function() {
624
624
  };
625
625
 
626
626
  /**
627
- * Initializes the document without setters or marking anything modified.
627
+ * Hydrates this document with the data in `doc`. Does not run setters or mark any paths modified.
628
628
  *
629
- * Called internally after a document is returned from mongodb. Normally,
629
+ * Called internally after a document is returned from MongoDB. Normally,
630
630
  * you do **not** need to call this function on your own.
631
631
  *
632
632
  * This function triggers `init` [middleware](https://mongoosejs.com/docs/middleware.html).
633
633
  * Note that `init` hooks are [synchronous](https://mongoosejs.com/docs/middleware.html#synchronous).
634
634
  *
635
- * @param {Object} doc document returned by mongo
635
+ * @param {Object} doc raw document returned by mongo
636
636
  * @param {Object} [opts]
637
+ * @param {Boolean} [opts.hydratedPopulatedDocs=false] If true, hydrate and mark as populated any paths that are populated in the raw document
637
638
  * @param {Function} [fn]
638
639
  * @api public
639
640
  * @memberOf Document
@@ -805,6 +806,14 @@ function init(self, obj, doc, opts, prefix) {
805
806
  reason: e
806
807
  }));
807
808
  }
809
+ } else if (opts.hydratedPopulatedDocs) {
810
+ doc[i] = schemaType.cast(value, self, true);
811
+
812
+ if (doc[i] && doc[i].$__ && doc[i].$__.wasPopulated) {
813
+ self.$populated(path, doc[i].$__.wasPopulated.value, doc[i].$__.wasPopulated.options);
814
+ } else if (Array.isArray(doc[i]) && doc[i].length && doc[i][0]?.$__?.wasPopulated) {
815
+ self.$populated(path, doc[i].map(populatedDoc => populatedDoc?.$__?.wasPopulated?.value).filter(val => val != null), doc[i][0].$__.wasPopulated.options);
816
+ }
808
817
  } else {
809
818
  doc[i] = value;
810
819
  }
@@ -822,7 +831,7 @@ function init(self, obj, doc, opts, prefix) {
822
831
  *
823
832
  * #### Example:
824
833
  *
825
- * weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 }, callback);
834
+ * weirdCar.updateOne({$inc: {wheels:1}}, { w: 1 });
826
835
  *
827
836
  * #### Valid options:
828
837
  *
@@ -834,7 +843,6 @@ function init(self, obj, doc, opts, prefix) {
834
843
  * @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and the [Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
835
844
  * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
836
845
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
837
- * @param {Function} [callback]
838
846
  * @return {Query}
839
847
  * @api public
840
848
  * @memberOf Document
@@ -1768,6 +1776,11 @@ Document.prototype.$__set = function(pathToMark, path, options, constructing, pa
1768
1776
  */
1769
1777
 
1770
1778
  Document.prototype.$__getValue = function(path) {
1779
+ if (typeof path !== 'string' && !Array.isArray(path)) {
1780
+ throw new TypeError(
1781
+ `Invalid \`path\`. Must be either string or array. Got "${path}" (type ${typeof path})`
1782
+ );
1783
+ }
1771
1784
  return utils.getValue(path, this._doc);
1772
1785
  };
1773
1786
 
@@ -3430,12 +3443,11 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
3430
3443
  * @param {Number} [options.wtimeout] sets a [timeout for the write concern](https://www.mongodb.com/docs/manual/reference/write-concern/#wtimeout). Overrides the [schema-level `writeConcern` option](https://mongoosejs.com/docs/guide.html#writeConcern).
3431
3444
  * @param {Boolean} [options.checkKeys=true] the MongoDB driver prevents you from saving keys that start with '$' or contain '.' by default. Set this option to `false` to skip that check. See [restrictions on field names](https://www.mongodb.com/docs/manual/reference/limits/#Restrictions-on-Field-Names)
3432
3445
  * @param {Boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
3433
- * @param {Function} [fn] optional callback
3434
3446
  * @method save
3435
3447
  * @memberOf Document
3436
3448
  * @instance
3437
3449
  * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
3438
- * @return {Promise|undefined} Returns undefined if used with callback or a Promise otherwise.
3450
+ * @return {Promise}
3439
3451
  * @api public
3440
3452
  * @see middleware https://mongoosejs.com/docs/middleware.html
3441
3453
  */
@@ -454,7 +454,6 @@ function format(obj, sub, color, shell) {
454
454
  /**
455
455
  * Retrieves information about this collections indexes.
456
456
  *
457
- * @param {Function} callback
458
457
  * @method getIndexes
459
458
  * @api public
460
459
  */
@@ -426,9 +426,6 @@ function _setClient(conn, client, options, dbName) {
426
426
  }
427
427
 
428
428
  conn.onOpen();
429
- if (client.topology?.s?.state === 'connected') {
430
- conn._lastHeartbeatAt = Date.now();
431
- }
432
429
 
433
430
  for (const i in conn.collections) {
434
431
  if (utils.object.hasOwnProperty(conn.collections, i)) {
@@ -437,7 +434,6 @@ function _setClient(conn, client, options, dbName) {
437
434
  }
438
435
  }
439
436
 
440
-
441
437
  /*!
442
438
  * Module exports.
443
439
  */
@@ -11,6 +11,7 @@ const isObject = require('./isObject');
11
11
  const isPOJO = require('./isPOJO');
12
12
  const symbols = require('./symbols');
13
13
  const trustedSymbol = require('./query/trusted').trustedSymbol;
14
+ const BSON = require('bson');
14
15
 
15
16
  /**
16
17
  * Object clone with Mongoose natives support.
@@ -30,6 +31,10 @@ function clone(obj, options, isArrayChild) {
30
31
  if (obj == null) {
31
32
  return obj;
32
33
  }
34
+
35
+ if (isBsonType(obj, 'Double')) {
36
+ return new BSON.Double(obj.value);
37
+ }
33
38
  if (typeof obj === 'number' || typeof obj === 'string' || typeof obj === 'boolean' || typeof obj === 'bigint') {
34
39
  return obj;
35
40
  }