mongoose 8.5.4 → 8.6.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.
@@ -33,15 +33,18 @@ class ChangeStream extends EventEmitter {
33
33
  );
34
34
  }
35
35
 
36
- // This wrapper is necessary because of buffering.
37
- changeStreamThunk((err, driverChangeStream) => {
38
- if (err != null) {
39
- this.emit('error', err);
40
- return;
41
- }
36
+ this.$driverChangeStreamPromise = new Promise((resolve, reject) => {
37
+ // This wrapper is necessary because of buffering.
38
+ changeStreamThunk((err, driverChangeStream) => {
39
+ if (err != null) {
40
+ this.emit('error', err);
41
+ return reject(err);
42
+ }
42
43
 
43
- this.driverChangeStream = driverChangeStream;
44
- this.emit('ready');
44
+ this.driverChangeStream = driverChangeStream;
45
+ this.emit('ready');
46
+ resolve();
47
+ });
45
48
  });
46
49
  }
47
50
 
@@ -53,20 +56,23 @@ class ChangeStream extends EventEmitter {
53
56
  this.bindedEvents = true;
54
57
 
55
58
  if (this.driverChangeStream == null) {
56
- this.once('ready', () => {
57
- this.driverChangeStream.on('close', () => {
58
- this.closed = true;
59
- });
59
+ this.$driverChangeStreamPromise.then(
60
+ () => {
61
+ this.driverChangeStream.on('close', () => {
62
+ this.closed = true;
63
+ });
60
64
 
61
- driverChangeStreamEvents.forEach(ev => {
62
- this.driverChangeStream.on(ev, data => {
63
- if (data != null && data.fullDocument != null && this.options && this.options.hydrate) {
64
- data.fullDocument = this.options.model.hydrate(data.fullDocument);
65
- }
66
- this.emit(ev, data);
65
+ driverChangeStreamEvents.forEach(ev => {
66
+ this.driverChangeStream.on(ev, data => {
67
+ if (data != null && data.fullDocument != null && this.options && this.options.hydrate) {
68
+ data.fullDocument = this.options.model.hydrate(data.fullDocument);
69
+ }
70
+ this.emit(ev, data);
71
+ });
67
72
  });
68
- });
69
- });
73
+ },
74
+ () => {} // No need to register events if opening change stream failed
75
+ );
70
76
 
71
77
  return;
72
78
  }
@@ -142,8 +148,12 @@ class ChangeStream extends EventEmitter {
142
148
  this.closed = true;
143
149
  if (this.driverChangeStream) {
144
150
  return this.driverChangeStream.close();
151
+ } else {
152
+ return this.$driverChangeStreamPromise.then(
153
+ () => this.driverChangeStream.close(),
154
+ () => {} // No need to close if opening the change stream failed
155
+ );
145
156
  }
146
- return Promise.resolve();
147
157
  }
148
158
  }
149
159
 
@@ -10,6 +10,7 @@ const eachAsync = require('../helpers/cursor/eachAsync');
10
10
  const helpers = require('../queryHelpers');
11
11
  const kareem = require('kareem');
12
12
  const immediate = require('../helpers/immediate');
13
+ const { once } = require('node:events');
13
14
  const util = require('util');
14
15
 
15
16
  /**
@@ -42,6 +43,7 @@ function QueryCursor(query) {
42
43
  this.cursor = null;
43
44
  this.skipped = false;
44
45
  this.query = query;
46
+ this._closed = false;
45
47
  const model = query.model;
46
48
  this._mongooseOptions = {};
47
49
  this._transforms = [];
@@ -135,6 +137,25 @@ QueryCursor.prototype._read = function() {
135
137
  });
136
138
  };
137
139
 
140
+ /**
141
+ * Returns the underlying cursor from the MongoDB Node driver that this cursor uses.
142
+ *
143
+ * @method getDriverCursor
144
+ * @memberOf QueryCursor
145
+ * @returns {Cursor} MongoDB Node driver cursor instance
146
+ * @instance
147
+ * @api public
148
+ */
149
+
150
+ QueryCursor.prototype.getDriverCursor = async function getDriverCursor() {
151
+ if (this.cursor) {
152
+ return this.cursor;
153
+ }
154
+
155
+ await once(this, 'cursor');
156
+ return this.cursor;
157
+ };
158
+
138
159
  /**
139
160
  * Registers a transform function which subsequently maps documents retrieved
140
161
  * via the streams interface or `.next()`
@@ -209,6 +230,7 @@ QueryCursor.prototype.close = async function close() {
209
230
  }
210
231
  try {
211
232
  await this.cursor.close();
233
+ this._closed = true;
212
234
  this.emit('close');
213
235
  } catch (error) {
214
236
  this.listeners('error').length > 0 && this.emit('error', error);
@@ -246,6 +268,9 @@ QueryCursor.prototype.next = async function next() {
246
268
  if (typeof arguments[0] === 'function') {
247
269
  throw new MongooseError('QueryCursor.prototype.next() no longer accepts a callback');
248
270
  }
271
+ if (this._closed) {
272
+ throw new MongooseError('Cannot call `next()` on a closed cursor');
273
+ }
249
274
  return new Promise((resolve, reject) => {
250
275
  _next(this, function(error, doc) {
251
276
  if (error) {
package/lib/document.js CHANGED
@@ -842,7 +842,7 @@ function init(self, obj, doc, opts, prefix) {
842
842
  */
843
843
 
844
844
  Document.prototype.updateOne = function updateOne(doc, options, callback) {
845
- const query = this.constructor.updateOne({ _id: this._id }, doc, options);
845
+ const query = this.constructor.updateOne({ _id: this._doc._id }, doc, options);
846
846
  const self = this;
847
847
  query.pre(function queryPreUpdateOne(cb) {
848
848
  self.constructor._middleware.execPre('updateOne', self, [self], cb);
@@ -883,7 +883,7 @@ Document.prototype.updateOne = function updateOne(doc, options, callback) {
883
883
 
884
884
  Document.prototype.replaceOne = function replaceOne() {
885
885
  const args = [...arguments];
886
- args.unshift({ _id: this._id });
886
+ args.unshift({ _id: this._doc._id });
887
887
  return this.constructor.replaceOne.apply(this.constructor, args);
888
888
  };
889
889
 
@@ -3050,7 +3050,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
3050
3050
  } else if (val != null && val.$__ != null && val.$__.wasPopulated) {
3051
3051
  // Array paths, like `somearray.1`, do not show up as populated with `$populated()`,
3052
3052
  // so in that case pull out the document's id
3053
- val = val._id;
3053
+ val = val._doc._id;
3054
3054
  }
3055
3055
  const scope = _this.$__.pathsToScopes != null && path in _this.$__.pathsToScopes ?
3056
3056
  _this.$__.pathsToScopes[path] :
@@ -15,7 +15,7 @@ class ParallelSaveError extends MongooseError {
15
15
  */
16
16
  constructor(doc) {
17
17
  const msg = 'Can\'t save() the same doc multiple times in parallel. Document: ';
18
- super(msg + doc._id);
18
+ super(msg + doc._doc._id);
19
19
  }
20
20
  }
21
21
 
@@ -16,7 +16,7 @@ class ParallelValidateError extends MongooseError {
16
16
  */
17
17
  constructor(doc) {
18
18
  const msg = 'Can\'t validate() the same doc multiple times in parallel. Document: ';
19
- super(msg + doc._id);
19
+ super(msg + doc._doc._id);
20
20
  }
21
21
  }
22
22
 
@@ -17,7 +17,7 @@ class VersionError extends MongooseError {
17
17
  */
18
18
  constructor(doc, currentVersion, modifiedPaths) {
19
19
  const modifiedPathsStr = modifiedPaths.join(', ');
20
- super('No matching document found for id "' + doc._id +
20
+ super('No matching document found for id "' + doc._doc._id +
21
21
  '" version ' + currentVersion + ' modifiedPaths "' + modifiedPathsStr + '"');
22
22
  this.version = currentVersion;
23
23
  this.modifiedPaths = modifiedPaths;
@@ -115,6 +115,9 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
115
115
  }
116
116
  }
117
117
 
118
+ // Shallow clone `obj` so we can add additional properties without modifying original
119
+ // schema. `Schema.prototype.clone()` copies `obj` by reference, no cloning.
120
+ schema.obj = { ...schema.obj };
118
121
  mergeDiscriminatorSchema(schema, baseSchema);
119
122
 
120
123
  // Clean up conflicting paths _after_ merging re: gh-6076
@@ -626,7 +626,7 @@ function _getLocalFieldValues(doc, localField, model, options, virtual, schema)
626
626
 
627
627
  function convertTo_id(val, schema) {
628
628
  if (val != null && val.$__ != null) {
629
- return val._id;
629
+ return val._doc._id;
630
630
  }
631
631
  if (val != null && val._id != null && (schema == null || !schema.$isSchemaMap)) {
632
632
  return val._id;
@@ -636,7 +636,7 @@ function convertTo_id(val, schema) {
636
636
  const rawVal = val.__array != null ? val.__array : val;
637
637
  for (let i = 0; i < rawVal.length; ++i) {
638
638
  if (rawVal[i] != null && rawVal[i].$__ != null) {
639
- rawVal[i] = rawVal[i]._id;
639
+ rawVal[i] = rawVal[i]._doc._id;
640
640
  }
641
641
  }
642
642
  if (utils.isMongooseArray(val) && val.$schema()) {
@@ -17,11 +17,11 @@ const utils = require('../../utils');
17
17
  */
18
18
 
19
19
  module.exports = function markArraySubdocsPopulated(doc, populated) {
20
- if (doc._id == null || populated == null || populated.length === 0) {
20
+ if (doc._doc._id == null || populated == null || populated.length === 0) {
21
21
  return;
22
22
  }
23
23
 
24
- const id = String(doc._id);
24
+ const id = String(doc._doc._id);
25
25
  for (const item of populated) {
26
26
  if (item.isVirtual) {
27
27
  continue;
package/lib/model.js CHANGED
@@ -2115,17 +2115,21 @@ Model.countDocuments = function countDocuments(conditions, options) {
2115
2115
  *
2116
2116
  * @param {String} field
2117
2117
  * @param {Object} [conditions] optional
2118
+ * @param {Object} [options] optional
2118
2119
  * @return {Query}
2119
2120
  * @api public
2120
2121
  */
2121
2122
 
2122
- Model.distinct = function distinct(field, conditions) {
2123
+ Model.distinct = function distinct(field, conditions, options) {
2123
2124
  _checkContext(this, 'distinct');
2124
- if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {
2125
+ if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
2125
2126
  throw new MongooseError('Model.distinct() no longer accepts a callback');
2126
2127
  }
2127
2128
 
2128
2129
  const mq = new this.Query({}, {}, this, this.$__collection);
2130
+ if (options != null) {
2131
+ mq.setOptions(options);
2132
+ }
2129
2133
 
2130
2134
  return mq.distinct(field, conditions);
2131
2135
  };
@@ -2345,7 +2349,7 @@ Model.findByIdAndUpdate = function(id, update, options) {
2345
2349
 
2346
2350
  // if a model is passed in instead of an id
2347
2351
  if (id instanceof Document) {
2348
- id = id._id;
2352
+ id = id._doc._id;
2349
2353
  }
2350
2354
 
2351
2355
  return this.findOneAndUpdate.call(this, { _id: id }, update, options);
@@ -3408,7 +3412,7 @@ Model.bulkSave = async function bulkSave(documents, options) {
3408
3412
  documents.map(async(document) => {
3409
3413
  const documentError = bulkWriteError && bulkWriteError.writeErrors.find(writeError => {
3410
3414
  const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
3411
- return writeErrorDocumentId.toString() === document._id.toString();
3415
+ return writeErrorDocumentId.toString() === document._doc._id.toString();
3412
3416
  });
3413
3417
 
3414
3418
  if (documentError == null) {
@@ -4436,7 +4440,7 @@ function _assign(model, vals, mod, assignmentOpts) {
4436
4440
 
4437
4441
  for (let __val of _val) {
4438
4442
  if (__val instanceof Document) {
4439
- __val = __val._id;
4443
+ __val = __val._doc._id;
4440
4444
  }
4441
4445
  key = String(__val);
4442
4446
  if (rawDocs[key]) {
package/lib/query.js CHANGED
@@ -2777,7 +2777,7 @@ Query.prototype.estimatedDocumentCount = function(options) {
2777
2777
  this.op = 'estimatedDocumentCount';
2778
2778
  this._validateOp();
2779
2779
 
2780
- if (typeof options === 'object' && options != null) {
2780
+ if (options != null) {
2781
2781
  this.setOptions(options);
2782
2782
  }
2783
2783
 
@@ -2836,7 +2836,7 @@ Query.prototype.countDocuments = function(conditions, options) {
2836
2836
  this.merge(conditions);
2837
2837
  }
2838
2838
 
2839
- if (typeof options === 'object' && options != null) {
2839
+ if (options != null) {
2840
2840
  this.setOptions(options);
2841
2841
  }
2842
2842
 
@@ -2874,21 +2874,24 @@ Query.prototype.__distinct = async function __distinct() {
2874
2874
  *
2875
2875
  * #### Example:
2876
2876
  *
2877
+ * distinct(field, conditions, options)
2877
2878
  * distinct(field, conditions)
2878
2879
  * distinct(field)
2879
2880
  * distinct()
2880
2881
  *
2881
2882
  * @param {String} [field]
2882
2883
  * @param {Object|Query} [filter]
2884
+ * @param {Object} [options]
2883
2885
  * @return {Query} this
2884
2886
  * @see distinct https://www.mongodb.com/docs/manual/reference/method/db.collection.distinct/
2885
2887
  * @api public
2886
2888
  */
2887
2889
 
2888
- Query.prototype.distinct = function(field, conditions) {
2890
+ Query.prototype.distinct = function(field, conditions, options) {
2889
2891
  if (typeof field === 'function' ||
2890
2892
  typeof conditions === 'function' ||
2891
- typeof arguments[2] === 'function') {
2893
+ typeof options === 'function' ||
2894
+ typeof arguments[3] === 'function') {
2892
2895
  throw new MongooseError('Query.prototype.distinct() no longer accepts a callback');
2893
2896
  }
2894
2897
 
@@ -2907,6 +2910,10 @@ Query.prototype.distinct = function(field, conditions) {
2907
2910
  this._distinct = field;
2908
2911
  }
2909
2912
 
2913
+ if (options != null) {
2914
+ this.setOptions(options);
2915
+ }
2916
+
2910
2917
  return this;
2911
2918
  };
2912
2919
 
@@ -3380,9 +3387,9 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
3380
3387
  if (!this._update || Object.keys(this._update).length === 0) {
3381
3388
  if (options.upsert) {
3382
3389
  // still need to do the upsert to empty doc
3383
- const doc = clone(this._update);
3384
- delete doc._id;
3385
- this._update = { $set: doc };
3390
+ const $set = clone(this._update);
3391
+ delete $set._id;
3392
+ this._update = { $set };
3386
3393
  } else {
3387
3394
  this._executionStack = null;
3388
3395
  const res = await this._findOne();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "8.5.4",
4
+ "version": "8.6.0",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "bson": "^6.7.0",
23
23
  "kareem": "2.6.3",
24
- "mongodb": "6.7.0",
24
+ "mongodb": "6.8.0",
25
25
  "mpath": "0.9.0",
26
26
  "mquery": "5.0.0",
27
27
  "ms": "2.1.3",
@@ -67,7 +67,7 @@
67
67
  "tsd": "0.31.1",
68
68
  "typescript": "5.5.4",
69
69
  "uuid": "10.0.0",
70
- "webpack": "5.93.0"
70
+ "webpack": "5.94.0"
71
71
  },
72
72
  "directories": {
73
73
  "lib": "./lib/mongoose"
@@ -22,7 +22,7 @@ declare module 'mongoose' {
22
22
  constructor(doc?: any);
23
23
 
24
24
  /** This documents _id. */
25
- _id?: T;
25
+ _id: T;
26
26
 
27
27
  /** This documents __v. */
28
28
  __v?: any;
@@ -259,11 +259,14 @@ declare module 'mongoose' {
259
259
  set(value: string | Record<string, any>): this;
260
260
 
261
261
  /** The return value of this method is used in calls to JSON.stringify(doc). */
262
+ toJSON(options?: ToObjectOptions & { flattenMaps?: true }): FlattenMaps<Require_id<DocType>>;
263
+ toJSON(options: ToObjectOptions & { flattenMaps: false }): Require_id<DocType>;
262
264
  toJSON<T = Require_id<DocType>>(options?: ToObjectOptions & { flattenMaps?: true }): FlattenMaps<T>;
263
265
  toJSON<T = Require_id<DocType>>(options: ToObjectOptions & { flattenMaps: false }): T;
264
266
 
265
267
  /** Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)). */
266
- toObject<T = Require_id<DocType>>(options?: ToObjectOptions): Require_id<T>;
268
+ toObject(options?: ToObjectOptions): Require_id<DocType>;
269
+ toObject<T>(options?: ToObjectOptions): Require_id<T>;
267
270
 
268
271
  /** Clears the modified state on the specified path. */
269
272
  unmarkModified<T extends keyof DocType>(path: T): void;
@@ -1,4 +1,5 @@
1
1
  import {
2
+ IsPathRequired,
2
3
  IsSchemaTypeFromBuiltinClass,
3
4
  RequiredPaths,
4
5
  OptionalPaths,
@@ -14,7 +15,9 @@ declare module 'mongoose' {
14
15
  [
15
16
  K in keyof (RequiredPaths<DocDefinition, TSchemaOptions['typeKey']> &
16
17
  OptionalPaths<DocDefinition, TSchemaOptions['typeKey']>)
17
- ]: ObtainRawDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']>;
18
+ ]: IsPathRequired<DocDefinition[K], TSchemaOptions['typeKey']> extends true
19
+ ? ObtainRawDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']>
20
+ : ObtainRawDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']> | null;
18
21
  }, TSchemaOptions>;
19
22
 
20
23
  /**
@@ -87,8 +87,8 @@ declare module 'mongoose' {
87
87
  'createdAt' | 'updatedAt'
88
88
  > as TimestampOptions[K] extends true
89
89
  ? K
90
- : TimestampOptions[K] extends string
91
- ? TimestampOptions[K]
90
+ : TimestampOptions[K] extends `${infer TimestampValue}`
91
+ ? TimestampValue
92
92
  : never]: NativeDate;
93
93
  } & T
94
94
  : T