@depup/mongoose 9.1.5-depup.0 → 9.2.1-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/lib/aggregate.js +26 -12
- package/lib/cursor/aggregationCursor.js +1 -1
- package/lib/cursor/queryCursor.js +1 -1
- package/lib/document.js +34 -11
- package/lib/helpers/buildMiddlewareFilter.js +24 -0
- package/lib/helpers/clone.js +19 -1
- package/lib/helpers/model/applyHooks.js +12 -1
- package/lib/helpers/model/castBulkWrite.js +7 -1
- package/lib/helpers/query/castUpdate.js +2 -2
- package/lib/helpers/query/sanitizeFilter.js +4 -1
- package/lib/helpers/query/trusted.js +1 -1
- package/lib/helpers/timestamps/setupTimestamps.js +45 -29
- package/lib/model.js +184 -47
- package/lib/mongoose.js +21 -1
- package/lib/options.js +2 -1
- package/lib/plugins/saveSubdocs.js +85 -67
- package/lib/plugins/sharding.js +33 -16
- package/lib/plugins/trackTransaction.js +26 -21
- package/lib/plugins/validateBeforeSave.js +37 -31
- package/lib/query.js +79 -31
- package/lib/queryHelpers.js +12 -4
- package/lib/schema/documentArray.js +11 -1
- package/lib/schema.js +11 -12
- package/lib/types/arraySubdocument.js +1 -0
- package/lib/types/subdocument.js +10 -6
- package/lib/validOptions.js +1 -0
- package/package.json +25 -39
- package/types/aggregate.d.ts +2 -0
- package/types/document.d.ts +17 -100
- package/types/index.d.ts +93 -10
- package/types/inferrawdoctype.d.ts +13 -3
- package/types/inferschematype.d.ts +6 -1
- package/types/middlewares.d.ts +7 -0
- package/types/models.d.ts +10 -54
- package/types/mongooseoptions.d.ts +10 -6
- package/types/query.d.ts +24 -3
- package/types/types.d.ts +2 -2
- package/types/utility.d.ts +1 -1
- package/types/virtuals.d.ts +2 -2
package/lib/aggregate.js
CHANGED
|
@@ -11,6 +11,7 @@ const { applyGlobalMaxTimeMS, applyGlobalDiskUse } = require('./helpers/query/ap
|
|
|
11
11
|
const clone = require('./helpers/clone');
|
|
12
12
|
const getConstructorName = require('./helpers/getConstructorName');
|
|
13
13
|
const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
|
|
14
|
+
const { buildMiddlewareFilter } = require('./helpers/buildMiddlewareFilter');
|
|
14
15
|
const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators');
|
|
15
16
|
const utils = require('./utils');
|
|
16
17
|
const { modelSymbol } = require('./helpers/symbols');
|
|
@@ -718,7 +719,7 @@ Aggregate.prototype.read = function(pref, tags) {
|
|
|
718
719
|
*
|
|
719
720
|
* await Model.aggregate(pipeline).readConcern('majority');
|
|
720
721
|
*
|
|
721
|
-
* @param {
|
|
722
|
+
* @param {'local'|'available'|'majority'|'snapshot'|'linearizable'|'l'|'a'|'m'|'s'|'lz'} level one of the listed read concern level or their aliases
|
|
722
723
|
* @see mongodb https://www.mongodb.com/docs/manual/reference/read-concern/
|
|
723
724
|
* @return {Aggregate} this
|
|
724
725
|
* @api public
|
|
@@ -784,7 +785,7 @@ Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) {
|
|
|
784
785
|
*
|
|
785
786
|
* Model.aggregate(..).explain()
|
|
786
787
|
*
|
|
787
|
-
* @param {
|
|
788
|
+
* @param {'queryPlanner'|'executionStats'|'allPlansExecution'} [verbosity]
|
|
788
789
|
* @return {Promise}
|
|
789
790
|
*/
|
|
790
791
|
|
|
@@ -800,13 +801,20 @@ Aggregate.prototype.explain = async function explain(verbosity) {
|
|
|
800
801
|
|
|
801
802
|
prepareDiscriminatorPipeline(this._pipeline, this._model.schema);
|
|
802
803
|
|
|
804
|
+
const preFilter = buildMiddlewareFilter(this.options, 'pre');
|
|
805
|
+
const postFilter = buildMiddlewareFilter(this.options, 'post');
|
|
806
|
+
|
|
807
|
+
// Remove middleware option before passing to MongoDB
|
|
808
|
+
const options = this.options != null ? { ...this.options } : {};
|
|
809
|
+
delete options.middleware;
|
|
810
|
+
|
|
803
811
|
try {
|
|
804
|
-
await model.hooks.execPre('aggregate', this);
|
|
812
|
+
await model.hooks.execPre('aggregate', this, [], { filter: preFilter });
|
|
805
813
|
} catch (error) {
|
|
806
|
-
return await model.hooks.execPost('aggregate', this, [null], { error });
|
|
814
|
+
return await model.hooks.execPost('aggregate', this, [null], { error, filter: postFilter });
|
|
807
815
|
}
|
|
808
816
|
|
|
809
|
-
const cursor = model.collection.aggregate(this._pipeline,
|
|
817
|
+
const cursor = await model.collection.aggregate(this._pipeline, options);
|
|
810
818
|
|
|
811
819
|
if (verbosity == null) {
|
|
812
820
|
verbosity = true;
|
|
@@ -816,10 +824,10 @@ Aggregate.prototype.explain = async function explain(verbosity) {
|
|
|
816
824
|
try {
|
|
817
825
|
result = await cursor.explain(verbosity);
|
|
818
826
|
} catch (error) {
|
|
819
|
-
return await model.hooks.execPost('aggregate', this, [null], { error });
|
|
827
|
+
return await model.hooks.execPost('aggregate', this, [null], { error, filter: postFilter });
|
|
820
828
|
}
|
|
821
829
|
|
|
822
|
-
await model.hooks.execPost('aggregate', this, [result], { error: null });
|
|
830
|
+
await model.hooks.execPost('aggregate', this, [result], { error: null, filter: postFilter });
|
|
823
831
|
|
|
824
832
|
return result;
|
|
825
833
|
};
|
|
@@ -893,6 +901,9 @@ Aggregate.prototype.session = function(session) {
|
|
|
893
901
|
* @param {Boolean} [options.allowDiskUse] boolean if true, the MongoDB server will use the hard drive to store data during this aggregation
|
|
894
902
|
* @param {Object} [options.collation] object see [`Aggregate.prototype.collation()`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.collation())
|
|
895
903
|
* @param {ClientSession} [options.session] ClientSession see [`Aggregate.prototype.session()`](https://mongoosejs.com/docs/api/aggregate.html#Aggregate.prototype.session())
|
|
904
|
+
* @param {Boolean|Object} [options.middleware=true] set to `false` to skip all user-defined middleware
|
|
905
|
+
* @param {Boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
|
|
906
|
+
* @param {Boolean} [options.middleware.post=true] set to `false` to skip only post hooks
|
|
896
907
|
* @see mongodb https://www.mongodb.com/docs/manual/reference/command/aggregate/
|
|
897
908
|
* @return {Aggregate} this
|
|
898
909
|
* @api public
|
|
@@ -1056,10 +1067,13 @@ Aggregate.prototype.exec = async function exec() {
|
|
|
1056
1067
|
prepareDiscriminatorPipeline(this._pipeline, this._model.schema);
|
|
1057
1068
|
stringifyFunctionOperators(this._pipeline);
|
|
1058
1069
|
|
|
1070
|
+
const preFilter = buildMiddlewareFilter(this.options, 'pre');
|
|
1071
|
+
const postFilter = buildMiddlewareFilter(this.options, 'post');
|
|
1072
|
+
|
|
1059
1073
|
try {
|
|
1060
|
-
await model.hooks.execPre('aggregate', this);
|
|
1074
|
+
await model.hooks.execPre('aggregate', this, [], { filter: preFilter });
|
|
1061
1075
|
} catch (error) {
|
|
1062
|
-
return await model.hooks.execPost('aggregate', this, [null], { error });
|
|
1076
|
+
return await model.hooks.execPost('aggregate', this, [null], { error, filter: postFilter });
|
|
1063
1077
|
}
|
|
1064
1078
|
|
|
1065
1079
|
if (!this._pipeline.length) {
|
|
@@ -1067,17 +1081,17 @@ Aggregate.prototype.exec = async function exec() {
|
|
|
1067
1081
|
}
|
|
1068
1082
|
|
|
1069
1083
|
const options = clone(this.options || {});
|
|
1084
|
+
delete options.middleware;
|
|
1070
1085
|
|
|
1071
1086
|
let result;
|
|
1072
1087
|
try {
|
|
1073
1088
|
const cursor = await collection.aggregate(this._pipeline, options);
|
|
1074
1089
|
result = await cursor.toArray();
|
|
1075
1090
|
} catch (error) {
|
|
1076
|
-
return await model.hooks.execPost('aggregate', this, [null], { error });
|
|
1091
|
+
return await model.hooks.execPost('aggregate', this, [null], { error, filter: postFilter });
|
|
1077
1092
|
}
|
|
1078
1093
|
|
|
1079
|
-
await model.hooks.execPost('aggregate', this, [result], { error: null });
|
|
1080
|
-
|
|
1094
|
+
await model.hooks.execPost('aggregate', this, [result], { error: null, filter: postFilter });
|
|
1081
1095
|
return result;
|
|
1082
1096
|
};
|
|
1083
1097
|
|
|
@@ -389,7 +389,7 @@ function _transformForAsyncIterator(doc) {
|
|
|
389
389
|
* Adds a [cursor flag](https://mongodb.github.io/node-mongodb-native/4.9/classes/AggregationCursor.html#addCursorFlag).
|
|
390
390
|
* Useful for setting the `noCursorTimeout` and `tailable` flags.
|
|
391
391
|
*
|
|
392
|
-
* @param {
|
|
392
|
+
* @param {'tailable'|'oplogReplay'|'noCursorTimeout'|'awaitData'|'partial'} flag
|
|
393
393
|
* @param {Boolean} value
|
|
394
394
|
* @return {AggregationCursor} this
|
|
395
395
|
* @api public
|
|
@@ -371,7 +371,7 @@ QueryCursor.prototype.options;
|
|
|
371
371
|
* Adds a [cursor flag](https://mongodb.github.io/node-mongodb-native/4.9/classes/FindCursor.html#addCursorFlag).
|
|
372
372
|
* Useful for setting the `noCursorTimeout` and `tailable` flags.
|
|
373
373
|
*
|
|
374
|
-
* @param {
|
|
374
|
+
* @param {'tailable'|'oplogReplay'|'noCursorTimeout'|'awaitData'|'partial'} flag
|
|
375
375
|
* @param {Boolean} value
|
|
376
376
|
* @return {AggregationCursor} this
|
|
377
377
|
* @api public
|
package/lib/document.js
CHANGED
|
@@ -41,6 +41,7 @@ const minimize = require('./helpers/minimize');
|
|
|
41
41
|
const mpath = require('mpath');
|
|
42
42
|
const parentPaths = require('./helpers/path/parentPaths');
|
|
43
43
|
const queryhelpers = require('./queryHelpers');
|
|
44
|
+
const { buildMiddlewareFilter } = require('./helpers/buildMiddlewareFilter');
|
|
44
45
|
const utils = require('./utils');
|
|
45
46
|
const isPromise = require('./helpers/isPromise');
|
|
46
47
|
|
|
@@ -136,6 +137,8 @@ function Document(obj, fields, options) {
|
|
|
136
137
|
this.$__.strictMode = fields;
|
|
137
138
|
}
|
|
138
139
|
fields = undefined;
|
|
140
|
+
} else if (options.strict !== undefined) {
|
|
141
|
+
this.$__.strictMode = options.strict;
|
|
139
142
|
} else if (schema.options.strict !== true) {
|
|
140
143
|
this.$__.strictMode = schema.options.strict;
|
|
141
144
|
}
|
|
@@ -788,6 +791,12 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
788
791
|
self[i] = value;
|
|
789
792
|
} else if (opts?.virtuals && (i in docSchema.virtuals)) {
|
|
790
793
|
self[i] = value;
|
|
794
|
+
} else if (opts?.strict === 'throw') {
|
|
795
|
+
// Only use strict: 'throw' semantics if explicit `strict: 'throw'` option
|
|
796
|
+
// passed in, like via `MyModel.hydrate(obj, null, { strict: 'throw' })`
|
|
797
|
+
// This is for backwards compatibility - strict: 'throw' at the schema level
|
|
798
|
+
// does not apply to documents loaded from the db.
|
|
799
|
+
throw new StrictModeError(i);
|
|
791
800
|
}
|
|
792
801
|
} else {
|
|
793
802
|
// Retain order when overwriting defaults
|
|
@@ -852,8 +861,11 @@ function init(self, obj, doc, opts, prefix) {
|
|
|
852
861
|
* @param {Object} update
|
|
853
862
|
* @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
|
|
854
863
|
* @param {Object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and the [Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
|
|
855
|
-
* @param {Boolean|
|
|
864
|
+
* @param {Boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
|
856
865
|
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
|
|
866
|
+
* @param {Boolean|Object} [options.middleware=true] set to `false` to skip all user-defined middleware
|
|
867
|
+
* @param {Boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
|
|
868
|
+
* @param {Boolean} [options.middleware.post=true] set to `false` to skip only post hooks
|
|
857
869
|
* @return {Query}
|
|
858
870
|
* @api public
|
|
859
871
|
* @memberOf Document
|
|
@@ -864,7 +876,7 @@ Document.prototype.updateOne = function updateOne(update, options) {
|
|
|
864
876
|
const query = this.constructor.updateOne();
|
|
865
877
|
const self = this;
|
|
866
878
|
query.pre(async function queryPreUpdateOne() {
|
|
867
|
-
const res = await self._execDocumentPreHooks('updateOne', self, update, options);
|
|
879
|
+
const res = await self._execDocumentPreHooks('updateOne', options, [self, update, options]);
|
|
868
880
|
// `self` is passed to pre hooks as argument for backwards compatibility, but that
|
|
869
881
|
// isn't the actual arguments passed to the wrapped function.
|
|
870
882
|
if (res[0] !== self || res[1] !== update || res[2] !== options) {
|
|
@@ -884,7 +896,7 @@ Document.prototype.updateOne = function updateOne(update, options) {
|
|
|
884
896
|
return res;
|
|
885
897
|
});
|
|
886
898
|
query.post(function queryPostUpdateOne() {
|
|
887
|
-
return self._execDocumentPostHooks('updateOne');
|
|
899
|
+
return self._execDocumentPostHooks('updateOne', options);
|
|
888
900
|
});
|
|
889
901
|
|
|
890
902
|
return query;
|
|
@@ -2638,6 +2650,9 @@ Document.prototype.isDirectSelected = function isDirectSelected(path) {
|
|
|
2638
2650
|
* @param {Object} [options] internal options
|
|
2639
2651
|
* @param {Boolean} [options.validateModifiedOnly=false] if `true` mongoose validates only modified paths.
|
|
2640
2652
|
* @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
|
|
2653
|
+
* @param {Boolean|Object} [options.middleware=true] set to `false` to skip all user-defined middleware
|
|
2654
|
+
* @param {Boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
|
|
2655
|
+
* @param {Boolean} [options.middleware.post=true] set to `false` to skip only post hooks
|
|
2641
2656
|
* @return {Promise} Returns a Promise.
|
|
2642
2657
|
* @api public
|
|
2643
2658
|
*/
|
|
@@ -2953,16 +2968,18 @@ function _pushNestedArrayPaths(val, paths, path) {
|
|
|
2953
2968
|
* ignore
|
|
2954
2969
|
*/
|
|
2955
2970
|
|
|
2956
|
-
Document.prototype._execDocumentPreHooks = async function _execDocumentPreHooks(opName,
|
|
2957
|
-
|
|
2971
|
+
Document.prototype._execDocumentPreHooks = async function _execDocumentPreHooks(opName, options, argsForHooks) {
|
|
2972
|
+
const filter = buildMiddlewareFilter(options, 'pre');
|
|
2973
|
+
return this.$__middleware.execPre(opName, this, argsForHooks || [], { filter });
|
|
2958
2974
|
};
|
|
2959
2975
|
|
|
2960
2976
|
/*!
|
|
2961
2977
|
* ignore
|
|
2962
2978
|
*/
|
|
2963
2979
|
|
|
2964
|
-
Document.prototype._execDocumentPostHooks = async function _execDocumentPostHooks(opName, error) {
|
|
2965
|
-
|
|
2980
|
+
Document.prototype._execDocumentPostHooks = async function _execDocumentPostHooks(opName, options, error) {
|
|
2981
|
+
const filter = buildMiddlewareFilter(options, 'post');
|
|
2982
|
+
return this.$__middleware.execPost(opName, this, [this], { error, filter });
|
|
2966
2983
|
};
|
|
2967
2984
|
|
|
2968
2985
|
/*!
|
|
@@ -2971,9 +2988,9 @@ Document.prototype._execDocumentPostHooks = async function _execDocumentPostHook
|
|
|
2971
2988
|
|
|
2972
2989
|
Document.prototype.$__validate = async function $__validate(pathsToValidate, options) {
|
|
2973
2990
|
try {
|
|
2974
|
-
[options] = await this._execDocumentPreHooks('validate', options);
|
|
2991
|
+
[options] = await this._execDocumentPreHooks('validate', options, [options]);
|
|
2975
2992
|
} catch (error) {
|
|
2976
|
-
await this._execDocumentPostHooks('validate', error);
|
|
2993
|
+
await this._execDocumentPostHooks('validate', options, error);
|
|
2977
2994
|
return;
|
|
2978
2995
|
}
|
|
2979
2996
|
|
|
@@ -3077,7 +3094,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
|
|
|
3077
3094
|
|
|
3078
3095
|
if (paths.length === 0) {
|
|
3079
3096
|
const error = _complete();
|
|
3080
|
-
await this._execDocumentPostHooks('validate', error);
|
|
3097
|
+
await this._execDocumentPostHooks('validate', options, error);
|
|
3081
3098
|
return;
|
|
3082
3099
|
}
|
|
3083
3100
|
|
|
@@ -3100,7 +3117,7 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
|
|
|
3100
3117
|
}
|
|
3101
3118
|
await Promise.all(promises);
|
|
3102
3119
|
const error = _complete();
|
|
3103
|
-
await this._execDocumentPostHooks('validate', error);
|
|
3120
|
+
await this._execDocumentPostHooks('validate', options, error);
|
|
3104
3121
|
|
|
3105
3122
|
async function validatePath(path) {
|
|
3106
3123
|
if (path == null || validated[path]) {
|
|
@@ -3225,6 +3242,9 @@ function _handlePathsToSkip(paths, pathsToSkip) {
|
|
|
3225
3242
|
* @param {Object} [options] options for validation
|
|
3226
3243
|
* @param {Boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
|
|
3227
3244
|
* @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
|
|
3245
|
+
* @param {Boolean|Object} [options.middleware=true] set to `false` to skip all user-defined middleware
|
|
3246
|
+
* @param {Boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
|
|
3247
|
+
* @param {Boolean} [options.middleware.post=true] set to `false` to skip only post hooks
|
|
3228
3248
|
* @return {ValidationError|undefined} ValidationError if there are errors during validation, or undefined if there is no error.
|
|
3229
3249
|
* @api public
|
|
3230
3250
|
*/
|
|
@@ -4154,6 +4174,7 @@ Document.prototype.$__toObjectShallow = function $__toObjectShallow(schemaFields
|
|
|
4154
4174
|
* @param {Boolean} [options.versionKey=true] if false, exclude the version key (`__v` by default) from the output
|
|
4155
4175
|
* @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`.
|
|
4156
4176
|
* @param {Boolean} [options.flattenObjectIds=false] if true, convert any ObjectIds in the result to 24 character hex strings.
|
|
4177
|
+
* @param {Boolean} [options.flattenUUIDs=false] if true, convert any UUIDs in the result to 36-character UUID strings in 8-4-4-4-12 format.
|
|
4157
4178
|
* @param {Boolean} [options.useProjection=false] - If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema.
|
|
4158
4179
|
* @param {Boolean} [options.schemaFieldsOnly=false] - If true, the resulting object will only have fields that are defined in the document's schema. By default, `toObject()` returns all fields in the underlying document from MongoDB, including ones that are not listed in the schema.
|
|
4159
4180
|
* @return {Object} document as a plain old JavaScript object (POJO). This object may contain ObjectIds, Maps, Dates, mongodb.Binary, Buffers, and other non-POJO values.
|
|
@@ -4426,6 +4447,7 @@ function omitDeselectedFields(self, json) {
|
|
|
4426
4447
|
* @param {Object} options
|
|
4427
4448
|
* @param {Boolean} [options.flattenMaps=true] if true, convert Maps to [POJOs](https://masteringjs.io/tutorials/fundamentals/pojo). Useful if you want to `JSON.stringify()` the result.
|
|
4428
4449
|
* @param {Boolean} [options.flattenObjectIds=false] if true, convert any ObjectIds in the result to 24 character hex strings.
|
|
4450
|
+
* @param {Boolean} [options.flattenUUIDs=false] if true, convert any UUIDs in the result to 36-character UUID strings in 8-4-4-4-12 format.
|
|
4429
4451
|
* @param {Boolean} [options.schemaFieldsOnly=false] - If true, the resulting object will only have fields that are defined in the document's schema. By default, `toJSON()` returns all fields in the underlying document from MongoDB, including ones that are not listed in the schema.
|
|
4430
4452
|
* @return {Object}
|
|
4431
4453
|
* @see Document#toObject https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()
|
|
@@ -4491,6 +4513,7 @@ Document.prototype.$parent = Document.prototype.parent;
|
|
|
4491
4513
|
|
|
4492
4514
|
Document.prototype.$__setParent = function $__setParent(parent) {
|
|
4493
4515
|
this.$__.parent = parent;
|
|
4516
|
+
this.$__parent = parent;
|
|
4494
4517
|
};
|
|
4495
4518
|
|
|
4496
4519
|
/**
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const symbols = require('../schema/symbols');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Filter predicate that returns true for built-in middleware (marked with `builtInMiddleware` symbol).
|
|
7
|
+
*/
|
|
8
|
+
const isBuiltInMiddleware = hook => hook.fn[symbols.builtInMiddleware];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Builds a filter for kareem's execPre/execPost based on middleware options.
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} options - Options object that may contain `middleware` setting
|
|
14
|
+
* @param {String} phase - Either 'pre' or 'post'
|
|
15
|
+
* @returns {Function|null} - null runs all middleware, isBuiltInMiddleware skips user middleware
|
|
16
|
+
*/
|
|
17
|
+
function buildMiddlewareFilter(options, phase) {
|
|
18
|
+
const shouldRun = options?.middleware?.[phase] ?? options?.middleware ?? true;
|
|
19
|
+
return shouldRun ? null : isBuiltInMiddleware;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
buildMiddlewareFilter
|
|
24
|
+
};
|
package/lib/helpers/clone.js
CHANGED
|
@@ -12,6 +12,9 @@ const isPOJO = require('./isPOJO');
|
|
|
12
12
|
const symbols = require('./symbols');
|
|
13
13
|
const trustedSymbol = require('./query/trusted').trustedSymbol;
|
|
14
14
|
const BSON = require('mongodb/lib/bson');
|
|
15
|
+
const UUID = BSON.UUID;
|
|
16
|
+
|
|
17
|
+
const Binary = BSON.Binary;
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* Object clone with Mongoose natives support.
|
|
@@ -45,6 +48,14 @@ function clone(obj, options, isArrayChild) {
|
|
|
45
48
|
|
|
46
49
|
if (isMongooseObject(obj)) {
|
|
47
50
|
if (options) {
|
|
51
|
+
if (options.flattenUUIDs) {
|
|
52
|
+
if (obj instanceof Binary && obj._subtype === Binary.SUBTYPE_UUID) {
|
|
53
|
+
return obj.toString();
|
|
54
|
+
}
|
|
55
|
+
if (obj?.isMongooseBuffer && obj._subtype === Binary.SUBTYPE_UUID) {
|
|
56
|
+
return obj.toUUID().toString();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
48
59
|
if (options.retainDocuments && obj.$__ != null) {
|
|
49
60
|
const clonedDoc = obj.$clone();
|
|
50
61
|
if (obj.__index != null) {
|
|
@@ -61,7 +72,7 @@ function clone(obj, options, isArrayChild) {
|
|
|
61
72
|
const MongooseMap = obj.constructor;
|
|
62
73
|
const ret = new MongooseMap({}, obj.$__path, clonedParent, obj.$__schemaType);
|
|
63
74
|
for (const [key, value] of obj) {
|
|
64
|
-
ret.$__set(key, clone(value, options));
|
|
75
|
+
ret.$__set(key, clone(value, { ...options, parentDoc: clonedParent }));
|
|
65
76
|
}
|
|
66
77
|
return ret;
|
|
67
78
|
}
|
|
@@ -111,6 +122,13 @@ function clone(obj, options, isArrayChild) {
|
|
|
111
122
|
return Decimal.fromString(obj.toString());
|
|
112
123
|
}
|
|
113
124
|
|
|
125
|
+
if (obj instanceof UUID) {
|
|
126
|
+
if (options?.flattenUUIDs) {
|
|
127
|
+
return obj.toJSON();
|
|
128
|
+
}
|
|
129
|
+
return new UUID(obj.buffer);
|
|
130
|
+
}
|
|
131
|
+
|
|
114
132
|
// object created with Object.create(null)
|
|
115
133
|
if (!objConstructor && isObject(obj)) {
|
|
116
134
|
return cloneObject(obj, options, isArrayChild);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { buildMiddlewareFilter } = require('../buildMiddlewareFilter');
|
|
4
|
+
|
|
3
5
|
/*!
|
|
4
6
|
* ignore
|
|
5
7
|
*/
|
|
@@ -84,7 +86,16 @@ function applyHooks(model, schema, options) {
|
|
|
84
86
|
model._middleware = middleware;
|
|
85
87
|
|
|
86
88
|
objToDecorate.$__init = middleware.
|
|
87
|
-
createWrapperSync('init', objToDecorate.$__init, null,
|
|
89
|
+
createWrapperSync('init', objToDecorate.$__init, null, {
|
|
90
|
+
...kareemOptions,
|
|
91
|
+
getOptions: (args) => {
|
|
92
|
+
const opts = args[1];
|
|
93
|
+
return {
|
|
94
|
+
pre: { filter: buildMiddlewareFilter(opts, 'pre') },
|
|
95
|
+
post: { filter: buildMiddlewareFilter(opts, 'post') }
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
});
|
|
88
99
|
|
|
89
100
|
// Support hooks for custom methods
|
|
90
101
|
const customMethods = Object.keys(schema.methods);
|
|
@@ -21,7 +21,13 @@ const setDefaultsOnInsert = require('../setDefaultsOnInsert');
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
module.exports = function castBulkWrite(originalModel, op, options) {
|
|
24
|
-
|
|
24
|
+
let now;
|
|
25
|
+
const timestampOpts = originalModel.schema.get('timestamps');
|
|
26
|
+
if (typeof timestampOpts?.currentTime === 'function') {
|
|
27
|
+
now = timestampOpts.currentTime();
|
|
28
|
+
} else {
|
|
29
|
+
now = originalModel.base.now();
|
|
30
|
+
}
|
|
25
31
|
|
|
26
32
|
if (op['insertOne']) {
|
|
27
33
|
return callback => module.exports.castInsertOne(originalModel, op['insertOne'], options).then(() => callback(null), err => callback(err));
|
|
@@ -41,7 +41,7 @@ const mongodbUpdateOperators = new Set([
|
|
|
41
41
|
* @param {Schema} schema
|
|
42
42
|
* @param {Object} obj
|
|
43
43
|
* @param {Object} [options]
|
|
44
|
-
* @param {Boolean|
|
|
44
|
+
* @param {Boolean|'throw'} [options.strict] defaults to true
|
|
45
45
|
* @param {Query} context passed to setters
|
|
46
46
|
* @return {Boolean} true iff the update is non-empty
|
|
47
47
|
* @api private
|
|
@@ -205,7 +205,7 @@ function castPipelineOperator(op, val) {
|
|
|
205
205
|
* @param {Object} obj part of a query
|
|
206
206
|
* @param {String} op the atomic operator ($pull, $set, etc)
|
|
207
207
|
* @param {Object} [options]
|
|
208
|
-
* @param {Boolean|
|
|
208
|
+
* @param {Boolean|'throw'} [options.strict]
|
|
209
209
|
* @param {Query} context
|
|
210
210
|
* @param {Object} filter
|
|
211
211
|
* @param {String} pref path prefix (internal only)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const MongooseError = require('../../error/mongooseError');
|
|
3
4
|
const hasDollarKeys = require('./hasDollarKeys');
|
|
4
5
|
const { trustedSymbol } = require('./trusted');
|
|
5
6
|
|
|
@@ -20,9 +21,11 @@ module.exports = function sanitizeFilter(filter) {
|
|
|
20
21
|
if (value?.[trustedSymbol]) {
|
|
21
22
|
continue;
|
|
22
23
|
}
|
|
23
|
-
if (key === '$and' || key === '$or') {
|
|
24
|
+
if (key === '$and' || key === '$or' || key === '$nor') {
|
|
24
25
|
sanitizeFilter(value);
|
|
25
26
|
continue;
|
|
27
|
+
} else if (key === '$jsonSchema' || key === '$where' || key === '$expr' || key === '$text') {
|
|
28
|
+
throw new MongooseError(key + ' is not allowed with sanitizeFilter');
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
if (hasDollarKeys(value)) {
|
|
@@ -5,7 +5,7 @@ const trustedSymbol = Symbol('mongoose#trustedSymbol');
|
|
|
5
5
|
exports.trustedSymbol = trustedSymbol;
|
|
6
6
|
|
|
7
7
|
exports.trusted = function trusted(obj) {
|
|
8
|
-
if (obj == null || typeof obj !== 'object') {
|
|
8
|
+
if (obj == null || (typeof obj !== 'object' && typeof obj !== 'function')) {
|
|
9
9
|
return obj;
|
|
10
10
|
}
|
|
11
11
|
obj[trustedSymbol] = true;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const applyTimestampsToChildren = require('../update/applyTimestampsToChildren');
|
|
4
4
|
const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate');
|
|
5
|
-
const get = require('../get');
|
|
6
5
|
const handleTimestampOption = require('../schema/handleTimestampOption');
|
|
7
6
|
const setDocumentTimestamps = require('./setDocumentTimestamps');
|
|
8
7
|
const symbols = require('../../schema/symbols');
|
|
@@ -42,14 +41,7 @@ module.exports = function setupTimestamps(schema, timestamps) {
|
|
|
42
41
|
|
|
43
42
|
schema.add(schemaAdditions);
|
|
44
43
|
|
|
45
|
-
schema.pre('save',
|
|
46
|
-
const timestampOption = get(this, '$__.saveOptions.timestamps');
|
|
47
|
-
if (timestampOption === false) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
setDocumentTimestamps(this, timestampOption, currentTime, createdAt, updatedAt);
|
|
52
|
-
});
|
|
44
|
+
schema.pre('save', timestampsPreSave);
|
|
53
45
|
|
|
54
46
|
schema.methods.initializeTimestamps = function(timestampsOptions) {
|
|
55
47
|
if (timestampsOptions === false) {
|
|
@@ -85,8 +77,6 @@ module.exports = function setupTimestamps(schema, timestamps) {
|
|
|
85
77
|
return this;
|
|
86
78
|
};
|
|
87
79
|
|
|
88
|
-
_setTimestampsOnUpdate[symbols.builtInMiddleware] = true;
|
|
89
|
-
|
|
90
80
|
const opts = { query: true, model: false };
|
|
91
81
|
schema.pre('findOneAndReplace', opts, _setTimestampsOnUpdate);
|
|
92
82
|
schema.pre('findOneAndUpdate', opts, _setTimestampsOnUpdate);
|
|
@@ -94,23 +84,49 @@ module.exports = function setupTimestamps(schema, timestamps) {
|
|
|
94
84
|
schema.pre('update', opts, _setTimestampsOnUpdate);
|
|
95
85
|
schema.pre('updateOne', opts, _setTimestampsOnUpdate);
|
|
96
86
|
schema.pre('updateMany', opts, _setTimestampsOnUpdate);
|
|
87
|
+
};
|
|
97
88
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Replacing with null update should still trigger timestamps
|
|
103
|
-
if (replaceOps.has(this.op) && this.getUpdate() == null) {
|
|
104
|
-
this.setUpdate({});
|
|
105
|
-
}
|
|
106
|
-
applyTimestampsToUpdate(
|
|
107
|
-
now,
|
|
108
|
-
createdAt,
|
|
109
|
-
updatedAt,
|
|
110
|
-
this.getUpdate(),
|
|
111
|
-
this._mongooseOptions,
|
|
112
|
-
replaceOps.has(this.op)
|
|
113
|
-
);
|
|
114
|
-
applyTimestampsToChildren(now, this.getUpdate(), this.model.schema);
|
|
89
|
+
function timestampsPreSave() {
|
|
90
|
+
const timestampOption = this.$__?.saveOptions?.timestamps;
|
|
91
|
+
if (timestampOption === false) {
|
|
92
|
+
return;
|
|
115
93
|
}
|
|
116
|
-
|
|
94
|
+
|
|
95
|
+
const schema = this.$__schema;
|
|
96
|
+
const { createdAt, updatedAt } = schema.$timestamps;
|
|
97
|
+
const timestamps = schema.options.timestamps;
|
|
98
|
+
const currentTime = timestamps != null && Object.hasOwn(timestamps, 'currentTime') ?
|
|
99
|
+
timestamps.currentTime :
|
|
100
|
+
null;
|
|
101
|
+
|
|
102
|
+
setDocumentTimestamps(this, timestampOption, currentTime, createdAt, updatedAt);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function _setTimestampsOnUpdate() {
|
|
106
|
+
const schema = this.model.schema;
|
|
107
|
+
const { createdAt, updatedAt } = schema.$timestamps;
|
|
108
|
+
const timestamps = schema.options.timestamps;
|
|
109
|
+
const currentTime = timestamps != null && Object.hasOwn(timestamps, 'currentTime') ?
|
|
110
|
+
timestamps.currentTime :
|
|
111
|
+
null;
|
|
112
|
+
|
|
113
|
+
const now = currentTime != null ?
|
|
114
|
+
currentTime() :
|
|
115
|
+
this.model.base.now();
|
|
116
|
+
// Replacing with null update should still trigger timestamps
|
|
117
|
+
if (replaceOps.has(this.op) && this.getUpdate() == null) {
|
|
118
|
+
this.setUpdate({});
|
|
119
|
+
}
|
|
120
|
+
applyTimestampsToUpdate(
|
|
121
|
+
now,
|
|
122
|
+
createdAt,
|
|
123
|
+
updatedAt,
|
|
124
|
+
this.getUpdate(),
|
|
125
|
+
this._mongooseOptions,
|
|
126
|
+
replaceOps.has(this.op)
|
|
127
|
+
);
|
|
128
|
+
applyTimestampsToChildren(now, this.getUpdate(), this.model.schema);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
timestampsPreSave[symbols.builtInMiddleware] = true;
|
|
132
|
+
_setTimestampsOnUpdate[symbols.builtInMiddleware] = true;
|