mongoose 9.9.2 → 9.9.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/README.md CHANGED
@@ -340,7 +340,7 @@ new Schema({
340
340
 
341
341
  ### Driver Access
342
342
 
343
- Mongoose is built on top of the [official MongoDB Node.js driver](https://github.com/mongodb/node-mongodb-native). Each mongoose model keeps a reference to a [native MongoDB driver collection](http://mongodb.github.io/node-mongodb-native/2.1/api/Collection.html). The collection object can be accessed using `YourModel.collection`. However, using the collection object directly bypasses all mongoose features, including hooks, validation, etc. The one
343
+ Mongoose is built on top of the [official MongoDB Node.js driver](https://github.com/mongodb/node-mongodb-native). Each mongoose model keeps a reference to a [native MongoDB driver collection](https://mongodb.github.io/node-mongodb-native/7.5/classes/Collection.html). The collection object can be accessed using `YourModel.collection`. However, using the collection object directly bypasses all mongoose features, including hooks, validation, etc. The one
344
344
  notable exception is that `YourModel.collection` still buffers
345
345
  commands. As such, `YourModel.collection.find()` will **not**
346
346
  return a cursor.
@@ -30,6 +30,12 @@ module.exports = function castDouble(val) {
30
30
  assert.ok(false);
31
31
  }
32
32
  } else if (typeof val === 'object') {
33
+ if (Array.isArray(val)) {
34
+ // `[5].valueOf()` returns the array itself, so without this guard a
35
+ // single-element or empty array would fall through to `Number(tempVal)`
36
+ // below and silently coerce instead of throwing.
37
+ assert.ok(false);
38
+ }
33
39
  const tempVal = val.valueOf() ?? val.toString();
34
40
  // ex: { a: 'im an object, valueOf: () => 'helloworld' } // throw an error
35
41
  if (typeof tempVal === 'string') {
package/lib/cast/int32.js CHANGED
@@ -20,6 +20,12 @@ module.exports = function castInt32(val) {
20
20
  if (val === '') {
21
21
  return null;
22
22
  }
23
+ if (Array.isArray(val)) {
24
+ // `Number([5])` is `5` and `Number([])` is `0`, so without this guard a
25
+ // single-element or empty array would silently coerce instead of throwing,
26
+ // unlike every other array input and unlike `castNumber()`'s own guard.
27
+ assert.ok(false);
28
+ }
23
29
 
24
30
  const coercedVal = isBsonType(val, 'Long') ? val.toNumber() : Number(val);
25
31
 
package/lib/document.js CHANGED
@@ -2570,25 +2570,15 @@ Document.prototype.isSelected = function isSelected(path) {
2570
2570
  return path.some(p => this.$__isSelected(p));
2571
2571
  }
2572
2572
 
2573
- const paths = Object.keys(this.$__.selected);
2574
- let inclusive = null;
2573
+ const index = _getProjectionIndex(this);
2574
+ const paths = index.paths;
2575
+ const inclusive = index.inclusive;
2575
2576
 
2576
- if (paths.length === 1 && paths[0] === '_id') {
2577
+ if (index.onlyId) {
2577
2578
  // only _id was selected.
2578
2579
  return this.$__.selected._id === 0;
2579
2580
  }
2580
2581
 
2581
- for (const cur of paths) {
2582
- if (cur === '_id') {
2583
- continue;
2584
- }
2585
- if (!isDefiningProjection(this.$__.selected[cur])) {
2586
- continue;
2587
- }
2588
- inclusive = !!this.$__.selected[cur];
2589
- break;
2590
- }
2591
-
2592
2582
  if (inclusive === null) {
2593
2583
  return true;
2594
2584
  }
@@ -2599,6 +2589,25 @@ Document.prototype.isSelected = function isSelected(path) {
2599
2589
 
2600
2590
  const pathHasDot = path.indexOf('.') !== -1;
2601
2591
 
2592
+ if (!index.hasNestedKey) {
2593
+ // No projection key is nested, so no key can start with `path + '.'` and
2594
+ // only the "is an ancestor of `path` projected?" check below can match.
2595
+ // Every ancestor that matches returns the same value, so we can walk
2596
+ // `path`'s ancestors instead of scanning every projection key. This keeps
2597
+ // `isSelected()` O(depth of path) rather than O(number of projected paths),
2598
+ // which matters because `toObject({ getters: true })` calls this once per
2599
+ // schema path. Re: gh-16373
2600
+ if (pathHasDot) {
2601
+ for (let dot = path.indexOf('.'); dot !== -1; dot = path.indexOf('.', dot + 1)) {
2602
+ const ancestor = path.slice(0, dot);
2603
+ if (ancestor !== '_id' && Object.hasOwn(this.$__.selected, ancestor)) {
2604
+ return inclusive;
2605
+ }
2606
+ }
2607
+ }
2608
+ return !inclusive;
2609
+ }
2610
+
2602
2611
  for (const cur of paths) {
2603
2612
  if (cur === '_id') {
2604
2613
  continue;
@@ -2615,6 +2624,43 @@ Document.prototype.isSelected = function isSelected(path) {
2615
2624
  return !inclusive;
2616
2625
  };
2617
2626
 
2627
+ /*!
2628
+ * Computes and caches the derived properties of `doc.$__.selected` that
2629
+ * `isSelected()` and `isDirectSelected()` would otherwise recompute on every
2630
+ * call. `$__.selected` is assigned once in the Document constructor and never
2631
+ * mutated afterwards, so this can safely be cached for the document's lifetime.
2632
+ */
2633
+
2634
+ function _getProjectionIndex(doc) {
2635
+ if (doc.$__.selectedIndex !== undefined) {
2636
+ return doc.$__.selectedIndex;
2637
+ }
2638
+
2639
+ const selected = doc.$__.selected;
2640
+ const paths = Object.keys(selected);
2641
+ let inclusive = null;
2642
+ let hasNestedKey = false;
2643
+
2644
+ for (const cur of paths) {
2645
+ if (cur === '_id') {
2646
+ continue;
2647
+ }
2648
+ if (cur.indexOf('.') !== -1) {
2649
+ hasNestedKey = true;
2650
+ }
2651
+ if (inclusive === null && isDefiningProjection(selected[cur])) {
2652
+ inclusive = !!selected[cur];
2653
+ }
2654
+ }
2655
+
2656
+ return (doc.$__.selectedIndex = {
2657
+ paths,
2658
+ inclusive,
2659
+ hasNestedKey,
2660
+ onlyId: paths.length === 1 && paths[0] === '_id'
2661
+ });
2662
+ }
2663
+
2618
2664
  Document.prototype.$__isSelected = Document.prototype.isSelected;
2619
2665
 
2620
2666
  /**
@@ -2650,34 +2696,22 @@ Document.prototype.isDirectSelected = function isDirectSelected(path) {
2650
2696
  return path.some(p => this.isDirectSelected(p));
2651
2697
  }
2652
2698
 
2653
- const paths = Object.keys(this.$__.selected);
2654
- let inclusive = null;
2699
+ const index = _getProjectionIndex(this);
2655
2700
 
2656
- if (paths.length === 1 && paths[0] === '_id') {
2701
+ if (index.onlyId) {
2657
2702
  // only _id was selected.
2658
2703
  return this.$__.selected._id === 0;
2659
2704
  }
2660
2705
 
2661
- for (const cur of paths) {
2662
- if (cur === '_id') {
2663
- continue;
2664
- }
2665
- if (!isDefiningProjection(this.$__.selected[cur])) {
2666
- continue;
2667
- }
2668
- inclusive = !!this.$__.selected[cur];
2669
- break;
2670
- }
2671
-
2672
- if (inclusive === null) {
2706
+ if (index.inclusive === null) {
2673
2707
  return true;
2674
2708
  }
2675
2709
 
2676
2710
  if (Object.hasOwn(this.$__.selected, path)) {
2677
- return inclusive;
2711
+ return index.inclusive;
2678
2712
  }
2679
2713
 
2680
- return !inclusive;
2714
+ return !index.inclusive;
2681
2715
  };
2682
2716
 
2683
2717
  /**
package/lib/internal.js CHANGED
@@ -17,6 +17,8 @@ InternalCache.prototype.strictMode = true;
17
17
 
18
18
  InternalCache.prototype.fullPath = undefined;
19
19
  InternalCache.prototype.selected = undefined;
20
+ // Lazily computed index over `selected`, see `_getProjectionIndex()` in document.js
21
+ InternalCache.prototype.selectedIndex = undefined;
20
22
  InternalCache.prototype.shardval = undefined;
21
23
  InternalCache.prototype.saveError = undefined;
22
24
  InternalCache.prototype.validationError = undefined;
package/lib/model.js CHANGED
@@ -1727,6 +1727,7 @@ function _ensureIndexes(model, options, callback) {
1727
1727
  utils.warn('mongoose: Duplicate schema index on ' + JSON.stringify(fields) +
1728
1728
  ' for model "' + model.modelName + '". ' +
1729
1729
  'This is often due to declaring an index using both "index: true" and "schema.index()". ' +
1730
+ 'MongoDB will not create the duplicate index and options on the duplicate definition (such as expireAfterSeconds or unique) will not be applied. ' +
1730
1731
  'Please remove the duplicate index definition.');
1731
1732
  break;
1732
1733
  }
@@ -3654,15 +3655,19 @@ Model.bulkSave = async function bulkSave(documents, options) {
3654
3655
  );
3655
3656
  }
3656
3657
 
3658
+ const failedDocumentIds = documents.length >= 25 ? new Set(bulkWriteError?.writeErrors?.map(writeError => {
3659
+ const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
3660
+ return writeErrorDocumentId.toString();
3661
+ })) : null;
3657
3662
  const successfulDocuments = [];
3658
3663
  for (let i = 0; i < documents.length; i++) {
3659
3664
  const document = documents[i];
3660
- const documentError = bulkWriteError?.writeErrors.find(writeError => {
3665
+ const documentId = document._doc._id.toString();
3666
+ const failed = failedDocumentIds == null ? bulkWriteError?.writeErrors.find(writeError => {
3661
3667
  const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
3662
- return writeErrorDocumentId.toString() === document._doc._id.toString();
3663
- });
3664
-
3665
- if (documentError == null) {
3668
+ return writeErrorDocumentId.toString() === documentId;
3669
+ }) != null : failedDocumentIds.has(documentId);
3670
+ if (!failed) {
3666
3671
  successfulDocuments.push(document);
3667
3672
  }
3668
3673
  }
package/lib/query.js CHANGED
@@ -1754,9 +1754,16 @@ Query.prototype.setOptions = function(options, overwrite) {
1754
1754
  }
1755
1755
  }
1756
1756
 
1757
- if ('setDefaultsOnInsert' in options) {
1758
- this._mongooseOptions.setDefaultsOnInsert = options.setDefaultsOnInsert;
1759
- delete options.setDefaultsOnInsert;
1757
+ if ('cloneUpdate' in options) {
1758
+ this._mongooseOptions.cloneUpdate = options.cloneUpdate;
1759
+ delete options.cloneUpdate;
1760
+ }
1761
+ if ('defaults' in options) {
1762
+ this._mongooseOptions.defaults = options.defaults;
1763
+ // deleting options.defaults will cause 7287 to fail
1764
+ }
1765
+ if (options.lean == null && this.schema && 'lean' in this.schema.options) {
1766
+ this._mongooseOptions.lean = this.schema.options.lean;
1760
1767
  }
1761
1768
  if ('overwriteDiscriminatorKey' in options) {
1762
1769
  this._mongooseOptions.overwriteDiscriminatorKey = options.overwriteDiscriminatorKey;
@@ -1766,13 +1773,9 @@ Query.prototype.setOptions = function(options, overwrite) {
1766
1773
  this._mongooseOptions.overwriteImmutable = options.overwriteImmutable;
1767
1774
  delete options.overwriteImmutable;
1768
1775
  }
1769
- if ('updatePipeline' in options) {
1770
- this._mongooseOptions.updatePipeline = options.updatePipeline;
1771
- delete options.updatePipeline;
1772
- }
1773
- if ('cloneUpdate' in options) {
1774
- this._mongooseOptions.cloneUpdate = options.cloneUpdate;
1775
- delete options.cloneUpdate;
1776
+ if ('sanitizeFilter' in options) {
1777
+ this._mongooseOptions.sanitizeFilter = options.sanitizeFilter;
1778
+ delete options.sanitizeFilter;
1776
1779
  }
1777
1780
  if ('sanitizeProjection' in options) {
1778
1781
  if (options.sanitizeProjection && !this._mongooseOptions.sanitizeProjection) {
@@ -1782,29 +1785,33 @@ Query.prototype.setOptions = function(options, overwrite) {
1782
1785
  this._mongooseOptions.sanitizeProjection = options.sanitizeProjection;
1783
1786
  delete options.sanitizeProjection;
1784
1787
  }
1785
- if ('sanitizeFilter' in options) {
1786
- this._mongooseOptions.sanitizeFilter = options.sanitizeFilter;
1787
- delete options.sanitizeFilter;
1788
+ if ('schemaLevelProjections' in options) {
1789
+ this._mongooseOptions.schemaLevelProjections = options.schemaLevelProjections;
1790
+ delete options.schemaLevelProjections;
1791
+ }
1792
+ if ('setDefaultsOnInsert' in options) {
1793
+ this._mongooseOptions.setDefaultsOnInsert = options.setDefaultsOnInsert;
1794
+ delete options.setDefaultsOnInsert;
1795
+ }
1796
+ if ('strict' in options) {
1797
+ this._mongooseOptions.strict = options.strict;
1798
+ delete options.strict;
1799
+ }
1800
+ if ('strictQuery' in options) {
1801
+ this._mongooseOptions.strictQuery = options.strictQuery;
1802
+ delete options.strictQuery;
1788
1803
  }
1789
1804
  if ('timestamps' in options) {
1790
1805
  this._mongooseOptions.timestamps = options.timestamps;
1791
1806
  delete options.timestamps;
1792
1807
  }
1793
- if ('defaults' in options) {
1794
- this._mongooseOptions.defaults = options.defaults;
1795
- // deleting options.defaults will cause 7287 to fail
1796
- }
1797
1808
  if ('translateAliases' in options) {
1798
1809
  this._mongooseOptions.translateAliases = options.translateAliases;
1799
1810
  delete options.translateAliases;
1800
1811
  }
1801
- if ('schemaLevelProjections' in options) {
1802
- this._mongooseOptions.schemaLevelProjections = options.schemaLevelProjections;
1803
- delete options.schemaLevelProjections;
1804
- }
1805
-
1806
- if (options.lean == null && this.schema && 'lean' in this.schema.options) {
1807
- this._mongooseOptions.lean = this.schema.options.lean;
1812
+ if ('updatePipeline' in options) {
1813
+ this._mongooseOptions.updatePipeline = options.updatePipeline;
1814
+ delete options.updatePipeline;
1808
1815
  }
1809
1816
 
1810
1817
  if (typeof options.limit === 'string') {
@@ -2361,13 +2368,22 @@ Query.prototype._unsetCastError = function _unsetCastError() {
2361
2368
  * Getter/setter around the current mongoose-specific options for this query
2362
2369
  * Below are the current Mongoose-specific options.
2363
2370
  *
2364
- * - `populate`: an array representing what paths will be populated. Should have one entry for each call to [`Query.prototype.populate()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.populate())
2371
+ * - `cloneUpdate`: if `false`, Mongoose will not clone updates before executing the query
2372
+ * - `defaults`: if `false`, Mongoose will not apply defaults to the returned document(s)
2365
2373
  * - `lean`: if truthy, Mongoose will not [hydrate](https://mongoosejs.com/docs/api/model.html#Model.hydrate()) any documents that are returned from this query. See [`Query.prototype.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) for more information.
2366
- * - `strict`: controls how Mongoose handles keys that aren't in the schema for updates. This option is `true` by default, which means Mongoose will silently strip any paths in the update that aren't in the schema. See the [`strict` mode docs](https://mongoosejs.com/docs/guide.html#strict) for more information.
2367
- * - `strictQuery`: controls how Mongoose handles keys that aren't in the schema for the query `filter`. This option is `false` by default, which means Mongoose will allow `Model.find({ foo: 'bar' })` even if `foo` is not in the schema. See the [`strictQuery` docs](https://mongoosejs.com/docs/guide.html#strictQuery) for more information.
2368
2374
  * - `nearSphere`: use `$nearSphere` instead of `near()`. See the [`Query.prototype.nearSphere()` docs](https://mongoosejs.com/docs/api/query.html#Query.prototype.nearSphere())
2375
+ * - `overwriteDiscriminatorKey`: allow setting the discriminator key in the update
2376
+ * - `overwriteImmutable`: allow overwriting properties that are set to `immutable` in the schema
2377
+ * - `populate`: an array representing what paths will be populated. Should have one entry for each call to [`Query.prototype.populate()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.populate())
2378
+ * - `sanitizeFilter`: if truthy, Mongoose will sanitize query filters before executing the query
2379
+ * - `sanitizeProjection`: if truthy, Mongoose will sanitize projections before executing the query
2369
2380
  * - `schemaLevelProjections`: if `false`, Mongoose will not apply schema-level `select: false` or `select: true` for this query
2370
- * - `cloneUpdate`: if `false`, Mongoose will not clone updates before executing the query
2381
+ * - `setDefaultsOnInsert`: if `true`, Mongoose will apply defaults to upserted documents
2382
+ * - `strict`: controls how Mongoose handles keys that aren't in the schema for updates. This option is `true` by default, which means Mongoose will silently strip any paths in the update that aren't in the schema. See the [`strict` mode docs](https://mongoosejs.com/docs/guide.html#strict) for more information.
2383
+ * - `strictQuery`: controls how Mongoose handles keys that aren't in the schema for the query `filter`. This option is `false` by default, which means Mongoose will allow `Model.find({ foo: 'bar' })` even if `foo` is not in the schema. See the [`strictQuery` docs](https://mongoosejs.com/docs/guide.html#strictQuery) for more information.
2384
+ * - `timestamps`: if `false`, Mongoose will not apply timestamps to the update
2385
+ * - `translateAliases`: if `true`, Mongoose will translate schema aliases in the query
2386
+ * - `updatePipeline`: if `true`, Mongoose will treat the update as an update pipeline
2371
2387
  *
2372
2388
  * Mongoose maintains a separate object for internal options because
2373
2389
  * Mongoose sends `Query.prototype.options` to the MongoDB server, and the
@@ -3565,9 +3581,6 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
3565
3581
  applyGlobalMaxTimeMS(this.options, dbOptions, baseOptions);
3566
3582
  applyGlobalDiskUse(this.options, dbOptions, baseOptions);
3567
3583
 
3568
- if ('strict' in this.options) {
3569
- this._mongooseOptions.strict = this.options.strict;
3570
- }
3571
3584
  const options = this._optionsForExec(this.model);
3572
3585
  convertNewToReturnDocument(options);
3573
3586
 
@@ -3828,11 +3841,6 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
3828
3841
  throw this.error();
3829
3842
  }
3830
3843
 
3831
- if ('strict' in this.options) {
3832
- this._mongooseOptions.strict = this.options.strict;
3833
- delete this.options.strict;
3834
- }
3835
-
3836
3844
  const filter = this._conditions;
3837
3845
  const options = this._optionsForExec();
3838
3846
  convertNewToReturnDocument(options);
@@ -4548,13 +4556,6 @@ function _update(query, op, filter, doc, options, callback) {
4548
4556
  query.op = op;
4549
4557
  doc = doc || {};
4550
4558
 
4551
- // strict is an option used in the update checking, make sure it gets set
4552
- if (options != null) {
4553
- if ('strict' in options) {
4554
- query._mongooseOptions.strict = options.strict;
4555
- }
4556
- }
4557
-
4558
4559
  if (!(filter instanceof Query) &&
4559
4560
  filter != null &&
4560
4561
  filter.toString() !== '[object Object]') {
@@ -5212,13 +5213,11 @@ Query.prototype.cast = function(model, obj) {
5212
5213
  }
5213
5214
 
5214
5215
  const opts = { upsert: this.options?.upsert };
5215
- if (this.options) {
5216
- if ('strict' in this.options) {
5217
- opts.strict = this.options.strict;
5218
- }
5219
- if ('strictQuery' in this.options) {
5220
- opts.strictQuery = this.options.strictQuery;
5221
- }
5216
+ if ('strict' in this._mongooseOptions) {
5217
+ opts.strict = this._mongooseOptions.strict;
5218
+ }
5219
+ if ('strictQuery' in this._mongooseOptions) {
5220
+ opts.strictQuery = this._mongooseOptions.strictQuery;
5222
5221
  }
5223
5222
  if ('sanitizeFilter' in this._mongooseOptions) {
5224
5223
  opts.sanitizeFilter = this._mongooseOptions.sanitizeFilter;
@@ -119,14 +119,26 @@ SchemaDocumentArrayElement.prototype.doValidate = async function doValidate(valu
119
119
  */
120
120
 
121
121
  SchemaDocumentArrayElement.prototype.clone = function() {
122
- this.options.$parentSchemaType = this.$parentSchemaType;
123
- const ret = SchemaType.prototype.clone.apply(this, arguments);
124
- delete this.options.$parentSchemaType;
125
-
126
- ret.Constructor = this.Constructor;
127
- ret.schema = this.schema;
122
+ // This schematype takes the subdocument schema where `SchemaType` takes the
123
+ // options, so it cannot go through `SchemaType.prototype.clone()`: the
124
+ // arguments would land in the wrong parameters and `$parentSchemaType` would
125
+ // never reach the constructor.
126
+ const options = Object.assign({}, this.options, {
127
+ $parentSchemaType: this.$parentSchemaType,
128
+ Constructor: this.Constructor
129
+ });
130
+ const schematype = new this.constructor(
131
+ this.path,
132
+ this.schema,
133
+ options,
134
+ this.parentSchema
135
+ );
136
+ schematype.validators = this.validators.slice();
137
+ if (this.requiredValidator !== undefined) {
138
+ schematype.requiredValidator = this.requiredValidator;
139
+ }
128
140
 
129
- return ret;
141
+ return schematype;
130
142
  };
131
143
 
132
144
  /*!
package/lib/schema.js CHANGED
@@ -451,6 +451,16 @@ Schema.prototype._clone = function _clone(Constructor) {
451
451
  );
452
452
  s.nested = clone(this.nested);
453
453
  s.subpaths = clone(this.subpaths);
454
+
455
+ s.mapPaths = [];
456
+ for (const [path, schemaType] of Object.entries(s.paths)) {
457
+ const mapPath = path + '.$*';
458
+ if (!schemaType.$isSchemaMap || !Object.hasOwn(s.paths, mapPath)) {
459
+ continue;
460
+ }
461
+ s.paths[mapPath] = schemaType.$__schemaType;
462
+ s.mapPaths.push(s.paths[mapPath]);
463
+ }
454
464
  for (const schemaType of Object.values(s.paths)) {
455
465
  if (schemaType.$isSingleNested) {
456
466
  const path = schemaType.path;
@@ -478,7 +488,6 @@ Schema.prototype._clone = function _clone(Constructor) {
478
488
  s.$implicitlyCreated = this.$implicitlyCreated;
479
489
  s.$id = ++id;
480
490
  s.$originalSchemaId = this.$id;
481
- s.mapPaths = [].concat(this.mapPaths);
482
491
 
483
492
  if (this.discriminatorMapping != null) {
484
493
  s.discriminatorMapping = Object.assign({}, this.discriminatorMapping);
@@ -2764,12 +2773,27 @@ Schema.prototype.remove = function(path) {
2764
2773
  }
2765
2774
  }
2766
2775
 
2776
+ this.mapPaths = this.mapPaths.filter(
2777
+ schemaType => !schemaType.path.startsWith(name + '.')
2778
+ );
2779
+
2767
2780
  delete this.nested[name];
2768
2781
  _deletePath(this, name);
2769
2782
  return;
2770
2783
  }
2771
2784
 
2772
2785
  delete this.paths[name];
2786
+ // A map registers its values under `<path>.$*`, both in `paths` and in
2787
+ // `mapPaths`. Left behind, they keep answering `pathType()` and casting queries
2788
+ // for a path the schema no longer has.
2789
+ for (const subpath of Object.keys(this.paths)) {
2790
+ if (subpath.startsWith(name + '.')) {
2791
+ delete this.paths[subpath];
2792
+ }
2793
+ }
2794
+ this.mapPaths = this.mapPaths.filter(
2795
+ schemaType => !schemaType.path.startsWith(name + '.')
2796
+ );
2773
2797
  _deletePath(this, name);
2774
2798
 
2775
2799
  this._removeEncryptedField(name);
@@ -2790,6 +2814,9 @@ function _deletePath(schema, name) {
2790
2814
 
2791
2815
  for (const piece of pieces) {
2792
2816
  branch = branch[piece];
2817
+ if (branch == null) {
2818
+ return;
2819
+ }
2793
2820
  }
2794
2821
 
2795
2822
  delete branch[last];
package/lib/schemaType.js CHANGED
@@ -230,8 +230,12 @@ SchemaType.prototype._addJSONSchemaEnum = function _addJSONSchemaEnum(definition
230
230
  }
231
231
 
232
232
  // The enum validator allows nullish values, so allow `null` wherever the type does.
233
+ // Skip that when the user already listed `null`: MongoDB refuses a `$jsonSchema`
234
+ // whose `enum` repeats a value, and so does Ajv.
233
235
  const allowsNull = Array.isArray(definition.type ?? definition.bsonType);
234
- definition.enum = allowsNull ? [...this.enumValues, null] : [...this.enumValues];
236
+ definition.enum = allowsNull && !this.enumValues.includes(null)
237
+ ? [...this.enumValues, null]
238
+ : [...this.enumValues];
235
239
 
236
240
  return definition;
237
241
  };
@@ -1722,7 +1726,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init, options) {
1722
1726
  !doc.$__.populated[path].options ||
1723
1727
  !doc.$__.populated[path].options.options ||
1724
1728
  !doc.$__.populated[path].options.options.lean) {
1725
- const PopulatedModel = pop ? pop.options[populateModelSymbol] : doc.constructor.db.model(this.options.ref);
1729
+ const PopulatedModel = pop ? pop.options[populateModelSymbol] : owner.constructor.db.model(this.options.ref);
1726
1730
  ret = PopulatedModel.hydrate(value, null, options);
1727
1731
  ret.$__.wasPopulated = { value: ret._doc._id, options: { [populateModelSymbol]: PopulatedModel } };
1728
1732
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "9.9.2",
4
+ "version": "9.9.4",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",