mongoose 8.5.2 → 8.5.4

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/cast.js CHANGED
@@ -8,6 +8,7 @@ const CastError = require('./error/cast');
8
8
  const StrictModeError = require('./error/strict');
9
9
  const Types = require('./schema/index');
10
10
  const cast$expr = require('./helpers/query/cast$expr');
11
+ const castString = require('./cast/string');
11
12
  const castTextSearch = require('./schema/operators/text');
12
13
  const get = require('./helpers/get');
13
14
  const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue');
@@ -95,6 +96,9 @@ module.exports = function cast(schema, obj, options, context) {
95
96
  val = cast(schema, val, options, context);
96
97
  } else if (path === '$text') {
97
98
  val = castTextSearch(val, path);
99
+ } else if (path === '$comment' && !schema.paths.hasOwnProperty('$comment')) {
100
+ val = castString(val, path);
101
+ obj[path] = val;
98
102
  } else {
99
103
  if (!schema) {
100
104
  // no casting for Mixed types
package/lib/document.js CHANGED
@@ -2770,15 +2770,6 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip) {
2770
2770
  }
2771
2771
  }
2772
2772
 
2773
- for (const path of paths) {
2774
- // Single nested paths (paths embedded under single nested subdocs) will
2775
- // be validated on their own when we call `validate()` on the subdoc itself.
2776
- // Re: gh-8468
2777
- if (doc.$__schema.singleNestedPaths.hasOwnProperty(path)) {
2778
- paths.delete(path);
2779
- continue;
2780
- }
2781
- }
2782
2773
 
2783
2774
  if (Array.isArray(pathsToValidate)) {
2784
2775
  paths = _handlePathsToValidate(paths, pathsToValidate);
@@ -2800,7 +2791,10 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip) {
2800
2791
  _v = _v.toObject({ transform: false });
2801
2792
  }
2802
2793
  const flat = flatten(_v, pathToCheck, flattenOptions, doc.$__schema);
2803
- Object.keys(flat).forEach(addToPaths);
2794
+ // Single nested paths (paths embedded under single nested subdocs) will
2795
+ // be validated on their own when we call `validate()` on the subdoc itself.
2796
+ // Re: gh-8468
2797
+ Object.keys(flat).filter(path => !doc.$__schema.singleNestedPaths.hasOwnProperty(path)).forEach(addToPaths);
2804
2798
  }
2805
2799
  }
2806
2800
 
@@ -3801,7 +3795,7 @@ Document.prototype.$__handleReject = function handleReject(err) {
3801
3795
  };
3802
3796
 
