@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.
Files changed (39) hide show
  1. package/lib/aggregate.js +26 -12
  2. package/lib/cursor/aggregationCursor.js +1 -1
  3. package/lib/cursor/queryCursor.js +1 -1
  4. package/lib/document.js +34 -11
  5. package/lib/helpers/buildMiddlewareFilter.js +24 -0
  6. package/lib/helpers/clone.js +19 -1
  7. package/lib/helpers/model/applyHooks.js +12 -1
  8. package/lib/helpers/model/castBulkWrite.js +7 -1
  9. package/lib/helpers/query/castUpdate.js +2 -2
  10. package/lib/helpers/query/sanitizeFilter.js +4 -1
  11. package/lib/helpers/query/trusted.js +1 -1
  12. package/lib/helpers/timestamps/setupTimestamps.js +45 -29
  13. package/lib/model.js +184 -47
  14. package/lib/mongoose.js +21 -1
  15. package/lib/options.js +2 -1
  16. package/lib/plugins/saveSubdocs.js +85 -67
  17. package/lib/plugins/sharding.js +33 -16
  18. package/lib/plugins/trackTransaction.js +26 -21
  19. package/lib/plugins/validateBeforeSave.js +37 -31
  20. package/lib/query.js +79 -31
  21. package/lib/queryHelpers.js +12 -4
  22. package/lib/schema/documentArray.js +11 -1
  23. package/lib/schema.js +11 -12
  24. package/lib/types/arraySubdocument.js +1 -0
  25. package/lib/types/subdocument.js +10 -6
  26. package/lib/validOptions.js +1 -0
  27. package/package.json +25 -39
  28. package/types/aggregate.d.ts +2 -0
  29. package/types/document.d.ts +17 -100
  30. package/types/index.d.ts +93 -10
  31. package/types/inferrawdoctype.d.ts +13 -3
  32. package/types/inferschematype.d.ts +6 -1
  33. package/types/middlewares.d.ts +7 -0
  34. package/types/models.d.ts +10 -54
  35. package/types/mongooseoptions.d.ts +10 -6
  36. package/types/query.d.ts +24 -3
  37. package/types/types.d.ts +2 -2
  38. package/types/utility.d.ts +1 -1
  39. 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 {String} level one of the listed read concern level or their aliases
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 {String} [verbosity]
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, this.options);
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 {String} flag
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 {String} flag
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|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
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, ...args) {
2957
- return this.$__middleware.execPre(opName, this, [...args]);
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
- return this.$__middleware.execPost(opName, this, [this], { error });
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
+ };
@@ -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, kareemOptions);
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
- const now = originalModel.base.now();
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|String} [options.strict] defaults to true
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|String} [options.strict]
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', function timestampsPreSave() {
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
- function _setTimestampsOnUpdate() {
99
- const now = currentTime != null ?
100
- currentTime() :
101
- this.model.base.now();
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;