mongoose 5.2.6 → 5.2.7

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/.travis.yml CHANGED
@@ -16,6 +16,6 @@ before_script:
16
16
  - ./mongodb-linux-x86_64-3.6.4/bin/mongod --fork --nopreallocj --dbpath ./data/db/27017 --syslog --port 27017
17
17
  script:
18
18
  - npm test
19
- - npm run lint
19
+ - node tools/checkNodeVersion.js "4.x || 5.x" || npm run lint
20
20
  notifications:
21
21
  email: false
package/History.md CHANGED
@@ -1,3 +1,20 @@
1
+ 5.2.7 / 2018-08-06
2
+ ==================
3
+ * fix(model): check `expireAfterSeconds` option when diffing indexes in syncIndexes() #6820 #6819 [christopherhex](https://github.com/christopherhex)
4
+ * chore: fix some common test flakes in travis #6816 [Fonger](https://github.com/Fonger)
5
+ * chore: bump eslint and webpack to avoid bad versions of eslint-scope #6814
6
+ * test(model): add delay to session tests to improve pass rate #6811 [Fonger](https://github.com/Fonger)
7
+ * fix(model): support options in `deleteMany` #6810 [Fonger](https://github.com/Fonger)
8
+ * fix(query): don't use $each when pushing an array into an array #6809 [lineus](https://github.com/lineus)
9
+ * chore: bump mquery so eslint isn't a prod dependency #6800
10
+ * fix(populate): correctly get schema type when calling `populate()` on already populated path #6798
11
+ * fix(populate): propagate readConcern options in populate from parent query #6792 #6785 [Fonger](https://github.com/Fonger)
12
+ * docs(connection): add description of useNewUrlParser option #6789
13
+ * fix(query): make select('+path') a no-op if no select prop in schema #6785
14
+ * docs(schematype+validation): document using function syntax for custom validator message #6772
15
+ * fix(update): throw CastError if updating with `$inc: null` #6770
16
+ * fix(connection): throw helpful error when calling `createConnection(undefined)` #6763
17
+
1
18
  5.2.6 / 2018-07-30
2
19
  ==================
3
20
  * fix(document): don't double-call deeply nested custom getters when using `get()` #6779 #6637
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const CastError = require('../error/cast');
4
+
5
+ /*!
6
+ * Given a value, cast it to a number, or throw a `CastError` if the value
7
+ * cannot be casted. `null` and `undefined` are considered valid.
8
+ *
9
+ * @param {Any} value
10
+ * @param {String} [path] optional the path to set on the CastError
11
+ * @return {Boolean|null|undefined}
12
+ * @throws {CastError} if `value` is not one of the allowed values
13
+ * @api private
14
+ */
15
+
16
+ module.exports = function castNumber(val, path) {
17
+ if (isNaN(val)) {
18
+ throw new CastError('number', val, path);
19
+ }
20
+
21
+ if (val == null) {
22
+ return val;
23
+ }
24
+ if (val === '') {
25
+ return null;
26
+ }
27
+
28
+ if (typeof val === 'string' || typeof val === 'boolean') {
29
+ val = Number(val);
30
+ }
31
+
32
+ if (isNaN(val)) {
33
+ throw new CastError('number', val, path);
34
+ }
35
+ if (val instanceof Number) {
36
+ return val;
37
+ }
38
+ if (typeof val === 'number') {
39
+ return val;
40
+ }
41
+ if (!Array.isArray(val) && typeof val.valueOf === 'function') {
42
+ return Number(val.valueOf());
43
+ }
44
+ if (val.toString && !Array.isArray(val) && val.toString() == Number(val)) {
45
+ return new Number(val);
46
+ }
47
+
48
+ throw new CastError('number', val, path);
49
+ };
package/lib/connection.js CHANGED
@@ -420,6 +420,12 @@ Connection.prototype.openUri = function(uri, options, callback) {
420
420
  'http://mongoosejs.com/docs/connections.html for supported connection syntax');
421
421
  }
422
422
 
423
+ if (typeof uri !== 'string') {
424
+ throw new MongooseError('The `uri` parameter to `openUri()` must be a ' +
425
+ `string, got "${typeof uri}". Make sure the first parameter to ` +
426
+ '`mongoose.connect()` or `mongoose.createConnection()` is a string.');
427
+ }
428
+
423
429
  const Promise = PromiseProvider.get();
