mongoose 9.7.4 → 9.8.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.
- package/README.md +2 -2
- package/lib/aggregate.js +3 -0
- package/lib/cast.js +1 -1
- package/lib/document.js +50 -26
- package/lib/helpers/query/castUpdate.js +27 -10
- package/lib/model.js +12 -2
- package/lib/query.js +5 -0
- package/lib/schema/array.js +3 -3
- package/lib/schema.js +3 -0
- package/lib/types/subdocument.js +4 -2
- package/lib/utils.js +3 -2
- package/lib/validOptions.js +1 -0
- package/package.json +6 -6
- package/types/index.d.ts +2 -0
- package/types/models.d.ts +22 -3
- package/types/mongooseoptions.d.ts +8 -0
- package/types/populate.d.ts +8 -0
- package/types/schemaoptions.d.ts +5 -0
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Check out the [plugins search site](https://plugins.mongoosejs.io/) to see hundr
|
|
|
32
32
|
Pull requests are always welcome! Please base pull requests against the `master`
|
|
33
33
|
branch and follow the [contributing guide](https://github.com/Automattic/mongoose/blob/master/CONTRIBUTING.md).
|
|
34
34
|
|
|
35
|
-
If your pull
|
|
35
|
+
If your pull request makes documentation changes, please do **not**
|
|
36
36
|
modify any `.html` files. The `.html` files are compiled code, so please make
|
|
37
37
|
your changes in `docs/*.pug`, `lib/*.js`, or `test/docs/*.js`.
|
|
38
38
|
|
|
@@ -341,7 +341,7 @@ new Schema({
|
|
|
341
341
|
### Driver Access
|
|
342
342
|
|
|
343
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
|
|
344
|
-
notable exception that `YourModel.collection` still buffers
|
|
344
|
+
notable exception is that `YourModel.collection` still buffers
|
|
345
345
|
commands. As such, `YourModel.collection.find()` will **not**
|
|
346
346
|
return a cursor.
|
|
347
347
|
|
package/lib/aggregate.js
CHANGED
|
@@ -615,6 +615,9 @@ Aggregate.prototype.graphLookup = function(options) {
|
|
|
615
615
|
*
|
|
616
616
|
* aggregate.sample(3); // Add a pipeline that picks 3 random documents
|
|
617
617
|
*
|
|
618
|
+
* // `$match` before `$sample` samples from the filtered subset
|
|
619
|
+
* aggregate.match({ difficulty: 'easy' }).sample(3);
|
|
620
|
+
*
|
|
618
621
|
* @see $sample https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/#pipe._S_sample
|
|
619
622
|
* @param {number} size number of random documents to pick
|
|
620
623
|
* @return {Aggregate}
|
package/lib/cast.js
CHANGED
|
@@ -413,7 +413,7 @@ function _cast(val, numbertype, context) {
|
|
|
413
413
|
_cast(item, numbertype, context);
|
|
414
414
|
val[nkey] = item;
|
|
415
415
|
} else {
|
|
416
|
-
val[nkey] = numbertype.castForQuery(
|
|
416
|
+
val[nkey] = numbertype.castForQuery(null, item, context);
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
}
|
package/lib/document.js
CHANGED
|
@@ -143,7 +143,7 @@ function Document(obj, fields, options) {
|
|
|
143
143
|
this.$__.strictMode = schema.options.strict;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
const requiredPaths = schema.requiredPaths(
|
|
146
|
+
const requiredPaths = schema.requiredPaths();
|
|
147
147
|
for (const path of requiredPaths) {
|
|
148
148
|
this.$__.activePaths.require(path);
|
|
149
149
|
}
|
|
@@ -756,6 +756,7 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
756
756
|
let i;
|
|
757
757
|
const strict = self.$__.strictMode;
|
|
758
758
|
const docSchema = self.$__schema;
|
|
759
|
+
const strictRead = docSchema.options.strictRead;
|
|
759
760
|
|
|
760
761
|
for (let index = 0; index < len; ++index) {
|
|
761
762
|
i = keys[index];
|
|
@@ -773,6 +774,14 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
773
774
|
}
|
|
774
775
|
|
|
775
776
|
const value = obj[i];
|
|
777
|
+
if (!schemaType && strictRead && docSchema.pathType(path) === 'adhocOrUndefined') {
|
|
778
|
+
if (strictRead === 'throw') {
|
|
779
|
+
throw new StrictModeError(path, 'Field `' + path + '` is not in schema and strictRead is set to throw.');
|
|
780
|
+
} else if (strictRead === true) {
|
|
781
|
+
continue;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
776
785
|
if (!schemaType && utils.isPOJO(value)) {
|
|
777
786
|
// assume nested object
|
|
778
787
|
if (!doc[i]) {
|
|
@@ -785,6 +794,13 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
785
794
|
}
|
|
786
795
|
init(self, value, doc[i], opts, path + '.');
|
|
787
796
|
} else if (!schemaType) {
|
|
797
|
+
// Handle strictRead: filter unknown fields during document hydration from DB
|
|
798
|
+
if (strictRead === 'throw') {
|
|
799
|
+
throw new StrictModeError(path, 'Field `' + path + '` is not in schema and strictRead is set to throw.');
|
|
800
|
+
} else if (strictRead === true) {
|
|
801
|
+
// Skip this field - do not store in _doc
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
788
804
|
doc[i] = value;
|
|
789
805
|
if (!strict && !prefix) {
|
|
790
806
|
self[i] = value;
|
|
@@ -2959,9 +2975,13 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
|
|
|
2959
2975
|
const doValidateOptions = {};
|
|
2960
2976
|
|
|
2961
2977
|
_evaluateRequiredFunctions(doc);
|
|
2978
|
+
// gh-16379: compute the modified-path set at most once and reuse it below, so
|
|
2979
|
+
// checking many required paths doesn't rebuild it per-path (O(required x modified)).
|
|
2980
|
+
let _modifiedPaths;
|
|
2981
|
+
const getModifiedPaths = () => (_modifiedPaths ??= doc.modifiedPaths());
|
|
2962
2982
|
// only validate required fields when necessary
|
|
2963
2983
|
let paths = new Set(Object.keys(doc.$__.activePaths.getStatePaths('require')).filter(function(path) {
|
|
2964
|
-
if (!doc.$__isSelected(path) && !doc.$isModified(path)) {
|
|
2984
|
+
if (!doc.$__isSelected(path) && !doc.$isModified(path, null, getModifiedPaths())) {
|
|
2965
2985
|
return false;
|
|
2966
2986
|
}
|
|
2967
2987
|
if (path.endsWith('.$*')) {
|
|
@@ -3040,7 +3060,7 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
|
|
|
3040
3060
|
}
|
|
3041
3061
|
}
|
|
3042
3062
|
}
|
|
3043
|
-
const modifiedPaths =
|
|
3063
|
+
const modifiedPaths = getModifiedPaths();
|
|
3044
3064
|
for (const subdoc of topLevelSubdocs) {
|
|
3045
3065
|
if (subdoc.$basePath) {
|
|
3046
3066
|
const fullPathToSubdoc = subdoc.$__pathRelativeToParent();
|
|
@@ -3567,6 +3587,10 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
|
|
|
3567
3587
|
* @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).
|
|
3568
3588
|
* @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)
|
|
3569
3589
|
* @param {boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
|
|
3590
|
+
* @param {Array} [options.pathsToSave] An array of paths that tell mongoose to only validate and save the paths in `pathsToSave`.
|
|
3591
|
+
* @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware
|
|
3592
|
+
* @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
|
|
3593
|
+
* @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks
|
|
3570
3594
|
* @method save
|
|
3571
3595
|
* @memberOf Document
|
|
3572
3596
|
* @instance
|
|
@@ -4718,6 +4742,9 @@ Document.prototype.equals = function(doc) {
|
|
|
4718
4742
|
* @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](https://mongoosejs.com/docs/schematypes.html#schematype-options).
|
|
4719
4743
|
* @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
|
|
4720
4744
|
* @param {object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object.
|
|
4745
|
+
* @param {boolean} [options.skipInvalidIds=false] By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type.
|
|
4746
|
+
* @param {number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results when populating multiple parent documents because it executes a single query for all parents. If you set `perDocumentLimit`, Mongoose will execute a separate query per parent document to ensure correct per-document `limit`.
|
|
4747
|
+
* @param {boolean} [options.strictPopulate=true] Set to false to allow populating paths that aren't defined in the given model's schema.
|
|
4721
4748
|
* @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
|
|
4722
4749
|
* @param {object} [options.options=null] Additional options like `limit` and `lean`.
|
|
4723
4750
|
* @param {boolean} [options.forceRepopulate=true] Set to `false` to prevent Mongoose from repopulating paths that are already populated
|
|
@@ -5147,8 +5174,8 @@ Document.prototype.$__delta = function $__delta(pathsToSave, pathsToSaveSet) {
|
|
|
5147
5174
|
const optimisticConcurrencyExcludeSet = this.$__schema.options._optimisticConcurrencyExcludeSet;
|
|
5148
5175
|
const modPaths = this.directModifiedPaths();
|
|
5149
5176
|
const hasRelevantModPaths = pathsToSave == null ?
|
|
5150
|
-
modPaths.find(path => !
|
|
5151
|
-
modPaths.find(path => !
|
|
5177
|
+
modPaths.find(path => !_pathOrAncestorInSet(path, optimisticConcurrencyExcludeSet)) :
|
|
5178
|
+
modPaths.find(path => !_pathOrAncestorInSet(path, optimisticConcurrencyExcludeSet) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5152
5179
|
if (hasRelevantModPaths) {
|
|
5153
5180
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5154
5181
|
}
|
|
@@ -5661,34 +5688,17 @@ Document.prototype._applyVersionIncrement = function _applyVersionIncrement() {
|
|
|
5661
5688
|
}
|
|
5662
5689
|
};
|
|
5663
5690
|
|
|
5664
|
-
/*!
|
|
5665
|
-
* Increment this document's version if necessary.
|
|
5666
|
-
*/
|
|
5667
|
-
|
|
5668
|
-
Document.prototype._applyVersionIncrement = function _applyVersionIncrement() {
|
|
5669
|
-
if (!this.$__.version) return;
|
|
5670
|
-
const doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
|
|
5671
|
-
|
|
5672
|
-
this.$__.version = undefined;
|
|
5673
|
-
if (doIncrement) {
|
|
5674
|
-
const key = this.$__schema.options.versionKey;
|
|
5675
|
-
const version = this.$__getValue(key) || 0;
|
|
5676
|
-
this.$__setValue(key, version + 1); // increment version if was successful
|
|
5677
|
-
}
|
|
5678
|
-
};
|
|
5679
|
-
|
|
5680
5691
|
/*!
|
|
5681
5692
|
* Module exports.
|
|
5682
5693
|
*/
|
|
5683
5694
|
|
|
5684
5695
|
/*!
|
|
5685
|
-
* Check if `path
|
|
5686
|
-
* exist in `pathSet`.
|
|
5696
|
+
* Check if `path` or any of its ancestor paths exist in `pathSet`.
|
|
5687
5697
|
* For example:
|
|
5688
|
-
*
|
|
5689
|
-
*
|
|
5698
|
+
* _pathOrAncestorInSet('profile.firstName', Set(['profile'])) === true
|
|
5699
|
+
* _pathOrAncestorInSet('profile', Set(['profile.firstName'])) === false
|
|
5690
5700
|
*/
|
|
5691
|
-
function
|
|
5701
|
+
function _pathOrAncestorInSet(path, pathSet) {
|
|
5692
5702
|
if (pathSet.has(path)) {
|
|
5693
5703
|
return true;
|
|
5694
5704
|
}
|
|
@@ -5699,6 +5709,20 @@ function _pathOverlapsSet(path, pathSet) {
|
|
|
5699
5709
|
}
|
|
5700
5710
|
idx = path.indexOf('.', idx + 1);
|
|
5701
5711
|
}
|
|
5712
|
+
return false;
|
|
5713
|
+
}
|
|
5714
|
+
|
|
5715
|
+
/*!
|
|
5716
|
+
* Check if `path`, any of its ancestor paths, or any of its descendant paths
|
|
5717
|
+
* exist in `pathSet`.
|
|
5718
|
+
* For example:
|
|
5719
|
+
* _pathOverlapsSet('profile.firstName', Set(['profile'])) === true
|
|
5720
|
+
* _pathOverlapsSet('profile', Set(['profile.firstName'])) === true
|
|
5721
|
+
*/
|
|
5722
|
+
function _pathOverlapsSet(path, pathSet) {
|
|
5723
|
+
if (_pathOrAncestorInSet(path, pathSet)) {
|
|
5724
|
+
return true;
|
|
5725
|
+
}
|
|
5702
5726
|
for (const p of pathSet) {
|
|
5703
5727
|
if (p.length > path.length + 1 && p[path.length] === '.' && p.slice(0, path.length) === path) {
|
|
5704
5728
|
return true;
|
|
@@ -11,6 +11,7 @@ const getConstructorName = require('../getConstructorName');
|
|
|
11
11
|
const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
|
|
12
12
|
const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath');
|
|
13
13
|
const handleImmutable = require('./handleImmutable');
|
|
14
|
+
const isOperator = require('./isOperator');
|
|
14
15
|
const moveImmutableProperties = require('../update/moveImmutableProperties');
|
|
15
16
|
const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol;
|
|
16
17
|
const setDottedPath = require('../path/setDottedPath');
|
|
@@ -235,10 +236,25 @@ function walkUpdatePath(schema, obj, op, options, context, filter, prefix) {
|
|
|
235
236
|
key = keys[i];
|
|
236
237
|
val = obj[key];
|
|
237
238
|
|
|
239
|
+
const fullPath = prefix + key;
|
|
240
|
+
const isTopLevelOperator = !prefix && isOperator(key);
|
|
241
|
+
let fullPathSchema = isTopLevelOperator ? schema._getSchema(fullPath) : null;
|
|
242
|
+
const isTopLevelNestedDollarPath = isTopLevelOperator && Object.hasOwn(schema.nested, key);
|
|
243
|
+
if (isTopLevelOperator &&
|
|
244
|
+
fullPathSchema == null &&
|
|
245
|
+
!isTopLevelNestedDollarPath) {
|
|
246
|
+
throw new MongooseError('Invalid update: Unexpected modifier "' + key + '" as a key in operator "' + op + '". '
|
|
247
|
+
+ 'Did you mean something like { ' + op + ': { fieldName: { ' + key + ': [...] } } }? '
|
|
248
|
+
+ 'Modifiers must appear under a valid field path.');
|
|
249
|
+
}
|
|
250
|
+
|
|
238
251
|
// `$pull` is special because we need to cast the RHS as a query, not as
|
|
239
252
|
// an update.
|
|
240
253
|
if (op === '$pull') {
|
|
241
|
-
|
|
254
|
+
if (!isTopLevelOperator) {
|
|
255
|
+
fullPathSchema = schema._getSchema(fullPath);
|
|
256
|
+
}
|
|
257
|
+
schematype = fullPathSchema;
|
|
242
258
|
if (schematype == null) {
|
|
243
259
|
const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, prefix + key, options);
|
|
244
260
|
if (_res.schematype != null) {
|
|
@@ -269,10 +285,13 @@ function walkUpdatePath(schema, obj, op, options, context, filter, prefix) {
|
|
|
269
285
|
}
|
|
270
286
|
}
|
|
271
287
|
|
|
288
|
+
if (!isTopLevelOperator && op !== '$pull') {
|
|
289
|
+
fullPathSchema = schema._getSchema(fullPath);
|
|
290
|
+
}
|
|
291
|
+
schematype = fullPathSchema;
|
|
292
|
+
|
|
272
293
|
if (getConstructorName(val) === 'Object') {
|
|
273
294
|
// watch for embedded doc schemas
|
|
274
|
-
schematype = schema._getSchema(prefix + key);
|
|
275
|
-
|
|
276
295
|
if (schematype == null) {
|
|
277
296
|
const _res = getEmbeddedDiscriminatorPath(schema, obj, filter, prefix + key, options);
|
|
278
297
|
if (_res.schematype != null) {
|
|
@@ -383,14 +402,12 @@ function walkUpdatePath(schema, obj, op, options, context, filter, prefix) {
|
|
|
383
402
|
(utils.isObject(val) && utils.hasOwnKeys(val) === false);
|
|
384
403
|
}
|
|
385
404
|
} else {
|
|
386
|
-
const isModifier =
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
405
|
+
const isModifier = !isTopLevelNestedDollarPath && schematype == null &&
|
|
406
|
+
(key === '$each' || key === '$or' || key === '$and' || key === '$in');
|
|
407
|
+
const checkPath = isModifier ? prefix : fullPath;
|
|
408
|
+
if (isModifier) {
|
|
409
|
+
schematype = schema._getSchema(checkPath);
|
|
391
410
|
}
|
|
392
|
-
const checkPath = isModifier ? prefix : prefix + key;
|
|
393
|
-
schematype = schema._getSchema(checkPath);
|
|
394
411
|
|
|
395
412
|
// You can use `$setOnInsert` with immutable keys
|
|
396
413
|
if (op !== '$setOnInsert' &&
|
package/lib/model.js
CHANGED
|
@@ -3656,12 +3656,16 @@ async function buildPreSavePromise(document, options) {
|
|
|
3656
3656
|
}
|
|
3657
3657
|
|
|
3658
3658
|
async function handleSuccessfulWrite(document, options) {
|
|
3659
|
-
|
|
3659
|
+
const wasNew = document.$isNew;
|
|
3660
|
+
if (wasNew) {
|
|
3660
3661
|
_setIsNew(document, false);
|
|
3661
3662
|
}
|
|
3662
3663
|
|
|
3663
3664
|
document.$__reset();
|
|
3664
|
-
document
|
|
3665
|
+
// version key gets initialized to 0 when inserting a new document so avoid incrementing version key here
|
|
3666
|
+
if (!wasNew) {
|
|
3667
|
+
document._applyVersionIncrement();
|
|
3668
|
+
}
|
|
3665
3669
|
const postFilter = buildMiddlewareFilter(options, 'post');
|
|
3666
3670
|
return document.schema.s.hooks.execPost('save', document, [document], { filter: postFilter });
|
|
3667
3671
|
}
|
|
@@ -3911,6 +3915,12 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
|
|
|
3911
3915
|
|
|
3912
3916
|
const isANewDocument = document.isNew;
|
|
3913
3917
|
if (isANewDocument) {
|
|
3918
|
+
// Like `$__version()` on insert, initialize the version key so
|
|
3919
|
+
// inserted documents get the same shape as `save()` gives them
|
|
3920
|
+
const versionKey = document.$__schema.options.versionKey;
|
|
3921
|
+
if (versionKey) {
|
|
3922
|
+
document.$__setValue(versionKey, 0);
|
|
3923
|
+
}
|
|
3914
3924
|
const writeOperation = { insertOne: { document } };
|
|
3915
3925
|
utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
|
|
3916
3926
|
return writeOperation;
|
package/lib/query.js
CHANGED
|
@@ -5088,12 +5088,17 @@ Query.prototype._castUpdate = function _castUpdate(obj) {
|
|
|
5088
5088
|
* @param {object} [match] Conditions for the population query
|
|
5089
5089
|
* @param {object} [options] Options for the population query (sort, etc)
|
|
5090
5090
|
* @param {string} [options.path=null] The path to populate.
|
|
5091
|
+
* @param {string|PopulateOptions} [options.populate=null] Recursively populate paths in the populated documents. See [deep populate docs](https://mongoosejs.com/docs/populate.html#deep-populate).
|
|
5091
5092
|
* @param {boolean} [options.retainNullValues=false] by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries.
|
|
5092
5093
|
* @param {boolean} [options.getters=false] if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. For example, you would need to set this option to `true` if you wanted to [add a `lowercase` getter to your `localField`](https://mongoosejs.com/docs/schematypes.html#schematype-options).
|
|
5093
5094
|
* @param {boolean} [options.clone=false] When you do `BlogPost.find().populate('author')`, blog posts with the same author will share 1 copy of an `author` doc. Enable this option to make Mongoose clone populated docs before assigning them.
|
|
5094
5095
|
* @param {object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object.
|
|
5096
|
+
* @param {boolean} [options.skipInvalidIds=false] By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type.
|
|
5097
|
+
* @param {number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents.
|
|
5098
|
+
* @param {boolean} [options.strictPopulate=true] Set to false to allow populating paths that aren't defined in the given model's schema.
|
|
5095
5099
|
* @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
|
|
5096
5100
|
* @param {object} [options.options=null] Additional options like `limit` and `lean`.
|
|
5101
|
+
* @param {boolean} [options.ordered=false] Set to `true` to execute any populate queries one at a time, as opposed to in parallel. Set this option to `true` if populating multiple paths or paths with multiple models in transactions.
|
|
5097
5102
|
* @see population https://mongoosejs.com/docs/populate.html
|
|
5098
5103
|
* @see Query#select https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
|
|
5099
5104
|
* @see Model.populate https://mongoosejs.com/docs/api/model.html#Model.populate()
|
package/lib/schema/array.js
CHANGED
|
@@ -510,9 +510,9 @@ SchemaArray.prototype._castForQuery = function(val, context) {
|
|
|
510
510
|
}
|
|
511
511
|
|
|
512
512
|
if (Array.isArray(val)) {
|
|
513
|
-
this.setters.
|
|
514
|
-
val =
|
|
515
|
-
}
|
|
513
|
+
for (let i = this.setters.length - 1; i >= 0; i--) {
|
|
514
|
+
val = this.setters[i].call(this, val, this);
|
|
515
|
+
}
|
|
516
516
|
val = val.map(function(v) {
|
|
517
517
|
if (utils.isObject(v) && v.$elemMatch) {
|
|
518
518
|
return v;
|
package/lib/schema.js
CHANGED
|
@@ -72,6 +72,7 @@ const numberRE = /^\d+$/;
|
|
|
72
72
|
* - [shardKey](https://mongoosejs.com/docs/guide.html#shardKey): object - defaults to `null`
|
|
73
73
|
* - [strict](https://mongoosejs.com/docs/guide.html#strict): bool - defaults to true
|
|
74
74
|
* - [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery): bool - defaults to false
|
|
75
|
+
* - [strictRead](https://mongoosejs.com/docs/guide.html#strictRead): bool or 'throw' - defaults to false. If set to `true`, fields not in the schema will be stripped when hydrating documents from MongoDB. If set to `'throw'`, an error will be thrown when a field not in the schema is encountered during document hydration.
|
|
75
76
|
* - [toJSON](https://mongoosejs.com/docs/guide.html#toJSON) - object - no default
|
|
76
77
|
* - [toObject](https://mongoosejs.com/docs/guide.html#toObject) - object - no default
|
|
77
78
|
* - [typeKey](https://mongoosejs.com/docs/guide.html#typeKey) - string - defaults to 'type'
|
|
@@ -606,10 +607,12 @@ Schema.prototype.defaultOptions = function(options) {
|
|
|
606
607
|
const baseOptions = this.base?.options || {};
|
|
607
608
|
const defaultStrict = baseOptions.strict ?? true;
|
|
608
609
|
const defaultStrictQuery = baseOptions.strictQuery ?? false;
|
|
610
|
+
const defaultStrictRead = baseOptions.strictRead ?? false;
|
|
609
611
|
const defaultId = baseOptions.id ?? true;
|
|
610
612
|
options = {
|
|
611
613
|
strict: defaultStrict,
|
|
612
614
|
strictQuery: defaultStrictQuery,
|
|
615
|
+
strictRead: defaultStrictRead,
|
|
613
616
|
bufferCommands: true,
|
|
614
617
|
capped: false, // { size, max, autoIndexId }
|
|
615
618
|
versionKey: '__v',
|
package/lib/types/subdocument.js
CHANGED
|
@@ -403,7 +403,9 @@ if (util.inspect.custom) {
|
|
|
403
403
|
|
|
404
404
|
/**
|
|
405
405
|
* Override `$toObject()` to handle minimizing the whole path. Should not minimize if schematype-level minimize
|
|
406
|
-
* is set to false re: gh-11247, gh-14058, gh-14151
|
|
406
|
+
* is set to false re: gh-11247, gh-14058, gh-14151. Should not minimize document array elements: an array
|
|
407
|
+
* element cannot be removed without shifting the array, so minimizing it to `undefined` makes the BSON
|
|
408
|
+
* serializer store `null` re: gh-7322.
|
|
407
409
|
*/
|
|
408
410
|
|
|
409
411
|
Subdocument.prototype.$toObject = function $toObject(options, json) {
|
|
@@ -413,7 +415,7 @@ Subdocument.prototype.$toObject = function $toObject(options, json) {
|
|
|
413
415
|
// If minimize is set, then we can minimize out the whole object.
|
|
414
416
|
if (utils.hasOwnKeys(ret) === false && options?._calledWithOptions != null) {
|
|
415
417
|
const minimize = options._calledWithOptions?.minimize ?? this?.$__schemaTypeOptions?.minimize ?? options.minimize;
|
|
416
|
-
if (minimize && !this.constructor.$__required) {
|
|
418
|
+
if (minimize && !this.constructor.$__required && !this.$isDocumentArrayElement) {
|
|
417
419
|
return undefined;
|
|
418
420
|
}
|
|
419
421
|
}
|
package/lib/utils.js
CHANGED
|
@@ -858,11 +858,12 @@ exports.array.unique = function(arr) {
|
|
|
858
858
|
ret.push(item);
|
|
859
859
|
primitives.add(item);
|
|
860
860
|
} else if (isBsonType(item, 'ObjectId')) {
|
|
861
|
-
|
|
861
|
+
const idStr = item.toString();
|
|
862
|
+
if (ids.has(idStr)) {
|
|
862
863
|
continue;
|
|
863
864
|
}
|
|
864
865
|
ret.push(item);
|
|
865
|
-
ids.add(
|
|
866
|
+
ids.add(idStr);
|
|
866
867
|
} else {
|
|
867
868
|
ret.push(item);
|
|
868
869
|
}
|
package/lib/validOptions.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongoose",
|
|
3
3
|
"description": "Mongoose MongoDB ODM",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.8.1",
|
|
5
5
|
"author": "Guillermo Rauch <guillermo@learnboost.com>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongodb",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@standard-schema/spec": "^1.1.0",
|
|
24
24
|
"kareem": "3.3.0",
|
|
25
|
-
"mongodb": "~7.
|
|
25
|
+
"mongodb": "~7.5",
|
|
26
26
|
"mpath": "0.9.0",
|
|
27
27
|
"mquery": "6.0.0",
|
|
28
28
|
"ms": "2.1.3",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"mkdirp": "^3.0.1",
|
|
56
56
|
"mocha": "12.0.0-beta-10",
|
|
57
57
|
"moment": "2.30.1",
|
|
58
|
-
"mongodb-client-encryption": "
|
|
58
|
+
"mongodb-client-encryption": "^7.2.0",
|
|
59
59
|
"mongodb-memory-server": "11.2.0",
|
|
60
60
|
"mongodb-runner": "^6.0.0",
|
|
61
61
|
"ncp": "^2.0.0",
|
|
@@ -99,17 +99,17 @@
|
|
|
99
99
|
"mongo": "node ./tools/repl.js",
|
|
100
100
|
"publish-7x": "npm publish --tag 7x",
|
|
101
101
|
"create-separate-require-instance": "rm -rf ./node_modules/mongoose-separate-require-instance && node ./scripts/create-tarball && tar -xzf mongoose.tgz -C ./node_modules && mv ./node_modules/package ./node_modules/mongoose-separate-require-instance",
|
|
102
|
-
"test": "mocha --exit ./test
|
|
102
|
+
"test": "mocha --exit --ignore \"test/encryption/**/*.test.js\" \"./test/**/*.test.js\"",
|
|
103
103
|
"test:ci": "npm run test -- --reporter min",
|
|
104
104
|
"test-deno": "deno run --allow-env --allow-read --allow-net --allow-run --allow-sys --allow-write ./test/deno.mjs",
|
|
105
105
|
"test-deno:ci": "npm run test-deno -- --reporter min",
|
|
106
|
-
"test-rs": "START_REPLICA_SET=1 mocha --timeout 30000 --exit ./test
|
|
106
|
+
"test-rs": "START_REPLICA_SET=1 mocha --timeout 30000 --exit --ignore \"test/encryption/**/*.test.js\" \"./test/**/*.test.js\"",
|
|
107
107
|
"test-rs:ci": "npm run test-rs -- --reporter min",
|
|
108
108
|
"test:types": "tstyche",
|
|
109
109
|
"setup-test-encryption": "node scripts/setup-encryption-tests.js",
|
|
110
110
|
"test-encryption": "mocha --exit ./test/encryption/*.test.js",
|
|
111
111
|
"test-encryption:ci": "npm run test-encryption -- --reporter min",
|
|
112
|
-
"tdd": "mocha --watch --inspect --
|
|
112
|
+
"tdd": "mocha --watch --inspect --ignore \"test/encryption/**/*.test.js\" \"./test/**/*.test.js\" --watch-files lib/**/*.js test/**/*.js",
|
|
113
113
|
"test-coverage": "c8 --reporter=html --reporter=text npm test",
|
|
114
114
|
"test-coverage:ci": "c8 --reporter=html --reporter=text npm run test:ci",
|
|
115
115
|
"ts-benchmark": "cd ./benchmarks/typescript/simple && npm install && npm run benchmark | node ../../../scripts/tsc-diagnostics-check",
|
package/types/index.d.ts
CHANGED
|
@@ -150,6 +150,8 @@ declare module 'mongoose' {
|
|
|
150
150
|
? IfAny<U, T & { _id: Types.ObjectId }, T & Required<{ _id: U }>>
|
|
151
151
|
: T & { _id: Types.ObjectId };
|
|
152
152
|
|
|
153
|
+
export type Default_id<T, TSchemaOptions = {}> = TSchemaOptions extends { _id: false } ? T : Require_id<T>;
|
|
154
|
+
|
|
153
155
|
export type Default__v<T, TSchemaOptions = {}> = TSchemaOptions extends { versionKey: false }
|
|
154
156
|
? T
|
|
155
157
|
: TSchemaOptions extends { versionKey: infer VK }
|
package/types/models.d.ts
CHANGED
|
@@ -116,6 +116,8 @@ declare module 'mongoose' {
|
|
|
116
116
|
SessionOption {
|
|
117
117
|
checkKeys?: boolean;
|
|
118
118
|
j?: boolean;
|
|
119
|
+
/** An array of paths that tell mongoose to only validate and save the paths in `pathsToSave`. */
|
|
120
|
+
pathsToSave?: string[];
|
|
119
121
|
safe?: boolean | WriteConcern;
|
|
120
122
|
timestamps?: boolean | QueryTimestampsConfig;
|
|
121
123
|
validateBeforeSave?: boolean;
|
|
@@ -239,7 +241,12 @@ declare module 'mongoose' {
|
|
|
239
241
|
base: Mongoose;
|
|
240
242
|
|
|
241
243
|
/** Standard Schema adapter for validating input with this model's schema. */
|
|
242
|
-
readonly '~standard': StandardSchemaV1.Props<
|
|
244
|
+
readonly '~standard': StandardSchemaV1.Props<
|
|
245
|
+
Default__v<
|
|
246
|
+
Default_id<TRawDocType, ObtainSchemaGeneric<TSchema, 'TSchemaOptions'>>,
|
|
247
|
+
ObtainSchemaGeneric<TSchema, 'TSchemaOptions'>
|
|
248
|
+
>
|
|
249
|
+
>;
|
|
243
250
|
|
|
244
251
|
/**
|
|
245
252
|
* If this is a discriminator model, `baseModelName` is the name of
|
|
@@ -516,8 +523,16 @@ declare module 'mongoose' {
|
|
|
516
523
|
/**
|
|
517
524
|
* Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
|
|
518
525
|
* The document returned has no paths marked as modified initially.
|
|
526
|
+
* With `strict: false`, fields not in the schema are kept on the document; pass
|
|
527
|
+
* `ExtraFields` to describe their types, e.g.
|
|
528
|
+
* `Model.hydrate<{ totalOrders: number }>(obj, null, { strict: false })`.
|
|
519
529
|
*/
|
|
520
|
-
hydrate
|
|
530
|
+
hydrate<ExtraFields = unknown>(
|
|
531
|
+
obj: any,
|
|
532
|
+
projection: ProjectionType<TRawDocType> | null | undefined,
|
|
533
|
+
options: HydrateOptions & { strict: false }
|
|
534
|
+
): THydratedDocumentType & ExtraFields;
|
|
535
|
+
hydrate(obj: any, projection?: ProjectionType<TRawDocType> | null | undefined, options?: HydrateOptions): THydratedDocumentType;
|
|
521
536
|
|
|
522
537
|
/**
|
|
523
538
|
* This function is responsible for building [indexes](https://www.mongodb.com/docs/manual/indexes/),
|
|
@@ -1215,7 +1230,11 @@ declare module 'mongoose' {
|
|
|
1215
1230
|
recompileSchema(): void;
|
|
1216
1231
|
|
|
1217
1232
|
/** Schema the model uses. */
|
|
1218
|
-
schema:
|
|
1233
|
+
schema: IfAny<
|
|
1234
|
+
TSchema,
|
|
1235
|
+
Schema<TRawDocType, Model<TRawDocType, TQueryHelpers, TInstanceMethods, TVirtuals>, TInstanceMethods, TQueryHelpers, TVirtuals>,
|
|
1236
|
+
TSchema
|
|
1237
|
+
>;
|
|
1219
1238
|
|
|
1220
1239
|
/** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */
|
|
1221
1240
|
updateMany(
|
|
@@ -192,6 +192,14 @@ declare module 'mongoose' {
|
|
|
192
192
|
*/
|
|
193
193
|
strictQuery?: boolean | 'throw';
|
|
194
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Sets the default [strictRead](https://mongoosejs.com/docs/guide.html#strictRead) mode for schemas.
|
|
197
|
+
* May be `false`, `true`, or `'throw'`.
|
|
198
|
+
*
|
|
199
|
+
* @default false
|
|
200
|
+
*/
|
|
201
|
+
strictRead?: boolean | 'throw';
|
|
202
|
+
|
|
195
203
|
/**
|
|
196
204
|
* Overwrites default objects to `toJSON()`, for determining how Mongoose
|
|
197
205
|
* documents get serialized by `JSON.stringify()`
|
package/types/populate.d.ts
CHANGED
|
@@ -61,6 +61,14 @@ declare module 'mongoose' {
|
|
|
61
61
|
match?: any;
|
|
62
62
|
/** optional model to use for population */
|
|
63
63
|
model?: string | Model<any>;
|
|
64
|
+
/** by default, Mongoose removes null and undefined values from populated arrays. Use this option to make `populate()` retain `null` and `undefined` array entries. */
|
|
65
|
+
retainNullValues?: boolean;
|
|
66
|
+
/** if true, Mongoose will call any getters defined on the `localField`. By default, Mongoose gets the raw value of `localField`. */
|
|
67
|
+
getters?: boolean;
|
|
68
|
+
/** if true, Mongoose will clone populated docs before assigning them, so docs that are populated onto multiple parents don't share 1 copy. */
|
|
69
|
+
clone?: boolean;
|
|
70
|
+
/** By default, Mongoose throws a cast error if `localField` and `foreignField` schemas don't line up. If you enable this option, Mongoose will instead filter out any `localField` properties that cannot be casted to `foreignField`'s schema type. */
|
|
71
|
+
skipInvalidIds?: boolean;
|
|
64
72
|
/** optional query options like sort, limit, etc */
|
|
65
73
|
options?: QueryOptions;
|
|
66
74
|
/** correct limit on populated array */
|
package/types/schemaoptions.d.ts
CHANGED
|
@@ -158,6 +158,11 @@ declare module 'mongoose' {
|
|
|
158
158
|
* [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas.
|
|
159
159
|
*/
|
|
160
160
|
strictQuery?: boolean | 'throw';
|
|
161
|
+
/**
|
|
162
|
+
* May be `false`, `true`, or `'throw'`. Sets the default
|
|
163
|
+
* [strictRead](https://mongoosejs.com/docs/guide.html#strictRead) mode for schemas.
|
|
164
|
+
*/
|
|
165
|
+
strictRead?: boolean | 'throw';
|
|
161
166
|
/** Exactly the same as the toObject option but only applies when the document's toJSON method is called. */
|
|
162
167
|
toJSON?: ToObjectOptions<DocType, THydratedDocumentType>;
|
|
163
168
|
/**
|