mongoose 9.9.3 → 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 +1 -1
- package/lib/model.js +10 -5
- package/lib/query.js +49 -50
- package/lib/schema/documentArrayElement.js +19 -7
- package/lib/schema.js +28 -1
- package/lib/schemaType.js +6 -2
- package/package.json +1 -1
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](
|
|
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.
|
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
|
|
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() ===
|
|
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 ('
|
|
1758
|
-
this._mongooseOptions.
|
|
1759
|
-
delete options.
|
|
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 ('
|
|
1770
|
-
this._mongooseOptions.
|
|
1771
|
-
delete options.
|
|
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 ('
|
|
1786
|
-
this._mongooseOptions.
|
|
1787
|
-
delete options.
|
|
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 ('
|
|
1802
|
-
this._mongooseOptions.
|
|
1803
|
-
delete options.
|
|
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
|
-
* - `
|
|
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
|
-
* - `
|
|
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.
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
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
|
|
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] :
|
|
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
|
}
|