424
430
  const _this = this;
425
431
 
@@ -4,8 +4,9 @@
4
4
  * ignore
5
5
  */
6
6
 
7
- var Mixed = require('../../schema/mixed');
8
- var mpath = require('mpath');
7
+ const Mixed = require('../../schema/mixed');
8
+ const get = require('lodash.get');
9
+ const mpath = require('mpath');
9
10
 
10
11
  /*!
11
12
  * @param {Schema} schema
@@ -28,79 +29,96 @@ module.exports = function getSchemaTypes(schema, doc, path) {
28
29
  while (p--) {
29
30
  trypath = parts.slice(0, p).join('.');
30
31
  foundschema = schema.path(trypath);
31
- if (foundschema) {
32
- if (foundschema.caster) {
33
- // array of Mixed?
34
- if (foundschema.caster instanceof Mixed) {
35
- return foundschema.caster;
36
- }
37
32
 
38
- let schemas = null;
39
- if (doc != null && foundschema.schema != null && foundschema.schema.discriminators != null) {
40
- const discriminators = foundschema.schema.discriminators;
41
- const discriminatorKeyPath = trypath + '.' +
42
- foundschema.schema.options.discriminatorKey;
43
- const keys = mpath.get(discriminatorKeyPath, subdoc) || [];
44
- schemas = Object.keys(discriminators).
45
- reduce(function(cur, discriminator) {
46
- if (keys.indexOf(discriminator) !== -1) {
47
- cur.push(discriminators[discriminator]);
48
- }
49
- return cur;
50
- }, []);
51
- }
33
+ if (foundschema == null) {
34
+ continue;
35
+ }
52
36
 
53
- // Now that we found the array, we need to check if there
54
- // are remaining document paths to look up for casting.
55
- // Also we need to handle array.$.path since schema.path
56
- // doesn't work for that.
57
- // If there is no foundschema.schema we are dealing with
58
- // a path like array.$
59
- if (p !== parts.length && foundschema.schema) {
60
- let ret;
61
- if (parts[p] === '$') {
62
- if (p + 1 === parts.length) {
63
- // comments.$
64
- return foundschema;
65
- }
66
- // comments.$.comments.$.title
67
- ret = search(parts.slice(p + 1), schema, mpath.get(trypath, subdoc));
68
- if (ret) {
69
- ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
70
- !foundschema.schema.$isSingleNested;
71
- }
72
- return ret;
73
- }
37
+ if (foundschema.caster) {
38
+ // array of Mixed?
39
+ if (foundschema.caster instanceof Mixed) {
40
+ return foundschema.caster;
41
+ }
74
42
 
75
- if (schemas != null && schemas.length > 0) {
76
- ret = [];
77
- for (var i = 0; i < schemas.length; ++i) {
78
- let _ret = search(parts.slice(p), schemas[i], mpath.get(trypath, subdoc));
79
- if (_ret != null) {
80
- _ret.$isUnderneathDocArray = _ret.$isUnderneathDocArray ||
81
- !foundschema.schema.$isSingleNested;
82
- if (_ret.$isUnderneathDocArray) {
83
- ret.$isUnderneathDocArray = true;
84
- }
85
- ret.push(_ret);
86
- }
43
+ let schemas = null;
44
+ if (doc != null && foundschema.schema != null && foundschema.schema.discriminators != null) {
45
+ const discriminators = foundschema.schema.discriminators;
46
+ const discriminatorKeyPath = trypath + '.' +
47
+ foundschema.schema.options.discriminatorKey;
48
+ const keys = mpath.get(discriminatorKeyPath, subdoc) || [];
49
+ schemas = Object.keys(discriminators).
50
+ reduce(function(cur, discriminator) {
51
+ if (keys.indexOf(discriminator) !== -1) {
52
+ cur.push(discriminators[discriminator]);
87
53
  }
88
- return ret;
89
- } else {
90
- ret = search(parts.slice(p), foundschema.schema, mpath.get(trypath, subdoc));
54
+ return cur;
55
+ }, []);
56
+ }
57
+
58
+ // Now that we found the array, we need to check if there
59
+ // are remaining document paths to look up for casting.
60
+ // Also we need to handle array.$.path since schema.path
61
+ // doesn't work for that.
62
+ // If there is no foundschema.schema we are dealing with
63
+ // a path like array.$
64
+ if (p !== parts.length && foundschema.schema) {
65
+ let ret;
66
+ if (parts[p] === '$') {
67
+ if (p + 1 === parts.length) {
68
+ // comments.$
69
+ return foundschema;
70
+ }
71
+ // comments.$.comments.$.title
72
+ ret = search(parts.slice(p + 1), schema, mpath.get(trypath, subdoc));
73
+ if (ret) {
74
+ ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
75
+ !foundschema.schema.$isSingleNested;
76
+ }
77
+ return ret;
78
+ }
91
79
 
92
- if (ret) {
93
- ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
80
+ if (schemas != null && schemas.length > 0) {
81
+ ret = [];
82
+ for (var i = 0; i < schemas.length; ++i) {
83
+ let _ret = search(parts.slice(p), schemas[i], mpath.get(trypath, subdoc));
84
+ if (_ret != null) {
85
+ _ret.$isUnderneathDocArray = _ret.$isUnderneathDocArray ||
94
86
  !foundschema.schema.$isSingleNested;
87
+ if (_ret.$isUnderneathDocArray) {
88
+ ret.$isUnderneathDocArray = true;
89
+ }
90
+ ret.push(_ret);
95
91
  }
92
+ }
93
+ return ret;
94
+ } else {
95
+ ret = search(parts.slice(p), foundschema.schema, mpath.get(trypath, subdoc));
96
96
 
97
- return ret;
97
+ if (ret) {
98
+ ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
99
+ !foundschema.schema.$isSingleNested;
98
100
  }
101
+
102
+ return ret;
99
103
  }
100
104
  }
105
+ }
106
+
107
+ if (doc.$__ && doc.populated(trypath)) {
108
+ const schema = get(doc.$__.populated[trypath], 'options.model.schema');
109
+ if (schema != null) {
110
+ const ret = search(parts.slice(p), schema, mpath.get(trypath, subdoc));
111
+
112
+ if (ret) {
113
+ ret.$isUnderneathDocArray = ret.$isUnderneathDocArray ||
114
+ !schema.$isSingleNested;
115
+ }
101
116
 
102
- return foundschema;
117
+ return ret;
118
+ }
103
119
  }
120
+
121
+ return foundschema;
104
122
  }
105
123
  }
106
124
 
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ const CastError = require('../../error/cast');
3
4
  const StrictModeError = require('../../error/strict');
4
5
  const ValidationError = require('../../error/validation');
6
+ const castNumber = require('../../cast/number');
5
7
  const getEmbeddedDiscriminatorPath = require('./getEmbeddedDiscriminatorPath');
6
8
  const utils = require('../../utils');
7
9
 
@@ -136,7 +138,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
136
138
  hasKeys = true;
137
139
  try {
138
140
  obj[key] = {
139
- $each: castUpdateVal(schematype, val.$each, op, context)
141
+ $each: castUpdateVal(schematype, val.$each, op, context, prefix + key)
140
142
  };
141
143
  } catch (error) {
142
144
  aggregatedError = _handleCastError(error, context, key, aggregatedError);
@@ -155,7 +157,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
155
157
  }
156
158
  } else {
157
159
  try {
158
- obj[key] = castUpdateVal(schematype, val, op, context);
160
+ obj[key] = castUpdateVal(schematype, val, op, context, prefix + key);
159
161
  } catch (error) {
160
162
  aggregatedError = _handleCastError(error, context, key, aggregatedError);
161
163
  }
@@ -170,7 +172,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
170
172
  } else if ((op === '$currentDate') || (op in castOps && schematype)) {
171
173
  // $currentDate can take an object
172
174
  try {
173
- obj[key] = castUpdateVal(schematype, val, op, context);
175
+ obj[key] = castUpdateVal(schematype, val, op, context, prefix + key);
174
176
  } catch (error) {
175
177
  aggregatedError = _handleCastError(error, context, key, aggregatedError);
176
178
  }
@@ -249,13 +251,15 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
249
251
  }
250
252
 
251
253
  try {
252
- obj[key] = castUpdateVal(schematype, val, op, key, context);
254
+ obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key);
253
255
  } catch (error) {
254
256
  aggregatedError = _handleCastError(error, context, key, aggregatedError);
255
257
  }
256
258
 
257
259
  if (Array.isArray(obj[key]) && (op === '$addToSet' || op === '$push') && key !== '$each') {
258
- obj[key] = { $each: obj[key] };
260
+ if (schematype.caster && !schematype.caster.$isMongooseArray) {
261
+ obj[key] = { $each: obj[key] };
262
+ }
259
263
  }
260
264
 
261
265
  if (options.omitUndefined && obj[key] === void 0) {
@@ -295,10 +299,17 @@ function _handleCastError(error, query, key, aggregatedError) {
295
299
 
296
300
  var numberOps = {
297
301
  $pop: 1,
298
- $unset: 1,
299
302
  $inc: 1
300
303
  };
301
304
 
305
+ /*!
306
+ * These ops require no casting because the RHS doesn't do anything.
307
+ */
308
+
309
+ const noCastOps = {
310
+ $unset: 1
311
+ };
312
+
302
313
  /*!
303
314
  * These operators require casting docs
304
315
  * to real Documents for Update operations.
@@ -331,12 +342,12 @@ var overwriteOps = {
331
342
  * @api private
332
343
  */