3803
3797
  /**
3804
- * Internal helper for toObject() and toJSON() that doesn't manipulate options
3798
+ * Internal common logic for toObject() and toJSON()
3805
3799
  *
3806
3800
  * @return {Object}
3807
3801
  * @api private
@@ -3834,14 +3828,17 @@ Document.prototype.$toObject = function(options, json) {
3834
3828
  }
3835
3829
 
3836
3830
  const depopulate = options._calledWithOptions.depopulate
3837
- ?? options._parentOptions?.depopulate
3838
3831
  ?? defaultOptions?.depopulate
3832
+ ?? options.depopulate
3839
3833
  ?? false;
3840
3834
  // _isNested will only be true if this is not the top level document, we
3841
3835
  // should never depopulate the top-level document
3842
3836
  if (depopulate && options._isNested && this.$__.wasPopulated) {
3843
3837
  return clone(this.$__.wasPopulated.value || this._doc._id, options);
3844
3838
  }
3839
+ if (depopulate) {
3840
+ options.depopulate = true;
3841
+ }
3845
3842
 
3846
3843
  // merge default options with input options.
3847
3844
  if (defaultOptions != null) {
@@ -3855,7 +3852,9 @@ Document.prototype.$toObject = function(options, json) {
3855
3852
  options.json = json;
3856
3853
  options.minimize = _minimize;
3857
3854
 
3858
- options._parentOptions = options;
3855
+ const parentOptions = options._parentOptions;
3856
+ // Parent options should only bubble down for subdocuments, not populated docs
3857
+ options._parentOptions = this.$isSubdocument ? options : null;
3859
3858
 
3860
3859
  options._skipSingleNestedGetters = false;
3861
3860
  // remember the root transform function
@@ -3886,6 +3885,7 @@ Document.prototype.$toObject = function(options, json) {
3886
3885
 
3887
3886
  const virtuals = options._calledWithOptions.virtuals
3888
3887
  ?? defaultOptions.virtuals
3888
+ ?? parentOptions?.virtuals
3889
3889
  ?? undefined;
3890
3890
 
3891
3891
  if (virtuals || (getters && virtuals !== false)) {
@@ -4768,7 +4768,23 @@ Document.prototype.depopulate = function(path) {
4768
4768
  continue;
4769
4769
  }
4770
4770
  delete populated[key];
4771
- utils.setValue(key, populatedIds, this._doc);
4771
+ if (Array.isArray(populatedIds)) {
4772
+ const arr = utils.getValue(key, this._doc);
4773
+ if (arr.isMongooseArray) {
4774
+ const rawArray = arr.__array;
4775
+ for (let i = 0; i < rawArray.length; ++i) {
4776
+ const subdoc = rawArray[i];
4777
+ if (subdoc == null) {
4778
+ continue;
4779
+ }
4780
+ rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id;
4781
+ }
4782
+ } else {
4783
+ utils.setValue(key, populatedIds, this._doc);
4784
+ }
4785
+ } else {
4786
+ utils.setValue(key, populatedIds, this._doc);
4787
+ }
4772
4788
  }
4773
4789
  return this;
4774
4790
  }
@@ -4781,7 +4797,23 @@ Document.prototype.depopulate = function(path) {
4781
4797
  delete this.$$populatedVirtuals[singlePath];
4782
4798
  delete this._doc[singlePath];
4783
4799
  } else if (populatedIds) {
4784
- utils.setValue(singlePath, populatedIds, this._doc);
4800
+ if (Array.isArray(populatedIds)) {
4801
+ const arr = utils.getValue(singlePath, this._doc);
4802
+ if (arr.isMongooseArray) {
4803
+ const rawArray = arr.__array;
4804
+ for (let i = 0; i < rawArray.length; ++i) {
4805
+ const subdoc = rawArray[i];
4806
+ if (subdoc == null) {
4807
+ continue;
4808
+ }
4809
+ rawArray[i] = subdoc instanceof Document ? subdoc._doc._id : subdoc._id;
4810
+ }
4811
+ } else {
4812
+ utils.setValue(singlePath, populatedIds, this._doc);
4813
+ }
4814
+ } else {
4815
+ utils.setValue(singlePath, populatedIds, this._doc);
4816
+ }
4785
4817
  }
4786
4818
  }
4787
4819
  return this;
package/lib/model.js CHANGED
@@ -10,6 +10,7 @@ const Document = require('./document');
10
10
  const DocumentNotFoundError = require('./error/notFound');
11
11
  const EventEmitter = require('events').EventEmitter;
12
12
  const Kareem = require('kareem');
13
+ const MongooseBulkWriteError = require('./error/bulkWriteError');
13
14
  const MongooseError = require('./error/index');
14
15
  const ObjectParameterError = require('./error/objectParameter');
15
16
  const OverwriteModelError = require('./error/overwriteModel');
@@ -62,7 +63,6 @@ const setDottedPath = require('./helpers/path/setDottedPath');
62
63
  const STATES = require('./connectionState');
63
64
  const util = require('util');
64
65
  const utils = require('./utils');
65
- const MongooseBulkWriteError = require('./error/bulkWriteError');
66
66
  const minimize = require('./helpers/minimize');
67
67
 
68
68
  const modelCollectionSymbol = Symbol('mongoose#Model#collection');
@@ -3202,8 +3202,8 @@ function _setIsNew(doc, val) {
3202
3202
  * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://www.mongodb.com/docs/manual/reference/write-concern/#j-option)
3203
3203
  * @param {Boolean} [options.skipValidation=false] Set to true to skip Mongoose schema validation on bulk write operations. Mongoose currently runs validation on `insertOne` and `replaceOne` operations by default.
3204
3204
  * @param {Boolean} [options.bypassDocumentValidation=false] If true, disable [MongoDB server-side schema validation](https://www.mongodb.com/docs/manual/core/schema-validation/) for all writes in this bulk.
3205
- * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully.
3206
- * @param {Boolean} [options.strict=null] Overwrites the [`strict` option](https://mongoosejs.com/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk.
3205
+ * @param {Boolean} [options.throwOnValidationError=false] If true and `ordered: false`, throw an error if one of the operations failed validation, but all valid operations completed successfully. Note that Mongoose will still send all valid operations to the MongoDB server.
3206
+ * @param {Boolean|"throw"} [options.strict=null] Overwrites the [`strict` option](https://mongoosejs.com/docs/guide.html#strict) on schema. If false, allows filtering and writing fields not defined in the schema for all writes in this bulk.
3207
3207
  * @return {Promise} resolves to a [`BulkWriteOpResult`](https://mongodb.github.io/node-mongodb-native/4.9/classes/BulkWriteResult.html) if the operation succeeds
3208
3208
  * @api public
3209
3209
  */
