mongoose 6.6.7 → 6.7.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 CHANGED
@@ -111,11 +111,11 @@ Aggregate.prototype.model = function(model) {
111
111
  this._model = model;
112
112
  if (model.schema != null) {
113
113
  if (this.options.readPreference == null &&
114
- model.schema.options.read != null) {
114
+ model.schema.options.read != null) {
115
115
  this.options.readPreference = model.schema.options.read;
116
116
  }
117
117
  if (this.options.collation == null &&
118
- model.schema.options.collation != null) {
118
+ model.schema.options.collation != null) {
119
119
  this.options.collation = model.schema.options.collation;
120
120
  }
121
121
  }
@@ -158,7 +158,7 @@ Aggregate.prototype.append = function() {
158
158
  * Requires MongoDB v3.4+ to work
159
159
  *
160
160
  * #### Example:
161
- *
161
+ *
162
162
  * // adding new fields based on existing fields
163
163
  * aggregate.addFields({
164
164
  * newField: '$b.nested'
@@ -328,6 +328,28 @@ Aggregate.prototype.project = function(arg) {
328
328
  * @api public
329
329
  */
330
330
 
331
+ /**
332
+ * Appends a new $fill operator to this aggregate pipeline.
333
+ *
334
+ * #### Example:
335
+ *
336
+ * aggregate.fill({
337
+ * output: {
338
+ * bootsSold: { value: 0 },
339
+ * sandalsSold: { value: 0 },
340
+ * sneakersSold: { value: 0 }
341
+ * }
342
+ * });
343
+ *
344
+ * @see $fill https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/
345
+ * @method fill
346
+ * @memberOf Aggregate
347
+ * @instance
348
+ * @param {Object} arg $fill operator contents
349
+ * @return {Aggregate}
350
+ * @api public
351
+ */
352
+
331
353
  /**
332
354
  * Appends a new $geoNear operator to this aggregate pipeline.
333
355
  *
@@ -366,7 +388,7 @@ Aggregate.prototype.near = function(arg) {
366
388
  * define methods
367
389
  */
368
390
 
369
- 'group match skip limit out densify'.split(' ').forEach(function($operator) {
391
+ 'group match skip limit out densify fill'.split(' ').forEach(function($operator) {
370
392
  Aggregate.prototype[$operator] = function(arg) {
371
393
  const op = {};
372
394
  op['$' + $operator] = arg;
@@ -702,7 +724,7 @@ Aggregate.prototype.readConcern = function(level) {
702
724
  Aggregate.prototype.redact = function(expression, thenExpr, elseExpr) {
703
725
  if (arguments.length === 3) {
704
726
  if ((typeof thenExpr === 'string' && !validRedactStringValues.has(thenExpr)) ||
705
- (typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {
727
+ (typeof elseExpr === 'string' && !validRedactStringValues.has(elseExpr))) {
706
728
  throw new Error('If thenExpr or elseExpr is string, it must be either $$DESCEND, $$PRUNE or $$KEEP');
707
729
  }
708
730
 
@@ -1099,9 +1121,7 @@ Aggregate.prototype.catch = function(reject) {
1099
1121
 
1100
1122
  if (Symbol.asyncIterator != null) {
1101
1123
  Aggregate.prototype[Symbol.asyncIterator] = function() {
1102
- return this.cursor({ useMongooseAggCursor: true }).
1103
- transformNull().
1104
- _transformForAsyncIterator();
1124
+ return this.cursor({ useMongooseAggCursor: true }).transformNull()._transformForAsyncIterator();
1105
1125
  };
1106
1126
  }
1107
1127
 
package/lib/document.js CHANGED
@@ -948,6 +948,48 @@ Document.prototype.$session = function $session(session) {
948
948
  return session;
949
949
  };
950
950
 
951
+ /**
952
+ * Getter/setter around whether this document will apply timestamps by
953
+ * default when using `save()` and `bulkSave()`.
954
+ *
955
+ * #### Example:
956
+ *
957
+ * const TestModel = mongoose.model('Test', new Schema({ name: String }, { timestamps: true }));
958
+ * const doc = new TestModel({ name: 'John Smith' });
959
+ *
960
+ * doc.$timestamps(); // true
961
+ *
962
+ * doc.$timestamps(false);
963
+ * await doc.save(); // Does **not** apply timestamps
964
+ *
965
+ * @param {Boolean} [value] overwrite the current session
966
+ * @return {Document|boolean|undefined} When used as a getter (no argument), a boolean will be returned indicating the timestamps option state or if unset "undefined" will be used, otherwise will return "this"
967
+ * @method $timestamps
968
+ * @api public
969
+ * @memberOf Document
970
+ */
971
+
972
+ Document.prototype.$timestamps = function $timestamps(value) {
973
+ if (arguments.length === 0) {
974
+ if (this.$__.timestamps != null) {
975
+ return this.$__.timestamps;
976
+ }
977
+
978
+ if (this.$__schema) {
979
+ return this.$__schema.options.timestamps;
980
+ }
981
+
982
+ return undefined;
983
+ }
984
+
985
+ const currentValue = this.$timestamps();
986
+ if (value !== currentValue) {
987
+ this.$__.timestamps = value;
988
+ }
989
+
990
+ return this;
991
+ };
992
+
951
993
  /**
952
994
  * Overwrite all values in this document with the values of `obj`, except
953
995
  * for immutable properties. Behaves similarly to `set()`, except for it
@@ -0,0 +1,101 @@
1
+ /*!
2
+ * Module requirements
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const MongooseError = require('./mongooseError');
8
+ const util = require('util');
9
+ const combinePathErrors = require('../helpers/error/combinePathErrors');
10
+
11
+ class SetOptionError extends MongooseError {
12
+ /**
13
+ * Mongoose.set Error
14
+ *
15
+ * @api private
16
+ * @inherits MongooseError
17
+ */
18
+ constructor() {
19
+ super('');
20
+
21
+ this.errors = {};
22
+ }
23
+
24
+ /**
25
+ * Console.log helper
26
+ */
27
+ toString() {
28
+ return combinePathErrors(this);
29
+ }
30
+
31
+ /**
32
+ * inspect helper
33
+ * @api private
34
+ */
35
+ inspect() {
36
+ return Object.assign(new Error(this.message), this);
37
+ }
38
+
39
+ /**
40
+ * add message
41
+ * @param {String} key
42
+ * @param {String|Error} error
43
+ * @api private
44
+ */
45
+ addError(key, error) {
46
+ if (error instanceof SetOptionError) {
47
+ const { errors } = error;
48
+ for (const optionKey of Object.keys(errors)) {
49
+ this.addError(optionKey, errors[optionKey]);
50
+ }
51
+
52
+ return;
53
+ }
54
+
55
+ this.errors[key] = error;
56
+ this.message = combinePathErrors(this);
57
+ }
58
+ }
59
+
60
+
61
+ if (util.inspect.custom) {
62
+ // Avoid Node deprecation warning DEP0079
63
+ SetOptionError.prototype[util.inspect.custom] = SetOptionError.prototype.inspect;
64
+ }
65
+
66
+ /**
67
+ * Helper for JSON.stringify
68
+ * Ensure `name` and `message` show up in toJSON output re: gh-9847
69
+ * @api private
70
+ */
71
+ Object.defineProperty(SetOptionError.prototype, 'toJSON', {
72
+ enumerable: false,
73
+ writable: false,
74
+ configurable: true,
75
+ value: function() {
76
+ return Object.assign({}, this, { name: this.name, message: this.message });
77
+ }
78
+ });
79
+
80
+
81
+ Object.defineProperty(SetOptionError.prototype, 'name', {
82
+ value: 'SetOptionError'
83
+ });
84
+
85
+ class SetOptionInnerError extends MongooseError {
86
+ /**
87
+ * Error for the "errors" array in "SetOptionError" with consistent message
88
+ * @param {String} key
89
+ */
90
+ constructor(key) {
91
+ super(`"${key}" is not a valid option to set`);
92
+ }
93
+ }
94
+
95
+ SetOptionError.SetOptionInnerError = SetOptionInnerError;
96
+
97
+ /*!
98
+ * Module exports
99
+ */
100
+
101
+ module.exports = SetOptionError;
@@ -7,6 +7,7 @@
7
7
  const MongooseError = require('./mongooseError');
8
8
  const getConstructorName = require('../helpers/getConstructorName');
9
9
  const util = require('util');
10
+ const combinePathErrors = require('../helpers/error/combinePathErrors');
10
11
 
11
12
  class ValidationError extends MongooseError {
12
13
  /**
@@ -38,7 +39,7 @@ class ValidationError extends MongooseError {
38
39
  * Console.log helper
39
40
  */
40
41
  toString() {
41
- return this.name + ': ' + _generateMessage(this);
42
+ return this.name + ': ' + combinePathErrors(this);
42
43
  }
43
44
 
44
45
  /**
@@ -66,7 +67,7 @@ class ValidationError extends MongooseError {
66
67
  }
67
68
 
68
69
  this.errors[path] = error;
69
- this.message = this._message + ': ' + _generateMessage(this);
70
+ this.message = this._message + ': ' + combinePathErrors(this);
70
71
  }
71
72
  }
72
73
 
@@ -95,27 +96,6 @@ Object.defineProperty(ValidationError.prototype, 'name', {
95
96
  value: 'ValidationError'
96
97
  });
97
98
 
98
- /*!
99
- * ignore
100
- */
101
-
102
- function _generateMessage(err) {
103
- const keys = Object.keys(err.errors || {});
104
- const len = keys.length;
105
- const msgs = [];
106
- let key;
107
-
108
- for (let i = 0; i < len; ++i) {
109
- key = keys[i];
110
- if (err === err.errors[key]) {
111
- continue;
112
- }
113
- msgs.push(key + ': ' + err.errors[key].message);
114
- }
115
-
116
- return msgs.join(', ');
117
- }
118
-
119
99
  /*!
120
100
  * Module exports
121
101
  */
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ /*!
4
+ * ignore
5
+ */
6
+
7
+ module.exports = function combinePathErrors(err) {
8
+ const keys = Object.keys(err.errors || {});
9
+ const len = keys.length;
10
+ const msgs = [];
11
+ let key;
12
+
13
+ for (let i = 0; i < len; ++i) {
14
+ key = keys[i];
15
+ if (err === err.errors[key]) {
16
+ continue;
17
+ }
18
+ msgs.push(key + ': ' + err.errors[key].message);
19
+ }
20
+
21
+ return msgs.join(', ');
22
+ };
@@ -19,11 +19,13 @@ const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
19
19
  * ignore
20
20
  */
21
21
 
22
- module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) {
22
+ module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins, mergeHooks) {
23
23
  if (!(schema && schema.instanceOfSchema)) {
24
24
  throw new Error('You must pass a valid discriminator Schema');
25
25
  }
26
26
 
27
+ mergeHooks = mergeHooks == null ? true : mergeHooks;
28
+
27
29
  if (model.schema.discriminatorMapping &&
28
30
  !model.schema.discriminatorMapping.isRoot) {
29
31
  throw new Error('Discriminator "' + name +
@@ -32,7 +34,7 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
32
34
 
33
35
  if (applyPlugins) {
34
36
  const applyPluginsToDiscriminators = get(model.base,
35
- 'options.applyPluginsToDiscriminators', false);
37
+ 'options.applyPluginsToDiscriminators', false) || !mergeHooks;
36
38
  // Even if `applyPluginsToDiscriminators` isn't set, we should still apply
37
39
  // global plugins to schemas embedded in the discriminator schema (gh-7370)
38
40
  model.base._applyPlugins(schema, {
@@ -179,7 +181,9 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
179
181
  schema.options._id = _id;
180
182
  }
181
183
  schema.options.id = id;
182
- schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
184
+ if (mergeHooks) {
185
+ schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
186
+ }
183
187
 
184
188
  schema.plugins = Array.prototype.slice.call(baseSchema.plugins);
185
189
  schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);
package/lib/index.js CHANGED
@@ -37,6 +37,7 @@ const trusted = require('./helpers/query/trusted').trusted;
37
37
  const sanitizeFilter = require('./helpers/query/sanitizeFilter');
38
38
  const isBsonType = require('./helpers/isBsonType');
39
39
  const MongooseError = require('./error/mongooseError');
40
+ const SetOptionError = require('./error/setOptionError');
40
41
 
41
42
  const defaultMongooseSymbol = Symbol.for('mongoose:default');
42
43
 
@@ -182,6 +183,9 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
182
183
  /**
183
184
  * Sets mongoose options
184
185
  *
186
+ * `key` can be used a object to set multiple options at once.
187
+ * If a error gets thrown for one option, other options will still be evaluated.
188
+ *
185
189
  * #### Example:
186
190
  *
187
191
  * mongoose.set('test', value) // sets the 'test' option to `value`
@@ -190,6 +194,8 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
190
194
  *
191
195
  * mongoose.set('debug', function(collectionName, methodName, ...methodArgs) {}); // use custom function to log collection methods + arguments
192
196
  *
197
+ * mongoose.set({ debug: true, autoIndex: false }); // set multiple options at once
198
+ *
193
199
  * Currently supported options are:
194
200
  * - 'applyPluginsToChildSchemas': `true` by default. Set to false to skip applying global plugins to child schemas
195
201
  * - 'applyPluginsToDiscriminators': `false` by default. Set to true to apply global plugins to discriminator schemas. This typically isn't necessary because plugins are applied to the base schema and discriminators copy all middleware, methods, statics, and properties from the base schema.
@@ -213,36 +219,66 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
213
219
  * - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()`
214
220
  * - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject)
215
221
  *
216
- * @param {String} key
217
- * @param {String|Function|Boolean} value
222
+ * @param {String|Object} key The name of the option or a object of multiple key-value pairs
223
+ * @param {String|Function|Boolean} value The value of the option, unused if "key" is a object
224
+ * @returns {Mongoose} The used Mongoose instnace
218
225
  * @api public
219
226
  */
220
227
 
221
228
  Mongoose.prototype.set = function(key, value) {
222
229
  const _mongoose = this instanceof Mongoose ? this : mongoose;
223
230
 
224
- if (VALID_OPTIONS.indexOf(key) === -1) {
225
- throw new Error(`\`${key}\` is an invalid option.`);
226
- }
231
+ if (arguments.length === 1 && typeof key !== 'object') {
232
+ if (VALID_OPTIONS.indexOf(key) === -1) {
233
+ const error = new SetOptionError();
234
+ error.addError(key, new SetOptionError.SetOptionInnerError(key));
235
+ throw error;
236
+ }
227
237
 
228
- if (arguments.length === 1) {
229
238
  return _mongoose.options[key];
230
239
  }
231
240
 
232
- _mongoose.options[key] = value;
241
+ let options = {};
233
242
 
234
- if (key === 'objectIdGetter') {
235
- if (value) {
236
- Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', {
237
- enumerable: false,
238
- configurable: true,
239
- get: function() {
240
- return this;
241
- }
242
- });
243
- } else {
244
- delete mongoose.Types.ObjectId.prototype._id;
243
+ if (arguments.length === 2) {
244
+ options = { [key]: value };
245
+ }
246
+
247
+ if (arguments.length === 1 && typeof key === 'object') {
248
+ options = key;
249
+ }
250
+
251
+ // array for errors to collect all errors for all key-value pairs, like ".validate"
252
+ let error = undefined;
253
+
254
+ for (const [optionKey, optionValue] of Object.entries(options)) {
255
+ if (VALID_OPTIONS.indexOf(optionKey) === -1) {
256
+ if (!error) {
257
+ error = new SetOptionError();
258
+ }
259
+ error.addError(optionKey, new SetOptionError.SetOptionInnerError(optionKey));
260
+ continue;
245
261
  }
262
+
263
+ _mongoose.options[optionKey] = optionValue;
264
+
265
+ if (optionKey === 'objectIdGetter') {
266
+ if (optionValue) {
267
+ Object.defineProperty(mongoose.Types.ObjectId.prototype, '_id', {
268
+ enumerable: false,
269
+ configurable: true,
270
+ get: function() {
271
+ return this;
272
+ }
273
+ });
274
+ } else {
275
+ delete mongoose.Types.ObjectId.prototype._id;
276
+ }
277
+ }
278
+ }
279
+
280
+ if (error) {
281
+ throw error;
246
282
  }
247
283
 
248
284
  return _mongoose;
package/lib/model.js CHANGED
@@ -511,6 +511,9 @@ Model.prototype.save = function(options, fn) {
511
511
  if (options.hasOwnProperty('session')) {
512
512
  this.$session(options.session);
513
513
  }
514
+ if (this.$__.timestamps != null) {
515
+ options.timestamps = this.$__.timestamps;
516
+ }
514
517
  this.$__.$versionError = generateVersionError(this, this.modifiedPaths());
515
518
 
516
519
  fn = this.constructor.$handleCallbackError(fn);
@@ -1211,6 +1214,7 @@ Model.exists = function exists(filter, options, callback) {
1211
1214
  * @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
1212
1215
  * @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
1213
1216
  * @param {Boolean} [options.overwriteModels=false] by default, Mongoose does not allow you to define a discriminator with the same name as another discriminator. Set this to allow overwriting discriminators with the same name.
1217
+ * @param {Boolean} [options.mergeHooks=true] By default, Mongoose merges the base schema's hooks with the discriminator schema's hooks. Set this option to `false` to make Mongoose use the discriminator schema's hooks instead.
1214
1218
  * @return {Model} The newly created discriminator model
1215
1219
  * @api public
1216
1220
  */
@@ -1238,7 +1242,7 @@ Model.discriminator = function(name, schema, options) {
1238
1242
  schema = schema.clone();
1239
1243
  }
1240
1244
 
1241
- schema = discriminator(this, name, schema, value, true);
1245
+ schema = discriminator(this, name, schema, value, true, options.mergeHooks);
1242
1246
  if (this.db.models[name] && !schema.options.overwriteModels) {
1243
1247
  throw new OverwriteModelError(name);
1244
1248
  }
@@ -3455,7 +3459,8 @@ Model.$__insertMany = function(arr, options, callback) {
3455
3459
  if (doc.$__schema.options.versionKey) {
3456
3460
  doc[doc.$__schema.options.versionKey] = 0;
3457
3461
  }
3458
- if ((!options || options.timestamps !== false) && doc.initializeTimestamps) {
3462
+ const shouldSetTimestamps = (!options || options.timestamps !== false) && doc.initializeTimestamps && (!doc.$__ || doc.$__.timestamps !== false);
3463
+ if (shouldSetTimestamps) {
3459
3464
  return doc.initializeTimestamps().toObject(internalToObjectOptions);
3460
3465
  }
3461
3466
  return doc.toObject(internalToObjectOptions);
@@ -3696,6 +3701,13 @@ Model.bulkSave = async function(documents, options) {
3696
3701
  document.$__.saveOptions = document.$__.saveOptions || {};
3697
3702
  document.$__.saveOptions.timestamps = options.timestamps;
3698
3703
  }
3704
+ } else {
3705
+ for (const document of documents) {
3706
+ if (document.$__.timestamps != null) {
3707
+ document.$__.saveOptions = document.$__.saveOptions || {};
3708
+ document.$__.saveOptions.timestamps = document.$__.timestamps;
3709
+ }
3710
+ }
3699
3711
  }
3700
3712
 
3701
3713
  await Promise.all(documents.map(buildPreSavePromise));
@@ -29,6 +29,8 @@ exports.Decimal128 = exports.Decimal = require('./decimal128');
29
29
 
30
30
  exports.Map = require('./map');
31
31
 
32
+ exports.UUID = require('./uuid');
33
+
32
34
  // alias
33
35
 
34
36
  exports.Oid = exports.ObjectId;