333
344
 
334
- function castUpdateVal(schema, val, op, $conditional, context) {
345
+ function castUpdateVal(schema, val, op, $conditional, context, path) {
335
346
  if (!schema) {
336
347
  // non-existing schema path
337
- return op in numberOps
338
- ? Number(val)
339
- : val;
348
+ return op in numberOps ?
349
+ castNumber(val, path) :
350
+ val;
340
351
  }
341
352
 
342
353
  var cond = schema.caster && op in castOps &&
@@ -357,11 +368,22 @@ function castUpdateVal(schema, val, op, $conditional, context) {
357
368
  return schema.cast(val);
358
369
  }
359
370
 
371
+ if (op in noCastOps) {
372
+ return val;
373
+ }
360
374
  if (op in numberOps) {
375
+ // Null and undefined not allowed for $pop, $inc
376
+ if (val == null) {
377
+ throw new CastError('number', val, schema.path);
378
+ }
361
379
  if (op === '$inc') {
362
- return schema.castForQueryWrapper({ val: val, context: context });
380
+ // Support `$inc` with long, int32, etc. (gh-4283)
381
+ return schema.castForQueryWrapper({
382
+ val: val,
383
+ context: context
384
+ });
363
385
  }
364
- return Number(val);
386
+ return castNumber(val, schema.path);
365
387
  }
366
388
  if (op === '$currentDate') {
367
389
  if (typeof val === 'object') {
package/lib/model.js CHANGED
@@ -1044,7 +1044,7 @@ Model.syncIndexes = function syncIndexes(options, callback) {
1044
1044
  utils.clone(schemaIndex[1]));
1045
1045
 
1046
1046
  // If these options are different, need to rebuild the index
1047
- const optionKeys = ['unique', 'partialFilterExpression', 'sparse'];
1047
+ const optionKeys = ['unique', 'partialFilterExpression', 'sparse', 'expireAfterSeconds'];
1048
1048
  const indexCopy = Object.assign({}, index);
1049
1049
  for (const key of optionKeys) {
1050
1050
  if (!(key in options) && !(key in indexCopy)) {
@@ -1480,19 +1480,26 @@ Model.deleteOne = function deleteOne(conditions, callback) {
1480
1480
  * Like `Model.remove()`, this function does **not** trigger `pre('remove')` or `post('remove')` hooks.
1481
1481
  *
1482
1482
  * @param {Object} conditions
1483
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
1483
1484
  * @param {Function} [callback]
1484
1485
  * @return {Query}
1485
1486
  * @api public
1486
1487
  */
1487
1488
 
1488
- Model.deleteMany = function deleteMany(conditions, callback) {
1489
+ Model.deleteMany = function deleteMany(conditions, options, callback) {
1489
1490
  if (typeof conditions === 'function') {
1490
1491
  callback = conditions;
1491
1492
  conditions = {};
1493
+ options = null;
1494
+ }
1495
+ else if (typeof options === 'function') {
1496
+ callback = options;
1497
+ options = null;
1492
1498
  }
1493
1499
 
1494
1500
  // get the mongodb collection object
1495
1501
  var mq = new this.Query(conditions, {}, this, this.collection);
1502
+ mq.setOptions(options);
1496
1503
 
1497
1504
  if (callback) {
1498
1505
  callback = this.$wrapCallback(callback);
@@ -2503,10 +2510,17 @@ Model.create = function create(doc, options, callback) {
2503
2510
  * This function does **not** trigger any middleware. In particular, it
2504
2511
  * does **not** trigger aggregate middleware.
2505
2512
  *
2513
+ * The ChangeStream object is an event emitter that emits the following events:
2514
+ *
2515
+ * - 'change': A change occurred, see below example
2516
+ * - 'error': An unrecoverable error occurred. In particular, change streams currently error out if they lose connection to the replica set primary. Follow [this GitHub issue](https://github.com/Automattic/mongoose/issues/6799) for updates.
2517
+ * - 'end': Emitted if the underlying stream is closed
2518
+ * - 'close': Emitted if the underlying stream is closed
2519
+ *
2506
2520
  * ####Example:
2507
2521
  *
2508
2522
  * const doc = await Person.create({ name: 'Ned Stark' });
2509
- * Person.watch().on('change', change => console.log(change));
2523
+ * const changeStream = Person.watch().on('change', change => console.log(change));
2510
2524
  * // Will print from the above `console.log()`:
2511
2525
  * // { _id: { _data: ... },
2512
2526
  * // operationType: 'delete',
@@ -3872,10 +3886,10 @@ function getModelsMapForPopulate(model, docs, options) {
3872
3886
  let discriminatorKey;
3873
3887
  let modelForFindSchema;
3874
3888
 
3875
- var originalModel = options.model;
3876
- var isVirtual = false;
3877
- var isRefPathArray = false;
3878
- var modelSchema = model.schema;
3889
+ let originalModel = options.model;
3890
+ let isVirtual = false;
3891
+ let isRefPathArray = false;
3892
+ const modelSchema = model.schema;
3879
3893
 
3880
3894
  for (i = 0; i < len; i++) {
3881
3895
  doc = docs[i];
package/lib/query.js CHANGED
@@ -844,13 +844,20 @@ Query.prototype.mod = function() {
844
844
  * // exclude c and d, include other fields
845
845
  * query.select('-c -d');
846
846
  *
847
+ * // Use `+` to override schema-level `select: false` without making the
848
+ * // projection inclusive.
849
+ * const schema = new Schema({
850
+ * foo: { type: String, select: false },
851
+ * bar: String
852
+ * });
853
+ * // ...
854
+ * query.select('+foo'); // Override foo's `select: false` without excluding `bar`
855
+ *
847
856
  * // or you may use object notation, useful when
848
857
  * // you have keys already prefixed with a "-"
849
858
  * query.select({ a: 1, b: 1 });
850
859
  * query.select({ c: 0, d: 0 });
851
860
  *
852
- * // force inclusion of field excluded at schema level
853
- * query.select('+path')
854
861
  *
855
862
  * @method select
856
863
  * @memberOf Query
@@ -3736,11 +3743,17 @@ Query.prototype.populate = function() {
3736
3743
 
3737
3744
  const res = utils.populate.apply(null, arguments);
3738
3745
 
3739
- // Propagate readPreference and lean from parent query, unless one already
3740
- // specified
3746
+ // Propagate readConcern and readPreference and lean from parent query,
3747
+ // unless one already specified
3741
3748
  if (this.options != null) {
3749
+ const readConcern = this.options.readConcern;
3742
3750
  const readPref = this.options.readPreference;
3751
+
3743
3752
  for (let i = 0; i < res.length; ++i) {
3753
+ if (readConcern != null && get(res[i], 'options.readConcern') == null) {
3754
+ res[i].options = res[i].options || {};
3755
+ res[i].options.readConcern = readConcern;
3756
+ }
3744
3757
  if (readPref != null && get(res[i], 'options.readPreference') == null) {
3745
3758
  res[i].options = res[i].options || {};
3746
3759
  res[i].options.readPreference = readPref;
@@ -188,10 +188,16 @@ exports.applyPaths = function applyPaths(fields, schema) {
188
188
  let stack = [];
189
189
 
190
190
  let analyzePath = function(path, type) {
191
+ var plusPath = '+' + path;
192
+ var hasPlusPath = fields && plusPath in fields;
193
+ if (hasPlusPath) {
194
+ // forced inclusion
195
+ delete fields[plusPath];
196
+ }
197
+
191
198
  if (typeof type.selected !== 'boolean') return;
192
199
 
193
- var plusPath = '+' + path;
194
- if (fields && plusPath in fields) {
200
+ if (hasPlusPath) {
195
201
  // forced inclusion
196
202
  delete fields[plusPath];
197
203
 
@@ -1,13 +1,17 @@
1
+ 'use strict';
2
+
1
3
  /*!
2
4
  * Module requirements.
3
5
  */
4
6
 
5
- var SchemaType = require('../schematype');
6
- var CastError = SchemaType.CastError;
7
- var handleBitwiseOperator = require('./operators/bitwise');
8
- var MongooseError = require('../error');
9
- var utils = require('../utils');
10
- var Document;
7
+ const MongooseError = require('../error');
8
+ const SchemaType = require('../schematype');
9
+ const castNumber = require('../cast/number');
10
+ const handleBitwiseOperator = require('./operators/bitwise');
11
+ const utils = require('../utils');
12
+
13
+ const CastError = SchemaType.CastError;
14
+ let Document;
11
15
 
12
16
  /**
13
17
  * Number SchemaType constructor.
@@ -207,31 +211,7 @@ SchemaNumber.prototype.cast = function(value, doc, init) {
207
211
  value._id : // documents
208
212
  value;
209
213
 
210
- if (!isNaN(val)) {
211
- if (val === null) {
212
- return val;
213
- }
214
- if (val === '') {
215
- return null;
216
- }
217
- if (typeof val === 'string' || typeof val === 'boolean') {
218
- val = Number(val);
219
- }
220
- if (val instanceof Number) {
221
- return val;
222
- }
223
- if (typeof val === 'number') {
224
- return val;
225
- }
226
- if (!Array.isArray(val) && typeof val.valueOf === 'function') {
227
- return Number(val.valueOf());
228
- }
229
- if (val.toString && !Array.isArray(val) && val.toString() == Number(val)) {
230
- return new Number(val);
231
- }
232
- }
233
-
234
- throw new CastError('number', value, this.path);
214
+ return castNumber(val, this.path);
235
215
  };
236
216
 
237
217
  /*!
package/lib/schematype.js CHANGED
@@ -414,7 +414,9 @@ SchemaType.prototype.get = function(fn) {
414
414
  /**
415
415
  * Adds validator(s) for this document path.
416
416
  *
417
- * Validators always receive the value to validate as their first argument and must return `Boolean`. Returning `false` means validation failed.
417
+ * Validators always receive the value to validate as their first argument and
418
+ * must return `Boolean`. Returning `false` or throwing an error means
419
+ * validation failed.
418
420
  *
419
421
  * The error message argument is optional. If not passed, the [default generic error message template](#error_messages_MongooseError-messages) will be used.
420
422
  *
@@ -446,24 +448,35 @@ SchemaType.prototype.get = function(fn) {
446
448
  *
447
449
  * ####Error message templates:
448
450
  *
449
- * From the examples above, you may have noticed that error messages support basic templating. There are a few other template keywords besides `{PATH}` and `{VALUE}` too. To find out more, details are available [here](#error_messages_MongooseError.messages)
451
+ * From the examples above, you may have noticed that error messages support
452
+ * basic templating. There are a few other template keywords besides `{PATH}`
453
+ * and `{VALUE}` too. To find out more, details are available
454
+ * [here](#error_messages_MongooseError.messages).
450
455
  *
451
- * ####Asynchronous validation:
456
+ * If Mongoose's built-in error message templating isn't enough, Mongoose
457
+ * supports setting the `message` property to a function.
452
458
  *
453
- * Passing a validator function that receives two arguments tells mongoose that the validator is an asynchronous validator. The first argument passed to the validator function is the value being validated. The second argument is a callback function that must called when you finish validating the value and passed either `true` or `false` to communicate either success or failure respectively.
459
+ * schema.path('name').validate({
460
+ * validator: v => v.length > 5,
461
+ * // `errors['name']` will be "name must have length > 5, got 'foo'"
462
+ * message: props => `${props.path} must have length > 5, got '${props.value}'`
463
+ * });
464
+ *
465
+ * To bypass Mongoose's error messages and just copy the error message that
466
+ * the validator throws, do this:
454
467
  *
455
468
  * schema.path('name').validate({
456
- * isAsync: true,
457
- * validator: function (value, respond) {
458
- * doStuff(value, function () {
459
- * ...
460
- * respond(false); // validation failed
461
- * });
462
- * },
463
- * message: 'Custom error message!' // Optional
469
+ * validator: () => { throw new Error('Oops!'); },
470
+ * // `errors['name']` will be "Oops!"
471
+ * message: props => props.reason.message
464
472
  * });
465
473
  *
466
- * // Can also return a promise
474
+ * ####Asynchronous validation:
475
+ *
476
+ * Mongoose supports validators that return a promise. A validator that returns
477
+ * a promise is called an _async validator_. Async validators run in
478
+ * parallel, and `validate()` will wait until all async validators have settled.
479
+ *
467
480
  * schema.path('name').validate({
468
481
  * validator: function (value) {
469
482
  * return new Promise(function (resolve, reject) {
@@ -485,13 +498,14 @@ SchemaType.prototype.get = function(fn) {
485
498
  * var dvd = new Product(..);
486
499
  * dvd.save(); // emits error on the `conn` above
487
500
  *
488
- * If you desire handling these errors at the Model level, attach an `error` listener to your Model and the event will instead be emitted there.
501
+ * If you want to handle these errors at the Model level, add an `error`
502
+ * listener to your Model as shown below.
489
503
  *
490
504
  * // registering an error listener on the Model lets us handle errors more locally
491
505
  * Product.on('error', handleError);
492
506
  *
493
507
  * @param {RegExp|Function|Object} obj validator
494
- * @param {String} [errorMsg] optional error message
508
+ * @param {String|Function} [errorMsg] optional error message. If function, should return the error message as a string
495
509
  * @param {String} [type] optional validator type
496
510
  * @return {SchemaType} this
497
511
  * @api public
@@ -836,7 +850,7 @@ SchemaType.prototype.doValidate = function(value, fn, scope) {
836
850
  asyncValidate(validator, scope, value, validatorProperties, validate);
837
851
  } else {
838
852
  try {
839
- ok = validator.call(scope, value);
853
+ ok = validator.call(scope, value, validatorProperties);
840
854
  } catch (error) {
841
855
  ok = false;
842
856
  validatorProperties.reason = error;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "5.2.6",
4
+ "version": "5.2.7",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -27,7 +27,7 @@
27
27
  "mongodb-core": "3.1.0",
28
28
  "mongoose-legacy-pluralize": "1.0.2",
29
29
  "mpath": "0.4.1",
30
- "mquery": "3.1.1",
30
+ "mquery": "3.1.2",
31
31
  "ms": "2.0.0",
32
32
  "regexp-clone": "0.0.1",
33
33
  "sliced": "1.0.1"
@@ -41,7 +41,7 @@
41
41
  "bluebird": "3.5.0",
42
42
  "co": "4.6.0",
43
43
  "dox": "0.3.1",
44
- "eslint": "4.14.0",
44
+ "eslint": "5.3.0",
45
45
  "highlight.js": "9.1.0",
46
46
  "jade": "1.11.0",
47
47
  "lodash": "4.17.5",
@@ -58,14 +58,13 @@
58
58
  "tbd": "0.6.4",
59
59
  "uuid": "2.0.3",
60
60
  "uuid-parse": "1.0.0",
61
- "webpack": "4.12.0",
61
+ "webpack": "4.16.4",
62
62
  "validator": "5.4.0"
63
63
  },
64
64
  "directories": {
65
65
  "lib": "./lib/mongoose"
66
66
  },
67
67
  "scripts": {
68
- "fix-lint": "eslint . --fix",
69
68
  "lint": "eslint . --quiet",
70
69
  "release": "git pull && git push origin master --tags && npm publish",
71
70
  "release-legacy": "git pull origin 4.x && git push origin 4.x --tags && npm publish --tag legacy",
@@ -0,0 +1,7 @@
1
+ const version = process.version;
2
+ const semver = require('semver');
3
+
4
+ // Gnarly but this helps us avoid running eslint on node v4.
5
+ if (!semver.satisfies(version, process.argv[2])) {
6
+ throw new Error(`${version} does not satisfy "${process.argv[2]}"`);
7
+ }