@@ -4468,7 +4468,7 @@ function _assign(model, vals, mod, assignmentOpts) {
4468
4468
  rawOrder[key].push(i);
4469
4469
  } else if (isVirtual ||
4470
4470
  rawDocs[key].constructor !== val.constructor ||
4471
- String(rawDocs[key]._doc._id) !== String(val._doc._id)) {
4471
+ (rawDocs[key] instanceof Document ? String(rawDocs[key]._doc._id) : String(rawDocs[key]._id)) !== (val instanceof Document ? String(val._doc._id) : String(val._id))) {
4472
4472
  // May need to store multiple docs with the same id if there's multiple models
4473
4473
  // if we have discriminators or a ref function. But avoid converting to an array
4474
4474
  // if we have multiple queries on the same model because of `perDocumentLimit` re: gh-9906
package/lib/mongoose.js CHANGED
@@ -358,7 +358,7 @@ Mongoose.prototype.get = Mongoose.prototype.set;
358
358
  *
359
359
  * // initialize now, connect later
360
360
  * db = mongoose.createConnection();
361
- * db.openUri('127.0.0.1', 'database', port, [opts]);
361
+ * await db.openUri('mongodb://127.0.0.1:27017/database');
362
362
  *
363
363
  * @param {String} uri mongodb URI to connect to
364
364
  * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html), except for 4 mongoose-specific options explained below.
