@depup/mongoose 9.3.3-depup.0 → 9.8.0-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -9
- package/changes.json +3 -8
- package/eslint.config.mjs +4 -1
- package/lib/aggregate.js +54 -24
- package/lib/cast.js +1 -1
- package/lib/connection.js +20 -8
- package/lib/cursor/aggregationCursor.js +47 -16
- package/lib/cursor/queryCursor.js +23 -7
- package/lib/document.js +240 -105
- package/lib/drivers/node-mongodb-native/collection.js +47 -14
- package/lib/drivers/node-mongodb-native/connection.js +13 -0
- package/lib/error/cast.js +18 -9
- package/lib/error/divergentArray.js +1 -1
- package/lib/error/index.js +1 -1
- package/lib/error/messages.js +1 -0
- package/lib/helpers/clone.js +22 -5
- package/lib/helpers/document/applyDefaults.js +5 -0
- package/lib/helpers/populate/createPopulateQueryFilter.js +9 -1
- package/lib/helpers/populate/getModelsMapForPopulate.js +8 -6
- package/lib/helpers/populate/splitPopulateQuery.js +81 -0
- package/lib/helpers/query/castUpdate.js +4 -0
- package/lib/helpers/schema/getKeysInSchemaOrder.js +13 -16
- package/lib/helpers/update/applyTimestampsToUpdate.js +4 -2
- package/lib/model.js +159 -41
- package/lib/mongoose.js +3 -3
- package/lib/options/schemaTypeOptions.js +14 -0
- package/lib/plugins/saveSubdocs.js +16 -13
- package/lib/query.js +208 -102
- package/lib/queryHelpers.js +13 -12
- package/lib/schema/array.js +4 -6
- package/lib/schema/bigint.js +1 -3
- package/lib/schema/boolean.js +1 -3
- package/lib/schema/buffer.js +1 -3
- package/lib/schema/date.js +8 -8
- package/lib/schema/decimal128.js +1 -3
- package/lib/schema/documentArray.js +3 -5
- package/lib/schema/double.js +1 -3
- package/lib/schema/int32.js +1 -3
- package/lib/schema/map.js +1 -4
- package/lib/schema/number.js +2 -4
- package/lib/schema/objectId.js +7 -3
- package/lib/schema/string.js +20 -5
- package/lib/schema/subdocument.js +2 -4
- package/lib/schema/union.js +50 -0
- package/lib/schema/uuid.js +1 -3
- package/lib/schema.js +40 -14
- package/lib/schemaType.js +93 -12
- package/lib/standardSchema/convertErrorToIssues.js +20 -0
- package/lib/stateMachine.js +11 -6
- package/lib/tracing.js +38 -0
- package/lib/types/array/methods/index.js +4 -4
- package/lib/types/documentArray/methods/index.js +117 -3
- package/lib/types/subdocument.js +4 -2
- package/lib/utils.js +12 -2
- package/lib/validOptions.js +1 -0
- package/package.json +30 -33
- package/{tstyche.config.json → tstyche.json} +2 -1
- package/types/augmentations.d.ts +2 -2
- package/types/document.d.ts +61 -7
- package/types/expressions.d.ts +16 -0
- package/types/index.d.ts +25 -7
- package/types/inferhydrateddoctype.d.ts +1 -1
- package/types/inferrawdoctype.d.ts +6 -3
- package/types/inferschematype.d.ts +10 -2
- package/types/models.d.ts +30 -13
- package/types/mongooseoptions.d.ts +9 -1
- package/types/populate.d.ts +52 -0
- package/types/query.d.ts +39 -12
- package/types/schemaoptions.d.ts +5 -0
- package/types/schematypes.d.ts +10 -0
- package/types/tracing.d.ts +10 -0
- package/types/utility.d.ts +11 -2
- package/lib/helpers/createJSONSchemaTypeDefinition.js +0 -24
package/lib/document.js
CHANGED
|
@@ -562,27 +562,26 @@ function $applyDefaultsToNested(val, path, doc) {
|
|
|
562
562
|
Document.prototype.$__buildDoc = function(obj, fields, skipId, exclude, hasIncludedChildren) {
|
|
563
563
|
const doc = {};
|
|
564
564
|
|
|
565
|
-
const paths = Object.keys(this.$__schema.paths)
|
|
566
|
-
// Don't build up any paths that are underneath a map, we don't know
|
|
567
|
-
// what the keys will be
|
|
568
|
-
filter(p => !p.includes('$*'));
|
|
565
|
+
const paths = Object.keys(this.$__schema.paths);
|
|
569
566
|
const plen = paths.length;
|
|
570
567
|
let ii = 0;
|
|
571
568
|
|
|
572
569
|
for (; ii < plen; ++ii) {
|
|
573
570
|
const p = paths[ii];
|
|
574
571
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (obj && '_id' in obj) {
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
572
|
+
// Don't build up any paths that are underneath a map, we don't know
|
|
573
|
+
// what the keys will be
|
|
574
|
+
if (p.includes('$*')) {
|
|
575
|
+
continue;
|
|
582
576
|
}
|
|
583
577
|
|
|
584
578
|
const path = this.$__schema.paths[p].splitPath();
|
|
585
579
|
const len = path.length;
|
|
580
|
+
// This loop only creates intermediate objects for nested paths: it never
|
|
581
|
+
// sets any leaf values, so single-segment paths are no-ops.
|
|
582
|
+
if (len === 1) {
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
586
585
|
const last = len - 1;
|
|
587
586
|
let curPath = '';
|
|
588
587
|
let doc_ = doc;
|
|
@@ -757,6 +756,7 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
757
756
|
let i;
|
|
758
757
|
const strict = self.$__.strictMode;
|
|
759
758
|
const docSchema = self.$__schema;
|
|
759
|
+
const strictRead = docSchema.options.strictRead;
|
|
760
760
|
|
|
761
761
|
for (let index = 0; index < len; ++index) {
|
|
762
762
|
i = keys[index];
|
|
@@ -774,6 +774,14 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
774
774
|
}
|
|
775
775
|
|
|
776
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
|
+
|
|
777
785
|
if (!schemaType && utils.isPOJO(value)) {
|
|
778
786
|
// assume nested object
|
|
779
787
|
if (!doc[i]) {
|
|
@@ -786,6 +794,13 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
786
794
|
}
|
|
787
795
|
init(self, value, doc[i], opts, path + '.');
|
|
788
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
|
+
}
|
|
789
804
|
doc[i] = value;
|
|
790
805
|
if (!strict && !prefix) {
|
|
791
806
|
self[i] = value;
|
|
@@ -1133,7 +1148,11 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
1133
1148
|
return this;
|
|
1134
1149
|
}
|
|
1135
1150
|
|
|
1136
|
-
|
|
1151
|
+
// Only need to clone if we're overwriting `_skipMinimizeTopLevel`, recursive
|
|
1152
|
+
// calls treat a missing `_skipMinimizeTopLevel` as `false`
|
|
1153
|
+
if (_skipMinimizeTopLevel) {
|
|
1154
|
+
options = Object.assign({}, options, { _skipMinimizeTopLevel: false });
|
|
1155
|
+
}
|
|
1137
1156
|
|
|
1138
1157
|
for (let i = 0; i < len; ++i) {
|
|
1139
1158
|
key = keys[i];
|
|
@@ -1220,15 +1239,9 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
1220
1239
|
val = handleSpreadDoc(val, true);
|
|
1221
1240
|
|
|
1222
1241
|
// if this doc is being constructed we should not trigger getters
|
|
1223
|
-
const priorVal =
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1227
|
-
if (constructing) {
|
|
1228
|
-
return void 0;
|
|
1229
|
-
}
|
|
1230
|
-
return this.$__getValue(path);
|
|
1231
|
-
})();
|
|
1242
|
+
const priorVal = this.$__.priorDoc != null ?
|
|
1243
|
+
this.$__.priorDoc.$__getValue(path) :
|
|
1244
|
+
(constructing ? void 0 : this.$__getValue(path));
|
|
1232
1245
|
|
|
1233
1246
|
if (pathType === 'nested' && val) {
|
|
1234
1247
|
if (typeof val === 'object' && val != null) {
|
|
@@ -1410,13 +1423,10 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
1410
1423
|
try {
|
|
1411
1424
|
// If the user is trying to set a ref path to a document with
|
|
1412
1425
|
// the correct model name, treat it as populated
|
|
1413
|
-
const refMatches = (() => {
|
|
1426
|
+
const refMatches = !(val instanceof Document) ? false : (() => {
|
|
1414
1427
|
if (schema.options == null) {
|
|
1415
1428
|
return false;
|
|
1416
1429
|
}
|
|
1417
|
-
if (!(val instanceof Document)) {
|
|
1418
|
-
return false;
|
|
1419
|
-
}
|
|
1420
1430
|
const model = val.constructor;
|
|
1421
1431
|
|
|
1422
1432
|
// Check ref
|
|
@@ -1954,7 +1964,9 @@ Document.prototype.get = function(path, type, options) {
|
|
|
1954
1964
|
|
|
1955
1965
|
// Fast path if we know we're just accessing top-level path on the document:
|
|
1956
1966
|
// just get the schema path, avoid `$__path()` because that does string manipulation
|
|
1957
|
-
let schema = noDottedPath ?
|
|
1967
|
+
let schema = noDottedPath ?
|
|
1968
|
+
Object.hasOwn(this.$__schema.paths, path) ? this.$__schema.paths[path] : undefined :
|
|
1969
|
+
this.$__path(path);
|
|
1958
1970
|
if (schema == null) {
|
|
1959
1971
|
schema = this.$__schema.virtualpath(path);
|
|
1960
1972
|
|
|
@@ -1964,7 +1976,7 @@ Document.prototype.get = function(path, type, options) {
|
|
|
1964
1976
|
}
|
|
1965
1977
|
|
|
1966
1978
|
if (noDottedPath) {
|
|
1967
|
-
let obj = this._doc[path];
|
|
1979
|
+
let obj = Object.hasOwn(this._doc, path) ? this._doc[path] : undefined;
|
|
1968
1980
|
if (adhoc) {
|
|
1969
1981
|
obj = adhoc.cast(obj);
|
|
1970
1982
|
}
|
|
@@ -1991,6 +2003,10 @@ Document.prototype.get = function(path, type, options) {
|
|
|
1991
2003
|
}
|
|
1992
2004
|
|
|
1993
2005
|
for (let i = 0, l = pieces.length; i < l; i++) {
|
|
2006
|
+
if (specialProperties.has(pieces[i])) {
|
|
2007
|
+
return undefined;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
1994
2010
|
if (obj?._doc) {
|
|
1995
2011
|
obj = obj._doc;
|
|
1996
2012
|
}
|
|
@@ -2012,7 +2028,7 @@ Document.prototype.get = function(path, type, options) {
|
|
|
2012
2028
|
|
|
2013
2029
|
if (schema != null && options.getters !== false) {
|
|
2014
2030
|
obj = schema.applyGetters(obj, this);
|
|
2015
|
-
} else if (this.$__schema.nested
|
|
2031
|
+
} else if (Object.hasOwn(this.$__schema.nested, path) && options.virtuals) {
|
|
2016
2032
|
// Might need to apply virtuals if this is a nested path
|
|
2017
2033
|
return applyVirtuals(this, clone(obj) || {}, { path: path });
|
|
2018
2034
|
}
|
|
@@ -2870,7 +2886,8 @@ Document.prototype.validate = async function validate(pathsToValidate, options)
|
|
|
2870
2886
|
|
|
2871
2887
|
await this._execDocumentPostHooks('validate', options, error);
|
|
2872
2888
|
} finally {
|
|
2873
|
-
delete
|
|
2889
|
+
// Assign rather than `delete` to avoid putting `$__` in dictionary mode
|
|
2890
|
+
this.$__.validateModifiedOnly = undefined;
|
|
2874
2891
|
this.$op = null;
|
|
2875
2892
|
this.$__.validating = null;
|
|
2876
2893
|
}
|
|
@@ -2913,7 +2930,7 @@ function _completeValidate(doc) {
|
|
|
2913
2930
|
}
|
|
2914
2931
|
}
|
|
2915
2932
|
|
|
2916
|
-
doc.$__.cachedRequired =
|
|
2933
|
+
doc.$__.cachedRequired = null;
|
|
2917
2934
|
doc.$emit('validate', doc);
|
|
2918
2935
|
doc.constructor.emit('validate', doc);
|
|
2919
2936
|
|
|
@@ -2981,9 +2998,40 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
|
|
|
2981
2998
|
// Skip $* paths - they represent map schemas, not actual document paths
|
|
2982
2999
|
return;
|
|
2983
3000
|
}
|
|
3001
|
+
|
|
3002
|
+
const _pathType = doc.$__schema.path(p);
|
|
3003
|
+
|
|
3004
|
+
// Optimization: if primitive path with no validators, or array of primitives
|
|
3005
|
+
// with no validators, skip validating this path entirely.
|
|
3006
|
+
// Note: paths with no _pathType (e.g. sub-paths under Mixed) must still be
|
|
3007
|
+
// added, as they trigger validation of the parent Mixed path.
|
|
3008
|
+
if (_pathType) {
|
|
3009
|
+
if (!_pathType.schema &&
|
|
3010
|
+
!_pathType.embeddedSchemaType &&
|
|
3011
|
+
_pathType.validators.length === 0 &&
|
|
3012
|
+
!_pathType.$parentSchemaDocArray &&
|
|
3013
|
+
// gh-15957: skip this optimization for SchemaMap as maps can contain subdocuments
|
|
3014
|
+
// that need validation even if the map itself has no validators
|
|
3015
|
+
!_pathType.$isSchemaMap &&
|
|
3016
|
+
!_pathType.$isSchemaUnion) {
|
|
3017
|
+
return;
|
|
3018
|
+
} else if (_pathType.$isMongooseArray &&
|
|
3019
|
+
!_pathType.$isMongooseDocumentArray && // Skip document arrays...
|
|
3020
|
+
!_pathType.embeddedSchemaType.$isMongooseArray && // and arrays of arrays
|
|
3021
|
+
_pathType.validators.length === 0 && // and arrays with top-level validators
|
|
3022
|
+
_pathType.embeddedSchemaType.validators.length === 0) {
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
|
|
2984
3027
|
paths.add(p);
|
|
2985
3028
|
}
|
|
2986
3029
|
|
|
3030
|
+
const onlyPrimitiveValues = doc.$__hasOnlyPrimitiveValues();
|
|
3031
|
+
if (onlyPrimitiveValues && paths.size === 0) {
|
|
3032
|
+
return [[], doValidateOptions];
|
|
3033
|
+
}
|
|
3034
|
+
|
|
2987
3035
|
if (!isNestedValidate) {
|
|
2988
3036
|
// If we're validating a subdocument, all this logic will run anyway on the top-level document, so skip for subdocuments.
|
|
2989
3037
|
// But only run for top-level subdocuments, because we're looking for subdocuments that are not modified at top-level but
|
|
@@ -3048,37 +3096,17 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip, isNestedValidate
|
|
|
3048
3096
|
}
|
|
3049
3097
|
}
|
|
3050
3098
|
|
|
3051
|
-
|
|
3052
|
-
const
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
if (p == null || p.startsWith(_pathType.path + '.')) {
|
|
3060
|
-
paths.delete(p);
|
|
3099
|
+
if (!onlyPrimitiveValues) {
|
|
3100
|
+
for (const path of paths) {
|
|
3101
|
+
const _pathType = doc.$__schema.path(path);
|
|
3102
|
+
if (_pathType && _pathType.$isMongooseDocumentArray) {
|
|
3103
|
+
for (const p of paths) {
|
|
3104
|
+
if (p == null || p.startsWith(_pathType.path + '.')) {
|
|
3105
|
+
paths.delete(p);
|
|
3106
|
+
}
|
|
3061
3107
|
}
|
|
3062
3108
|
}
|
|
3063
3109
|
}
|
|
3064
|
-
|
|
3065
|
-
// Optimization: if primitive path with no validators, or array of primitives
|
|
3066
|
-
// with no validators, skip validating this path entirely.
|
|
3067
|
-
if (!_pathType.schema &&
|
|
3068
|
-
!_pathType.embeddedSchemaType &&
|
|
3069
|
-
_pathType.validators.length === 0 &&
|
|
3070
|
-
!_pathType.$parentSchemaDocArray &&
|
|
3071
|
-
// gh-15957: skip this optimization for SchemaMap as maps can contain subdocuments
|
|
3072
|
-
// that need validation even if the map itself has no validators
|
|
3073
|
-
!_pathType.$isSchemaMap) {
|
|
3074
|
-
paths.delete(path);
|
|
3075
|
-
} else if (_pathType.$isMongooseArray &&
|
|
3076
|
-
!_pathType.$isMongooseDocumentArray && // Skip document arrays...
|
|
3077
|
-
!_pathType.embeddedSchemaType.$isMongooseArray && // and arrays of arrays
|
|
3078
|
-
_pathType.validators.length === 0 && // and arrays with top-level validators
|
|
3079
|
-
_pathType.embeddedSchemaType.validators.length === 0) {
|
|
3080
|
-
paths.delete(path);
|
|
3081
|
-
}
|
|
3082
3110
|
}
|
|
3083
3111
|
|
|
3084
3112
|
|
|
@@ -3193,7 +3221,7 @@ function _pushNestedArrayPaths(val, paths, path) {
|
|
|
3193
3221
|
* ignore
|
|
3194
3222
|
*/
|
|
3195
3223
|
|
|
3196
|
-
Document.prototype._execDocumentPreHooks =
|
|
3224
|
+
Document.prototype._execDocumentPreHooks = function _execDocumentPreHooks(opName, options, argsForHooks) {
|
|
3197
3225
|
const filter = buildMiddlewareFilter(options, 'pre');
|
|
3198
3226
|
return this.$__middleware.execPre(opName, this, argsForHooks || [], { filter });
|
|
3199
3227
|
};
|
|
@@ -3202,7 +3230,7 @@ Document.prototype._execDocumentPreHooks = async function _execDocumentPreHooks(
|
|
|
3202
3230
|
* ignore
|
|
3203
3231
|
*/
|
|
3204
3232
|
|
|
3205
|
-
Document.prototype._execDocumentPostHooks =
|
|
3233
|
+
Document.prototype._execDocumentPostHooks = function _execDocumentPostHooks(opName, options, error) {
|
|
3206
3234
|
const filter = buildMiddlewareFilter(options, 'post');
|
|
3207
3235
|
return this.$__middleware.execPost(opName, this, [this], { error, filter });
|
|
3208
3236
|
};
|
|
@@ -3253,6 +3281,9 @@ function _handlePathsToSkip(paths, pathsToSkip) {
|
|
|
3253
3281
|
/**
|
|
3254
3282
|
* Executes registered validation rules (skipping asynchronous validators) for this document.
|
|
3255
3283
|
*
|
|
3284
|
+
* **Deprecated.** Use [`validate()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) instead.
|
|
3285
|
+
* `validateSync()` does not run validation middleware and will be removed in Mongoose 10.
|
|
3286
|
+
*
|
|
3256
3287
|
* #### Note:
|
|
3257
3288
|
*
|
|
3258
3289
|
* This method is useful if you need synchronous validation.
|
|
@@ -3270,14 +3301,21 @@ function _handlePathsToSkip(paths, pathsToSkip) {
|
|
|
3270
3301
|
* @param {object} [options] options for validation
|
|
3271
3302
|
* @param {boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
|
|
3272
3303
|
* @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
|
|
3273
|
-
* @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware
|
|
3274
|
-
* @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
|
|
3275
|
-
* @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks
|
|
3276
3304
|
* @return {ValidationError|undefined} ValidationError if there are errors during validation, or undefined if there is no error.
|
|
3305
|
+
* @deprecated Use `validate()` instead. `validateSync()` does not run middleware and will be removed in Mongoose 10.
|
|
3277
3306
|
* @api public
|
|
3278
3307
|
*/
|
|
3279
3308
|
|
|
3280
|
-
Document.prototype.validateSync = function(
|
|
3309
|
+
Document.prototype.validateSync = function() {
|
|
3310
|
+
utils.warn('Mongoose: `Document.prototype.validateSync()` is deprecated and will be removed in Mongoose 10. Use `Document.prototype.validate()` instead.');
|
|
3311
|
+
return this.$__validateSync.apply(this, arguments);
|
|
3312
|
+
};
|
|
3313
|
+
|
|
3314
|
+
/*!
|
|
3315
|
+
* ignore
|
|
3316
|
+
*/
|
|
3317
|
+
|
|
3318
|
+
Document.prototype.$__validateSync = function(pathsToValidate, options) {
|
|
3281
3319
|
const _this = this;
|
|
3282
3320
|
|
|
3283
3321
|
if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) {
|
|
@@ -3545,6 +3583,10 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
|
|
|
3545
3583
|
* @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).
|
|
3546
3584
|
* @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)
|
|
3547
3585
|
* @param {boolean} [options.timestamps=true] if `false` and [timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this `save()`.
|
|
3586
|
+
* @param {Array} [options.pathsToSave] An array of paths that tell mongoose to only validate and save the paths in `pathsToSave`.
|
|
3587
|
+
* @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware
|
|
3588
|
+
* @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
|
|
3589
|
+
* @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks
|
|
3548
3590
|
* @method save
|
|
3549
3591
|
* @memberOf Document
|
|
3550
3592
|
* @instance
|
|
@@ -3595,25 +3637,25 @@ Document.prototype.$isValid = function(path) {
|
|
|
3595
3637
|
Document.prototype.$__reset = function reset() {
|
|
3596
3638
|
let _this = this;
|
|
3597
3639
|
|
|
3598
|
-
|
|
3599
|
-
|
|
3640
|
+
const onlyPrimitiveValues = this.$__hasOnlyPrimitiveValues();
|
|
3641
|
+
|
|
3642
|
+
// Skip for subdocuments. Also skip if doc only has primitive values,
|
|
3643
|
+
// because primitives can't be subdocs.
|
|
3644
|
+
const subdocs = !this.$isSubdocument && !onlyPrimitiveValues ? this.$getAllSubdocs({ useCache: true }) : null;
|
|
3600
3645
|
if (subdocs?.length > 0) {
|
|
3601
3646
|
for (const subdoc of subdocs) {
|
|
3602
3647
|
subdoc.$__reset();
|
|
3603
3648
|
}
|
|
3604
3649
|
}
|
|
3605
3650
|
|
|
3606
|
-
//
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
type[arrayAtomicsSymbol] = {};
|
|
3615
|
-
}
|
|
3616
|
-
});
|
|
3651
|
+
// Clear atomics on dirty paths. Walk the modified and default paths
|
|
3652
|
+
// directly instead of calling $__dirty(), which builds intermediate
|
|
3653
|
+
// arrays, a Map, and does parent-path deduplication we don't need here.
|
|
3654
|
+
// Skip entirely if the doc only has primitive values, because only arrays
|
|
3655
|
+
// and maps have atomics.
|
|
3656
|
+
if (!onlyPrimitiveValues) {
|
|
3657
|
+
this.$__resetAtomics();
|
|
3658
|
+
}
|
|
3617
3659
|
|
|
3618
3660
|
this.$__.backup = {};
|
|
3619
3661
|
this.$__.backup.activePaths = {
|
|
@@ -3636,6 +3678,40 @@ Document.prototype.$__reset = function reset() {
|
|
|
3636
3678
|
return this;
|
|
3637
3679
|
};
|
|
3638
3680
|
|
|
3681
|
+
/*!
|
|
3682
|
+
* Clear atomics on all dirty (modified + default) paths. This is a lighter
|
|
3683
|
+
* alternative to calling `$__dirty()` when we only need to clear atomics
|
|
3684
|
+
* and don't need the full {path, value, schema} objects or parent-path
|
|
3685
|
+
* deduplication.
|
|
3686
|
+
*/
|
|
3687
|
+
|
|
3688
|
+
Document.prototype.$__resetAtomics = function $__resetAtomics() {
|
|
3689
|
+
const activePaths = this.$__.activePaths;
|
|
3690
|
+
const modifyPaths = activePaths.getStatePaths('modify');
|
|
3691
|
+
const defaultPaths = activePaths.getStatePaths('default');
|
|
3692
|
+
|
|
3693
|
+
_clearAtomicsOnPaths(this, modifyPaths);
|
|
3694
|
+
_clearAtomicsOnPaths(this, defaultPaths);
|
|
3695
|
+
};
|
|
3696
|
+
|
|
3697
|
+
function _clearAtomicsOnPaths(doc, paths) {
|
|
3698
|
+
if (paths == null) {
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
const keys = Object.keys(paths);
|
|
3702
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
3703
|
+
const type = doc.$__getValue(keys[i]);
|
|
3704
|
+
if (type != null) {
|
|
3705
|
+
if (typeof type.clearAtomics === 'function') {
|
|
3706
|
+
type.clearAtomics();
|
|
3707
|
+
} else if (type[arrayAtomicsSymbol]) {
|
|
3708
|
+
type[arrayAtomicsBackupSymbol] = type[arrayAtomicsSymbol];
|
|
3709
|
+
type[arrayAtomicsSymbol] = {};
|
|
3710
|
+
}
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3639
3715
|
/*!
|
|
3640
3716
|
* ignore
|
|
3641
3717
|
*/
|
|
@@ -3709,15 +3785,17 @@ Document.prototype.$__dirty = function() {
|
|
|
3709
3785
|
}
|
|
3710
3786
|
|
|
3711
3787
|
let top = null;
|
|
3788
|
+
let foundParent = false;
|
|
3712
3789
|
|
|
3713
3790
|
const array = parentPaths(item.path);
|
|
3714
3791
|
for (let i = 0; i < array.length - 1; i++) {
|
|
3715
3792
|
if (allPaths.has(array[i])) {
|
|
3716
3793
|
top = allPaths.get(array[i]);
|
|
3794
|
+
foundParent = true;
|
|
3717
3795
|
break;
|
|
3718
3796
|
}
|
|
3719
3797
|
}
|
|
3720
|
-
if (
|
|
3798
|
+
if (!foundParent) {
|
|
3721
3799
|
minimal.push(item);
|
|
3722
3800
|
} else if (top != null &&
|
|
3723
3801
|
top[arrayAtomicsSymbol] != null &&
|
|
@@ -4660,6 +4738,9 @@ Document.prototype.equals = function(doc) {
|
|
|
4660
4738
|
* @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).
|
|
4661
4739
|
* @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.
|
|
4662
4740
|
* @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.
|
|
4741
|
+
* @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.
|
|
4742
|
+
* @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`.
|
|
4743
|
+
* @param {boolean} [options.strictPopulate=true] Set to false to allow populating paths that aren't defined in the given model's schema.
|
|
4663
4744
|
* @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
|
|
4664
4745
|
* @param {object} [options.options=null] Additional options like `limit` and `lean`.
|
|
4665
4746
|
* @param {boolean} [options.forceRepopulate=true] Set to `false` to prevent Mongoose from repopulating paths that are already populated
|
|
@@ -4995,34 +5076,53 @@ Document.prototype.$__fullPath = function(path) {
|
|
|
4995
5076
|
* });
|
|
4996
5077
|
*
|
|
4997
5078
|
* // returns an empty object, no changes happened yet
|
|
4998
|
-
* user
|
|
5079
|
+
* user.$getChanges(); // { }
|
|
4999
5080
|
*
|
|
5000
5081
|
* user.country = undefined;
|
|
5001
5082
|
* user.age = 26;
|
|
5002
5083
|
*
|
|
5003
|
-
* user
|
|
5084
|
+
* user.$getChanges(); // { $set: { age: 26 }, { $unset: { country: 1 } } }
|
|
5004
5085
|
*
|
|
5005
5086
|
* await user.save();
|
|
5006
5087
|
*
|
|
5007
|
-
* user
|
|
5088
|
+
* user.$getChanges(); // { }
|
|
5008
5089
|
*
|
|
5009
|
-
* Modifying the object that
|
|
5010
|
-
* change tracking state. Even if you `delete user
|
|
5090
|
+
* Modifying the object that `$getChanges()` returns does not affect the document's
|
|
5091
|
+
* change tracking state. Even if you `delete user.$getChanges().$set`, Mongoose
|
|
5011
5092
|
* will still send a `$set` to the server.
|
|
5012
5093
|
*
|
|
5013
5094
|
* @return {object}
|
|
5014
5095
|
* @api public
|
|
5015
|
-
* @method getChanges
|
|
5096
|
+
* @method $getChanges
|
|
5016
5097
|
* @memberOf Document
|
|
5017
5098
|
* @instance
|
|
5018
5099
|
*/
|
|
5019
5100
|
|
|
5020
|
-
Document.prototype
|
|
5101
|
+
Document.prototype.$getChanges = function() {
|
|
5021
5102
|
const delta = this.$__delta();
|
|
5022
5103
|
const changes = delta ? delta[1] : {};
|
|
5023
5104
|
return changes;
|
|
5024
5105
|
};
|
|
5025
5106
|
|
|
5107
|
+
/**
|
|
5108
|
+
* **Deprecated.** Use `$getChanges()` instead.
|
|
5109
|
+
*
|
|
5110
|
+
* Returns the changes that happened to the document
|
|
5111
|
+
* in the format that will be sent to MongoDB.
|
|
5112
|
+
*
|
|
5113
|
+
* @return {Object}
|
|
5114
|
+
* @deprecated Use `$getChanges()` instead. `getChanges` does not use the `$` prefix convention and may conflict with user-defined schema methods/properties.
|
|
5115
|
+
* @api public
|
|
5116
|
+
* @method getChanges
|
|
5117
|
+
* @memberOf Document
|
|
5118
|
+
* @instance
|
|
5119
|
+
*/
|
|
5120
|
+
|
|
5121
|
+
Document.prototype.getChanges = function() {
|
|
5122
|
+
utils.warn('`getChanges()` is deprecated, use `$getChanges()` instead.');
|
|
5123
|
+
return this.$getChanges();
|
|
5124
|
+
};
|
|
5125
|
+
|
|
5026
5126
|
/**
|
|
5027
5127
|
* Produces a special query document of the modified properties used in updates.
|
|
5028
5128
|
*
|
|
@@ -5052,20 +5152,26 @@ Document.prototype.$__delta = function $__delta(pathsToSave, pathsToSaveSet) {
|
|
|
5052
5152
|
const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
|
|
5053
5153
|
if (optimisticConcurrency) {
|
|
5054
5154
|
if (Array.isArray(optimisticConcurrency)) {
|
|
5055
|
-
|
|
5056
|
-
|
|
5155
|
+
if (!this.$__schema.options._optimisticConcurrencySet) {
|
|
5156
|
+
this.$__schema.options._optimisticConcurrencySet = new Set(optimisticConcurrency);
|
|
5157
|
+
}
|
|
5158
|
+
const optimisticConcurrencySet = this.$__schema.options._optimisticConcurrencySet;
|
|
5159
|
+
const modPaths = this.directModifiedPaths();
|
|
5057
5160
|
const hasRelevantModPaths = pathsToSave == null ?
|
|
5058
|
-
modPaths.find(path =>
|
|
5059
|
-
modPaths.find(path =>
|
|
5161
|
+
modPaths.find(path => _pathOverlapsSet(path, optimisticConcurrencySet)) :
|
|
5162
|
+
modPaths.find(path => _pathOverlapsSet(path, optimisticConcurrencySet) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5060
5163
|
if (hasRelevantModPaths) {
|
|
5061
5164
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5062
5165
|
}
|
|
5063
5166
|
} else if (Array.isArray(optimisticConcurrency?.exclude)) {
|
|
5064
|
-
|
|
5065
|
-
|
|
5167
|
+
if (!this.$__schema.options._optimisticConcurrencyExcludeSet) {
|
|
5168
|
+
this.$__schema.options._optimisticConcurrencyExcludeSet = new Set(optimisticConcurrency.exclude);
|
|
5169
|
+
}
|
|
5170
|
+
const optimisticConcurrencyExcludeSet = this.$__schema.options._optimisticConcurrencyExcludeSet;
|
|
5171
|
+
const modPaths = this.directModifiedPaths();
|
|
5066
5172
|
const hasRelevantModPaths = pathsToSave == null ?
|
|
5067
|
-
modPaths.find(path => !
|
|
5068
|
-
modPaths.find(path => !
|
|
5173
|
+
modPaths.find(path => !_pathOrAncestorInSet(path, optimisticConcurrencyExcludeSet)) :
|
|
5174
|
+
modPaths.find(path => !_pathOrAncestorInSet(path, optimisticConcurrencyExcludeSet) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5069
5175
|
if (hasRelevantModPaths) {
|
|
5070
5176
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5071
5177
|
}
|
|
@@ -5198,6 +5304,12 @@ function checkDivergentArray(doc, path, array) {
|
|
|
5198
5304
|
// If any array was selected using an $elemMatch projection, we deny the update.
|
|
5199
5305
|
// NOTE: MongoDB only supports projected $elemMatch on top level array.
|
|
5200
5306
|
const top = path.split('.')[0];
|
|
5307
|
+
if (doc.$__.selected[top] && doc.$__.selected[top].$slice != null && utils.isMongooseArray(array)) {
|
|
5308
|
+
const atomics = array[arrayAtomicsSymbol];
|
|
5309
|
+
if (atomics.$set) {
|
|
5310
|
+
return top;
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5201
5313
|
if (doc.$__.selected[top + '.$']) {
|
|
5202
5314
|
return top;
|
|
5203
5315
|
}
|
|
@@ -5573,24 +5685,47 @@ Document.prototype._applyVersionIncrement = function _applyVersionIncrement() {
|
|
|
5573
5685
|
};
|
|
5574
5686
|
|
|
5575
5687
|
/*!
|
|
5576
|
-
*
|
|
5688
|
+
* Module exports.
|
|
5577
5689
|
*/
|
|
5578
5690
|
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5691
|
+
/*!
|
|
5692
|
+
* Check if `path` or any of its ancestor paths exist in `pathSet`.
|
|
5693
|
+
* For example:
|
|
5694
|
+
* _pathOrAncestorInSet('profile.firstName', Set(['profile'])) === true
|
|
5695
|
+
* _pathOrAncestorInSet('profile', Set(['profile.firstName'])) === false
|
|
5696
|
+
*/
|
|
5697
|
+
function _pathOrAncestorInSet(path, pathSet) {
|
|
5698
|
+
if (pathSet.has(path)) {
|
|
5699
|
+
return true;
|
|
5588
5700
|
}
|
|
5589
|
-
|
|
5701
|
+
let idx = path.indexOf('.');
|
|
5702
|
+
while (idx !== -1) {
|
|
5703
|
+
if (pathSet.has(path.substring(0, idx))) {
|
|
5704
|
+
return true;
|
|
5705
|
+
}
|
|
5706
|
+
idx = path.indexOf('.', idx + 1);
|
|
5707
|
+
}
|
|
5708
|
+
return false;
|
|
5709
|
+
}
|
|
5590
5710
|
|
|
5591
5711
|
/*!
|
|
5592
|
-
*
|
|
5712
|
+
* Check if `path`, any of its ancestor paths, or any of its descendant paths
|
|
5713
|
+
* exist in `pathSet`.
|
|
5714
|
+
* For example:
|
|
5715
|
+
* _pathOverlapsSet('profile.firstName', Set(['profile'])) === true
|
|
5716
|
+
* _pathOverlapsSet('profile', Set(['profile.firstName'])) === true
|
|
5593
5717
|
*/
|
|
5718
|
+
function _pathOverlapsSet(path, pathSet) {
|
|
5719
|
+
if (_pathOrAncestorInSet(path, pathSet)) {
|
|
5720
|
+
return true;
|
|
5721
|
+
}
|
|
5722
|
+
for (const p of pathSet) {
|
|
5723
|
+
if (p.length > path.length + 1 && p[path.length] === '.' && p.slice(0, path.length) === path) {
|
|
5724
|
+
return true;
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
return false;
|
|
5728
|
+
}
|
|
5594
5729
|
|
|
5595
5730
|
Document.VERSION_WHERE = VERSION_WHERE;
|
|
5596
5731
|
Document.VERSION_INC = VERSION_INC;
|