@@ -406,11 +406,10 @@ Mongoose.prototype.createConnection = function(uri, options) {
406
406
  * // with options
407
407
  * mongoose.connect(uri, options);
408
408
  *
409
- * // optional callback that gets fired when initial connection completed
409
+ * // Using `await` throws "MongooseServerSelectionError: Server selection timed out after 30000 ms"
410
+ * // if Mongoose can't connect.
410
411
  * const uri = 'mongodb://nonexistent.domain:27000';
411
- * mongoose.connect(uri, function(error) {
412
- * // if error is truthy, the initial connection failed.
413
- * })
412
+ * await mongoose.connect(uri);
414
413
  *
415
414
  * @param {String} uri mongodb URI to connect to
416
415
  * @param {Object} [options] passed down to the [MongoDB driver's `connect()` function](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/MongoClientOptions.html), except for 4 mongoose-specific options explained below.
package/lib/query.js CHANGED
@@ -2446,19 +2446,23 @@ Query.prototype.merge = function(source) {
2446
2446
  }
2447
2447
 
2448
2448
  opts.omit = {};
2449
- if (source.$and) {
2449
+ if (Array.isArray(source.$and)) {
2450
2450
  opts.omit['$and'] = true;
2451
2451
  if (!this._conditions) {
2452
2452
  this._conditions = {};
2453
2453
  }
2454
- this._conditions.$and = (this._conditions.$and || []).concat(source.$and);
2454
+ this._conditions.$and = (this._conditions.$and || []).concat(
2455
+ source.$and.map(el => utils.isPOJO(el) ? utils.merge({}, el) : el)
2456
+ );
2455
2457
  }
2456
- if (source.$or) {
2458
+ if (Array.isArray(source.$or)) {
2457
2459
  opts.omit['$or'] = true;
2458
2460
  if (!this._conditions) {
2459
2461
  this._conditions = {};
2460
2462
  }
2461
- this._conditions.$or = (this._conditions.$or || []).concat(source.$or);
2463
+ this._conditions.$or = (this._conditions.$or || []).concat(
2464
+ source.$or.map(el => utils.isPOJO(el) ? utils.merge({}, el) : el)
2465
+ );
2462
2466
  }
2463
2467
 
2464
2468
  // plain object
@@ -15,7 +15,7 @@ const castString = require('../../cast/string');
15
15
  * @api private
16
16
  */
17
17
 
18
- module.exports = function(val, path) {
18
+ module.exports = function castTextSearch(val, path) {
19
19
  if (val == null || typeof val !== 'object') {
20
20
  throw new CastError('$text', val, path);
21
21
  }
@@ -721,6 +721,7 @@ const methods = {
721
721
  }
722
722
 
723
723
  this._registerAtomic('$push', atomic);
724
+
724
725
  return ret;
725
726
  },
726
727
 
package/lib/types/map.js CHANGED
@@ -68,10 +68,17 @@ class MongooseMap extends Map {
68
68
  * and change tracking. Note that Mongoose maps _only_ support strings and
69
69
  * ObjectIds as keys.
70
70
  *
71
+ * Keys also cannot:
72
+ * - be named after special properties `prototype`, `constructor`, and `__proto__`
73
+ * - start with a dollar sign (`$`)
74
+ * - contain any dots (`.`)
75
+ *
71
76
  * #### Example:
72
77
  *
73
78
  * doc.myMap.set('test', 42); // works
74
79
  * doc.myMap.set({ obj: 42 }, 42); // Throws "Mongoose maps only support string keys"
80
+ * doc.myMap.set(10, 42); // Throws "Mongoose maps only support string keys"
81
+ * doc.myMap.set("$test", 42); // Throws "Mongoose maps do not support keys that start with "$", got "$test""
75
82
  *
76
83
  * @api public
77
84
  * @method set
package/lib/utils.js CHANGED
@@ -53,6 +53,12 @@ exports.toCollectionName = function(name, pluralize) {
53
53
  return name;
54
54
  }
55
55
  if (typeof pluralize === 'function') {
56
+ if (typeof name !== 'string') {
57
+ throw new TypeError('Collection name must be a string');
58
+ }
59
+ if (name.length === 0) {
60
+ throw new TypeError('Collection name cannot be empty');
61
+ }
56
62
  return pluralize(name);
57
63
  }
58
64
  return name;
@@ -301,6 +307,8 @@ exports.merge = function merge(to, from, options, path) {
301
307
  to[key] = from[key];
302
308
  }
303
309
  }
310
+
311
+ return to;
304
312
  };
305
313
 
306
314
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "8.5.2",
4
+ "version": "8.5.4",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@babel/core": "7.24.7",
32
- "@babel/preset-env": "7.24.7",
32
+ "@babel/preset-env": "7.25.3",
33
33
  "@typescript-eslint/eslint-plugin": "^6.21.0",
34
34
  "@typescript-eslint/parser": "^6.21.0",
35
35
  "acquit": "1.3.0",
@@ -49,13 +49,13 @@
49
49
  "eslint-plugin-mocha-no-only": "1.2.0",
50
50
  "express": "^4.19.2",
51
51
  "fs-extra": "~11.2.0",
52
- "highlight.js": "11.9.0",
52
+ "highlight.js": "11.10.0",
53
53
  "lodash.isequal": "4.5.0",
54
54
  "lodash.isequalwith": "4.4.0",
55
55
  "markdownlint-cli2": "^0.13.0",
56
- "marked": "4.3.0",
56
+ "marked": "14.0.0",
57
57
  "mkdirp": "^3.0.1",
58
- "mocha": "10.6.0",
58
+ "mocha": "10.7.0",
59
59
  "moment": "2.30.1",
60
60
  "mongodb-memory-server": "10.0.0",
61
61
  "ncp": "^2.0.0",
@@ -65,9 +65,9 @@
65
65
  "sinon": "18.0.0",
66
66
  "stream-browserify": "3.0.0",
67
67
  "tsd": "0.31.1",
68
- "typescript": "5.5.3",
68
+ "typescript": "5.5.4",
69
69
  "uuid": "10.0.0",
70
- "webpack": "5.92.1"
70
+ "webpack": "5.93.0"
71
71
  },
72
72
  "directories": {
73
73
  "lib": "./lib/mongoose"
@@ -72,7 +72,7 @@ declare module 'mongoose' {
72
72
  readonly config: any;
73
73
 
74
74
  /** The mongodb.Db instance, set when the connection is opened */
75
- readonly db: mongodb.Db;
75
+ readonly db: mongodb.Db | undefined;
76
76
 
77
77
  /**
78
78
  * Helper for `createCollection()`. Will explicitly create the given collection
package/types/cursor.d.ts CHANGED
@@ -51,7 +51,7 @@ declare module 'mongoose' {
51
51
  * Get the next document from this cursor. Will return `null` when there are
52
52
  * no documents left.
53
53
  */
54
- next(): Promise<DocType>;
54
+ next(): Promise<DocType | null>;
55
55
 
56
56
  options: Options;
57
57
  }
package/types/index.d.ts CHANGED
@@ -157,8 +157,37 @@ declare module 'mongoose' {
157
157
  >
158
158
  >
159
159
  >;
160
- export type HydratedSingleSubdocument<DocType, TOverrides = {}> = Types.Subdocument<unknown, Record<string, never>, DocType> & Require_id<DocType> & TOverrides;
161
- export type HydratedArraySubdocument<DocType, TOverrides = {}> = Types.ArraySubdocument<unknown, Record<string, never>, DocType> & Require_id<DocType> & TOverrides;
160
+ export type HydratedSingleSubdocument<
161
+ DocType,
162
+ TOverrides = {}
163
+ > = IfAny<
164
+ DocType,
165
+ any,
166
+ TOverrides extends Record<string, never> ?
167
+ Types.Subdocument<unknown, Record<string, never>, DocType> & Require_id<DocType> :
168
+ IfAny<
169
+ TOverrides,
170
+ Types.Subdocument<unknown, Record<string, never>, DocType> & Require_id<DocType>,
171
+ Types.Subdocument<unknown, Record<string, never>, DocType> & MergeType<
172
+ Require_id<DocType>,
173
+ TOverrides
174
+ >
175
+ >
176
+ >;
177
+ export type HydratedArraySubdocument<DocType, TOverrides = {}> = IfAny<
178
+ DocType,
179
+ any,
180
+ TOverrides extends Record<string, never> ?
181
+ Types.ArraySubdocument<unknown, Record<string, never>, DocType> & Require_id<DocType> :
182
+ IfAny<
183
+ TOverrides,
184
+ Types.ArraySubdocument<unknown, Record<string, never>, DocType> & Require_id<DocType>,
185
+ Types.ArraySubdocument<unknown, Record<string, never>, DocType> & MergeType<
186
+ Require_id<DocType>,
187
+ TOverrides
188
+ >
189
+ >
190
+ >;
162
191
 
163
192
  export type HydratedDocumentFromSchema<TSchema extends Schema> = HydratedDocument<
164
193
  InferSchemaType<TSchema>,
@@ -10,12 +10,12 @@ declare module 'mongoose' {
10
10
  export type InferRawDocType<
11
11
  DocDefinition,
12
12
  TSchemaOptions extends Record<any, any> = DefaultSchemaOptions
13
- > = {
13
+ > = ApplySchemaOptions<{
14
14
  [
15
15
  K in keyof (RequiredPaths<DocDefinition, TSchemaOptions['typeKey']> &
16
16
  OptionalPaths<DocDefinition, TSchemaOptions['typeKey']>)
17
17
  ]: ObtainRawDocumentPathType<DocDefinition[K], TSchemaOptions['typeKey']>;
18
- };
18
+ }, TSchemaOptions>;
19
19
 
20
20
  /**
21
21
  * @summary Obtains schema Path type.
@@ -74,14 +74,25 @@ declare module 'mongoose' {
74
74
 
75
75
  type ApplySchemaOptions<T, O = DefaultSchemaOptions> = ResolveTimestamps<T, O>;
76
76
 
77
- type ResolveTimestamps<T, O> = O extends { timestamps: true }
77
+ type ResolveTimestamps<T, O> = O extends { methods: any } | { statics: any } | { virtuals: any } | { timestamps?: false } ? T
78
78
  // For some reason, TypeScript sets all the document properties to unknown
79
79
  // if we use methods, statics, or virtuals. So avoid inferring timestamps
80
80
  // if any of these are set for now. See gh-12807
81
- ? O extends { methods: any } | { statics: any } | { virtuals: any }
82
- ? T
83
- : { createdAt: NativeDate; updatedAt: NativeDate; } & T
84
- : T;
81
+ : O extends { timestamps: infer TimestampOptions } ? TimestampOptions extends true
82
+ ? { createdAt: NativeDate; updatedAt: NativeDate; } & T
83
+ : TimestampOptions extends SchemaTimestampsConfig
84
+ ? {
85
+ -readonly [K in keyof Pick<
86
+ TimestampOptions,
87
+ 'createdAt' | 'updatedAt'
88
+ > as TimestampOptions[K] extends true
89
+ ? K
90
+ : TimestampOptions[K] extends string
91
+ ? TimestampOptions[K]
92
+ : never]: NativeDate;
93
+ } & T
94
+ : T
95
+ : T;
85
96
  }
86
97
 
87
98
  type IsPathDefaultUndefined<PathType> = PathType extends { default: undefined } ?
package/types/query.d.ts CHANGED
@@ -431,7 +431,7 @@ declare module 'mongoose' {
431
431
  ): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType, 'findOne', TInstanceMethods>;
432
432
  findOne(
433
433
  filter?: FilterQuery<RawDocType>
434
- ): QueryWithHelpers<DocType | null, RawDocType, THelpers, RawDocType, 'findOne', TInstanceMethods>;
434
+ ): QueryWithHelpers<DocType | null, DocType, THelpers, RawDocType, 'findOne', TInstanceMethods>;
435
435
 
436
436
  /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */
437
437
  findOneAndDelete(