mongoose 5.7.4 → 5.7.8

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/History.md CHANGED
@@ -1,3 +1,43 @@
1
+ 5.7.8 / 2019-11-04
2
+ ==================
3
+ * fix(document): allow manually populating path within document array #8273
4
+ * fix(populate): update top-level `populated()` when updating document array with populated subpaths #8265
5
+ * fix(cursor): throw error when using aggregation cursor as async iterator #8280
6
+ * fix(schema): retain `_id: false` in schema after nesting in another schema #8274
7
+ * fix(document): make Document class an event emitter to support defining documents without models in node #8272
8
+ * docs: document return types for `.discriminator()` #8287
9
+ * docs(connection): add note about exporting schemas, not models, in multi connection paradigm #8275
10
+ * docs: clarify that transforms defined in `toObject()` options are applied to subdocs #8260
11
+
12
+ 5.7.7 / 2019-10-24
13
+ ==================
14
+ * fix(populate): make populate virtual consistently an empty array if local field is only empty arrays #8230
15
+ * fix(query): allow findOne(objectid) and find(objectid) #8268
16
+
17
+ 5.7.6 / 2019-10-21
18
+ ==================
19
+ * fix: upgrade mongodb driver -> 3.3.3 to fix issue with failing to connect to a replica set if one member is down #8209
20
+ * fix(document): fix TypeError when setting a single nested subdoc with timestamps #8251
21
+ * fix(cursor): fix issue with long-running `eachAsync()` cursor #8249 #8235
22
+ * fix(connection): ensure repeated `close` events from useUnifiedTopology don't disconnect Mongoose from replica set #8224
23
+ * fix(document): support calling `Document` constructor directly in Node.js #8237
24
+ * fix(populate): add document array subpaths to parent doc `populated()` when calling `DocumentArray#push()` #8247
25
+ * fix(options): add missing minlength and maxlength to SchemaStringOptions #8256
26
+ * docs: add documentarraypath to API docs, including DocumentArrayPath#discriminator() #8164
27
+ * docs(schematypes): add a section about the `type` property #8227
28
+ * docs(api): fix Connection.close return param #8258 [gosuhiman](https://github.com/gosuhiman)
29
+ * docs: update link to broken image on home page #8253 [krosenk729](https://github.com/krosenk729)
30
+
31
+ 5.7.5 / 2019-10-14
32
+ ==================
33
+ * fix(query): delete top-level `_bsontype` property in queries to prevent silent empty queries #8222
34
+ * fix(update): handle subdocument pre('validate') errors in update validation #7187
35
+ * fix(subdocument): make subdocument#isModified use parent document's isModified #8223
36
+ * docs(index): add favicon to home page #8226
37
+ * docs: add schema options to API docs #8012
38
+ * docs(middleware): add note about accessing the document being updated in pre('findOneAndUpdate') #8218
39
+ * refactor: remove redundant code in ValidationError #8244 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez)
40
+
1
41
  5.7.4 / 2019-10-09
2
42
  ==================
3
43
  * fix(schema): handle `required: null` and `required: undefined` as `required: false` #8219
package/lib/aggregate.js CHANGED
@@ -1011,20 +1011,20 @@ Aggregate.prototype.catch = function(reject) {
1011
1011
 
1012
1012
  /**
1013
1013
  * Returns an asyncIterator for use with [`for/await/of` loops](http://bit.ly/async-iterators)
1014
- * This function *only* works for `find()` queries.
1015
1014
  * You do not need to call this function explicitly, the JavaScript runtime
1016
1015
  * will call it for you.
1017
1016
  *
1018
1017
  * ####Example
1019
1018
  *
1020
- * for await (const doc of Model.find().sort({ name: 1 })) {
1019
+ * const agg = Model.aggregate([{ $match: { age: { $gte: 25 } } }]);
1020
+ * for await (const doc of agg) {
1021
1021
  * console.log(doc.name);
1022
1022
  * }
1023
1023
  *
1024
1024
  * Node.js 10.x supports async iterators natively without any flags. You can
1025
1025
  * enable async iterators in Node.js 8.x using the [`--harmony_async_iteration` flag](https://github.com/tc39/proposal-async-iteration/issues/117#issuecomment-346695187).
1026
1026
  *
1027
- * **Note:** This function is not if `Symbol.asyncIterator` is undefined. If
1027
+ * **Note:** This function is not set if `Symbol.asyncIterator` is undefined. If
1028
1028
  * `Symbol.asyncIterator` is undefined, that means your Node.js version does not
1029
1029
  * support async iterators.
1030
1030
  *
package/lib/cast.js CHANGED
@@ -27,6 +27,14 @@ module.exports = function cast(schema, obj, options, context) {
27
27
  throw new Error('Query filter must be an object, got an array ', util.inspect(obj));
28
28
  }
29
29
 
30
+ // bson 1.x has the unfortunate tendency to remove filters that have a top-level
31
+ // `_bsontype` property. But we should still allow ObjectIds because
32
+ // `Collection#find()` has a special case to support `find(objectid)`.
33
+ // Should remove this when we upgrade to bson 4.x. See gh-8222, gh-8268
34
+ if (obj.hasOwnProperty('_bsontype') && obj._bsontype !== 'ObjectID') {
35
+ delete obj._bsontype;
36
+ }
37
+
30
38
  const paths = Object.keys(obj);
31
39
  let i = paths.length;
32
40
  let _keys;
package/lib/connection.js CHANGED
@@ -639,18 +639,28 @@ Connection.prototype.openUri = function(uri, options, callback) {
639
639
  _this.db = db;
640
640
 
641
641
  // `useUnifiedTopology` events
642
- if (options.useUnifiedTopology &&
643
- get(db, 's.topology.s.description.type') === 'Single') {
644
- const server = Array.from(db.s.topology.s.servers.values())[0];
645
- server.s.pool.on('reconnect', () => {
646
- _handleReconnect();
647
- });
648
- server.s.pool.on('reconnectFailed', () => {
649
- _this.emit('reconnectFailed');
650
- });
651
- server.s.pool.on('timeout', () => {
652
- _this.emit('timeout');
653
- });
642
+ const type = get(db, 's.topology.s.description.type', '');
643
+ if (options.useUnifiedTopology) {
644
+ if (type === 'Single') {
645
+ const server = Array.from(db.s.topology.s.servers.values())[0];
646
+ server.s.pool.on('reconnect', () => {
647
+ _handleReconnect();
648
+ });
649
+ server.s.pool.on('reconnectFailed', () => {
650
+ _this.emit('reconnectFailed');
651
+ });
652
+ server.s.pool.on('timeout', () => {
653
+ _this.emit('timeout');
654
+ });
655
+ } else if (type.startsWith('ReplicaSet')) {
656
+ db.on('close', function() {
657
+ const type = get(db, 's.topology.s.description.type', '');
658
+ if (type !== 'ReplicaSetWithPrimary') {
659
+ // Implicitly emits 'disconnected'
660
+ _this.readyState = STATES.disconnected;
661
+ }
662
+ });
663
+ }
654
664
  }
655
665
 
656
666
  // Backwards compat for mongoose 4.x
@@ -674,10 +684,12 @@ Connection.prototype.openUri = function(uri, options, callback) {
674
684
  _this.emit('attemptReconnect');
675
685
  });
676
686
  }
677
- db.on('close', function() {
678
- // Implicitly emits 'disconnected'
679
- _this.readyState = STATES.disconnected;
680
- });
687
+ if (!options.useUnifiedTopology || !type.startsWith('ReplicaSet')) {
688
+ db.on('close', function() {
689
+ // Implicitly emits 'disconnected'
690
+ _this.readyState = STATES.disconnected;
691
+ });
692
+ }
681
693
  client.on('left', function() {
682
694
  if (_this.readyState === STATES.connected &&
683
695
  get(db, 's.topology.s.coreTopology.s.replicaSetState.topologyType') === 'ReplicaSetNoPrimary') {
@@ -745,7 +757,7 @@ const handleUseMongoClient = function handleUseMongoClient(options) {
745
757
  *
746
758
  * @param {Boolean} [force] optional
747
759
  * @param {Function} [callback] optional
748
- * @return {Connection} self
760
+ * @return {Promise}
749
761
  * @api public
750
762
  */
751
763
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  'use strict';
6
6
 
7
+ const MongooseError = require('../error/mongooseError');
7
8
  const Readable = require('stream').Readable;
8
9
  const eachAsync = require('../helpers/cursor/eachAsync');
9
10
  const util = require('util');
@@ -94,6 +95,15 @@ AggregationCursor.prototype._read = function() {
94
95
  });
95
96
  };
96
97
 
98
+ if (Symbol.asyncIterator != null) {
99
+ const msg = 'Mongoose does not support using async iterators with an ' +
100
+ 'existing aggregation cursor. See http://bit.ly/mongoose-async-iterate-aggregation';
101
+
102
+ AggregationCursor.prototype[Symbol.asyncIterator] = function() {
103
+ throw new MongooseError(msg);
104
+ };
105
+ }
106
+
97
107
  /**
98
108
  * Registers a transform function which subsequently maps documents retrieved
99
109
  * via the streams interface or `.next()`
package/lib/document.js CHANGED
@@ -10,6 +10,7 @@ const MongooseError = require('./error/index');
10
10
  const MixedSchema = require('./schema/mixed');
11
11
  const ObjectExpectedError = require('./error/objectExpected');
12
12
  const ObjectParameterError = require('./error/objectParameter');
13
+ const Schema = require('./schema');
13
14
  const StrictModeError = require('./error/strict');
14
15
  const ValidatorError = require('./schematype').ValidatorError;
15
16
  const VirtualType = require('./virtualtype');
@@ -64,6 +65,17 @@ function Document(obj, fields, skipId, options) {
64
65
  }
65
66
  options = options || {};
66
67
 
68
+ // Support `browserDocument.js` syntax
69
+ if (this.schema == null) {
70
+ const _schema = utils.isObject(fields) && !fields.instanceOfSchema ?
71
+ new Schema(fields) :
72
+ fields;
73
+ this.$__setSchema(_schema);
74
+ fields = skipId;
75
+ skipId = options;
76
+ options = arguments[4] || {};
77
+ }
78
+
67
79
  this.$__ = new InternalCache;
68
80
  this.$__.emitter = new EventEmitter();
69
81
  this.isNew = 'isNew' in options ? options.isNew : true;
@@ -172,6 +184,10 @@ utils.each(
172
184
 
173
185
  Document.prototype.constructor = Document;
174
186
 
187
+ for (const i in EventEmitter.prototype) {
188
+ Document[i] = EventEmitter.prototype[i];
189
+ }
190
+
175
191
  /**
176
192
  * The documents schema.
177
193
  *
@@ -486,8 +502,7 @@ Document.prototype.$__init = function(doc, opts) {
486
502
 
487
503
  // handle docs with populated paths
488
504
  // If doc._id is not null or undefined
489
- if (doc._id !== null && doc._id !== undefined &&
490
- opts.populated && opts.populated.length) {
505
+ if (doc._id != null && opts.populated && opts.populated.length) {
491
506
  const id = String(doc._id);
492
507
  for (let i = 0; i < opts.populated.length; ++i) {
493
508
  const item = opts.populated[i];
@@ -501,6 +516,8 @@ Document.prototype.$__init = function(doc, opts) {
501
516
 
502
517
  init(this, doc, this._doc, opts);
503
518
 
519
+ markArraySubdocsPopulated(this, opts.populated);
520
+
504
521
  this.emit('init', this);
505
522
  this.constructor.emit('init', this);
506
523
 
@@ -509,6 +526,44 @@ Document.prototype.$__init = function(doc, opts) {
509
526
  return this;
510
527
  };
511
528
 
529
+ /*!
530
+ * If populating a path within a document array, make sure each
531
+ * subdoc within the array knows its subpaths are populated.
532
+ *
533
+ * ####Example:
534
+ * const doc = await Article.findOne().populate('comments.author');
535
+ * doc.comments[0].populated('author'); // Should be set
536
+ */
537
+
538
+ function markArraySubdocsPopulated(doc, populated) {
539
+ if (doc._id == null || populated == null || populated.length === 0) {
540
+ return;
541
+ }
542
+
543
+ const id = String(doc._id);
544
+ for (const item of populated) {
545
+ if (item.isVirtual) {
546
+ continue;
547
+ }
548
+ const path = item.path;
549
+ const pieces = path.split('.');
550
+ for (let i = 0; i < pieces.length - 1; ++i) {
551
+ const subpath = pieces.slice(0, i + 1).join('.');
552
+ const rest = pieces.slice(i + 1).join('.');
553
+ const val = doc.get(subpath);
554
+ if (val == null) {
555
+ continue;
556
+ }
557
+ if (val.isMongooseDocumentArray) {
558
+ for (let j = 0; j < val.length; ++j) {
559
+ val[j].populated(rest, item._docs[id][j], item);
560
+ }
561
+ break;
562
+ }
563
+ }
564
+ }
565
+ }
566
+
512
567
  /*!
513
568
  * Init helper.
514
569
  *
@@ -1079,12 +1134,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1079
1134
 
1080
1135
  let didPopulate = false;
1081
1136
  if (refMatches && val instanceof Document) {
1082
- if (this.ownerDocument) {
1083
- this.ownerDocument().populated(this.$__fullPath(path),
1084
- val._id, { [populateModelSymbol]: val.constructor });
1085
- } else {
1086
- this.populated(path, val._id, { [populateModelSymbol]: val.constructor });
1087
- }
1137
+ this.populated(path, val._id, { [populateModelSymbol]: val.constructor });
1088
1138
  didPopulate = true;
1089
1139
  }
1090
1140
 
@@ -1117,6 +1167,21 @@ Document.prototype.$set = function $set(path, val, type, options) {
1117
1167
  val = schema.applySetters(val, this, false, priorVal);
1118
1168
  }
1119
1169
 
1170
+ if (schema.$isMongooseDocumentArray &&
1171
+ Array.isArray(val) &&
1172
+ val.length > 0 &&
1173
+ val[0] != null &&
1174
+ val[0].$__ != null &&
1175
+ val[0].$__.populated != null) {
1176
+ const populatedPaths = Object.keys(val[0].$__.populated);
1177
+ for (const populatedPath of populatedPaths) {
1178
+ this.populated(path + '.' + populatedPath,
1179
+ val.map(v => v.populated(populatedPath)),
1180
+ val[0].$__.populated[populatedPath].options);
1181
+ }
1182
+ didPopulate = true;
1183
+ }
1184
+
1120
1185
  if (!didPopulate && this.$__.populated) {
1121
1186
  delete this.$__.populated[path];
1122
1187
  }
@@ -1932,9 +1997,11 @@ Document.prototype.validate = function(options, callback) {
1932
1997
  options = null;
1933
1998
  }
1934
1999
 
1935
- return utils.promiseOrCallback(callback, cb => this.$__validate(options, function(error) {
1936
- cb(error);
1937
- }), this.constructor.events);
2000
+ return utils.promiseOrCallback(callback, cb => {
2001
+ this.$__validate(options, function(error) {
2002
+ cb(error);
2003
+ });
2004
+ }, this.constructor.events);
1938
2005
  };
1939
2006
 
1940
2007
  /*!
@@ -2922,7 +2989,6 @@ Document.prototype.$toObject = function(options, json) {
2922
2989
  *
2923
2990
  * If you want to skip transformations, use `transform: false`:
2924
2991
  *
2925
- * if (!schema.options.toObject) schema.options.toObject = {};
2926
2992
  * schema.options.toObject.hide = '_id';
2927
2993
  * schema.options.toObject.transform = function (doc, ret, options) {
2928
2994
  * if (options.hide) {
@@ -2938,7 +3004,26 @@ Document.prototype.$toObject = function(options, json) {
2938
3004
  * doc.toObject({ hide: 'secret _id', transform: false });// { _id: 'anId', secret: 47, name: 'Wreck-it Ralph' }
2939
3005
  * doc.toObject({ hide: 'secret _id', transform: true }); // { name: 'Wreck-it Ralph' }
2940
3006
  *
2941
- * Transforms are applied _only to the document and are not applied to sub-documents_.
3007
+ * If you pass a transform in `toObject()` options, Mongoose will apply the transform
3008
+ * to [subdocuments](/docs/subdocs.html) in addition to the top-level document.
3009
+ * Similarly, `transform: false` skips transforms for all subdocuments.
3010
+ * Note that this is behavior is different for transforms defined in the schema:
3011
+ * if you define a transform in `schema.options.toObject.transform`, that transform
3012
+ * will **not** apply to subdocuments.
3013
+ *
3014
+ * const memberSchema = new Schema({ name: String, email: String });
3015
+ * const groupSchema = new Schema({ members: [memberSchema], name: String, email });
3016
+ * const Group = mongoose.model('Group', groupSchema);
3017
+ *
3018
+ * const doc = new Group({
3019
+ * name: 'Engineering',
3020
+ * email: 'dev@mongoosejs.io',
3021
+ * members: [{ name: 'Val', email: 'val@mongoosejs.io' }]
3022
+ * });
3023
+ *
3024
+ * // Removes `email` from both top-level document **and** array elements
3025
+ * // { name: 'Engineering', members: [{ name: 'Val' }] }
3026
+ * doc.toObject({ transform: (doc, ret) => { delete ret.email; return ret; } });
2942
3027
  *
2943
3028
  * Transforms, like all of these options, are also available for `toJSON`. See [this guide to `JSON.stringify()`](https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html) to learn why `toJSON()` and `toObject()` are separate functions.
2944
3029
  *
@@ -18,19 +18,21 @@ const util = require('util');
18
18
  function ValidationError(instance) {
19
19
  this.errors = {};
20
20
  this._message = '';
21
+
22
+ MongooseError.call(this, this._message);
21
23
  if (instance && instance.constructor.name === 'model') {
22
24
  this._message = instance.constructor.modelName + ' validation failed';
23
- MongooseError.call(this, this._message);
24
25
  } else {
25
26
  this._message = 'Validation failed';
26
- MongooseError.call(this, this._message);
27
27
  }
28
28
  this.name = 'ValidationError';
29
+
29
30
  if (Error.captureStackTrace) {
30
31
  Error.captureStackTrace(this);
31
32
  } else {
32
33
  this.stack = new Error().stack;
33
34
  }
35
+
34
36
  if (instance) {
35
37
  instance.errors = this.errors;
36
38
  }
@@ -22,6 +22,7 @@ const utils = require('../../utils');
22
22
 
23
23
  module.exports = function eachAsync(next, fn, options, callback) {
24
24
  const parallel = options.parallel || 1;
25
+ const enqueue = asyncQueue();
25
26
 
26
27
  const handleNextResult = function(doc, callback) {
27
28
  const promise = fn(doc);
@@ -37,71 +38,74 @@ module.exports = function eachAsync(next, fn, options, callback) {
37
38
  const iterate = function(callback) {
38
39
  let drained = false;
39
40
 
40
- const getAndRun = function(cb) {
41
- _next(function(err, doc) {
42
- if (err) return cb(err);
43
- if (drained) {
44
- return;
45
- }
46
- if (doc == null) {
47
- drained = true;
48
- return callback(null);
49
- }
50
- handleNextResult(doc, function(err) {
51
- if (err) return cb(err);
52
- // Make sure to clear the stack re: gh-4697
53
- setTimeout(function() {
54
- getAndRun(cb);
55
- }, 0);
56
- });
57
- });
58
- };
59
-
60
41
  let error = null;
61
42
  for (let i = 0; i < parallel; ++i) {
62
- getAndRun(err => {
63
- if (error != null) {
64
- return;
43
+ enqueue(fetch);
44
+ }
45
+
46
+ function fetch(done) {
47
+ if (drained || error) {
48
+ return done();
49
+ }
50
+
51
+ next(function(err, doc) {
52
+ if (drained || error) {
53
+ return done();
65
54
  }
66
55
  if (err != null) {
67
56
  error = err;
68
- return callback(err);
57
+ callback(err);
58
+ return done();
59
+ }
60
+ if (doc == null) {
61
+ drained = true;
62
+ callback(null);
63
+ return done();
69
64
  }
65
+
66
+ done();
67
+
68
+ handleNextResult(doc, function(err) {
69
+ if (err != null) {
70
+ error = err;
71
+ return callback(err);
72
+ }
73
+
74
+ setTimeout(() => enqueue(fetch), 0);
75
+ });
70
76
  });
71
77
  }
72
78
  };
73
79
 
74
- const _nextQueue = [];
75
80
  return utils.promiseOrCallback(callback, cb => {
76
81
  iterate(cb);
77
82
  });
83
+ };
78
84
 
79
- // `next()` can only execute one at a time, so make sure we always execute
80
- // `next()` in series, while still allowing multiple `fn()` instances to run
81
- // in parallel.
82
- function _next(cb) {
83
- if (_nextQueue.length === 0) {
84
- return next(_step(cb));
85
- }
86
- _nextQueue.push(cb);
87
- }
85
+ // `next()` can only execute one at a time, so make sure we always execute
86
+ // `next()` in series, while still allowing multiple `fn()` instances to run
87
+ // in parallel.
88
+ function asyncQueue() {
89
+ const _queue = [];
90
+ let inProgress = null;
91
+ let id = 0;
88
92
 
89
- function _step(cb) {
90
- return function(err, doc) {
91
- if (err != null) {
92
- return cb(err);
93
- }
94
- cb(null, doc);
93
+ return function enqueue(fn) {
94
+ if (_queue.length === 0 && inProgress == null) {
95
+ inProgress = id++;
96
+ return fn(_step);
97
+ }
98
+ _queue.push(fn);
99
+ };
95
100
 
96
- if (doc == null) {
97
- return;
101
+ function _step() {
102
+ setTimeout(() => {
103
+ inProgress = null;
104
+ if (_queue.length > 0) {
105
+ inProgress = id++;
106
+ const fn = _queue.shift();
107
+ fn(_step);
98
108
  }
99
-
100
- setTimeout(() => {
101
- if (_nextQueue.length > 0) {
102
- next(_step(_nextQueue.unshift()));
103
- }
104
- }, 0);
105
- };
109
+ }, 0);
106
110
  }
107
- };
111
+ }
@@ -136,6 +136,9 @@ module.exports = function(query, schema, castedDoc, options, callback) {
136
136
  _err.path = updates[i] + '.' + key;
137
137
  validationErrors.push(_err);
138
138
  }
139
+ } else {
140
+ err.path = updates[i];
141
+ validationErrors.push(err);
139
142
  }
140
143
  }
141
144
  callback(null);
package/lib/index.js CHANGED
@@ -1015,6 +1015,15 @@ Mongoose.prototype.now = function now() { return new Date(); };
1015
1015
 
1016
1016
  Mongoose.prototype.CastError = require('./error/cast');
1017
1017
 
1018
+ /**
1019
+ * The constructor used for schematype options
1020
+ *
1021
+ * @method SchemaTypeOptions
1022
+ * @api public
1023
+ */
1024
+
1025
+ Mongoose.prototype.SchemaTypeOptions = require('./options/SchemaTypeOptions');
1026
+
1018
1027
  /**
1019
1028
  * The [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) driver Mongoose uses.
1020
1029
  *
package/lib/model.js CHANGED
@@ -1066,7 +1066,8 @@ Model.exists = function exists(filter, options, callback) {
1066
1066
  *
1067
1067
  * @param {String} name discriminator model name
1068
1068
  * @param {Schema} schema discriminator model schema
1069
- * @param {String} value the string stored in the `discriminatorKey` property
1069
+ * @param {String} [value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
1070
+ * @return {Model} The newly created discriminator model
1070
1071
  * @api public
1071
1072
  */
1072
1073
 
@@ -1149,7 +1150,7 @@ for (const i in EventEmitter.prototype) {
1149
1150
  *
1150
1151
  * Mongoose calls this function automatically when a model is created using
1151
1152
  * [`mongoose.model()`](/docs/api.html#mongoose_Mongoose-model) or
1152
- * * [`connection.model()`](/docs/api.html#connection_Connection-model), so you
1153
+ * [`connection.model()`](/docs/api.html#connection_Connection-model), so you
1153
1154
  * don't need to call it. This function is also idempotent, so you may call it
1154
1155
  * to get back a promise that will resolve when your indexes are finished
1155
1156
  * building as an alternative to [`MyModel.on('index')`](/docs/guide.html#indexes)
@@ -1963,7 +1964,7 @@ Model.deleteMany = function deleteMany(conditions, options, callback) {
1963
1964
  * var promise = query.exec();
1964
1965
  * promise.addBack(function (err, docs) {});
1965
1966
  *
1966
- * @param {Object} filter
1967
+ * @param {Object|ObjectId} filter
1967
1968
  * @param {Object|String} [projection] optional fields to return, see [`Query.prototype.select()`](http://mongoosejs.com/docs/api.html#query_Query-select)
1968
1969
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
1969
1970
  * @param {Function} [callback]
@@ -3411,7 +3412,7 @@ Model.bulkWrite = function(ops, options, callback) {
3411
3412
  * var mongooseCandy = Candy.hydrate({ _id: '54108337212ffb6d459f854c', type: 'jelly bean' });
3412
3413
  *
3413
3414
  * @param {Object} obj
3414
- * @return {Model} document instance
3415
+ * @return {Document} document instance
3415
3416
  * @api public
3416
3417
  */
3417
3418
 
@@ -4120,10 +4121,11 @@ function populate(model, docs, options, callback) {
4120
4121
  assignmentOpts.excludeId = excludeIdReg.test(select) || (select && select._id === 0);
4121
4122
 
4122
4123
  if (ids.length === 0 || ids.every(utils.isNullOrUndefined)) {
4123
- // Ensure that we set populate virtuals with count option to 0 even
4124
- // if we don't actually execute a query.
4124
+ // Ensure that we set populate virtuals to 0 or empty array even
4125
+ // if we don't actually execute a query because they don't have
4126
+ // a value by default. See gh-7731, gh-8230
4125
4127
  --_remaining;
4126
- if (mod.count) {
4128
+ if (mod.count || mod.isVirtual) {
4127
4129
  _assign(model, [], mod, assignmentOpts);
4128
4130
  }
4129
4131
  continue;
@@ -2,6 +2,19 @@
2
2
 
3
3
  const SchemaTypeOptions = require('./SchemaTypeOptions');
4
4
 
5
+ /**
6
+ * The options defined on an Array schematype.
7
+ *
8
+ * ####Example:
9
+ *
10
+ * const schema = new Schema({ tags: [String] });
11
+ * schema.path('tags').options; // SchemaArrayOptions instance
12
+ *
13
+ * @api public
14
+ * @inherits SchemaTypeOptions
15
+ * @constructor SchemaArrayOptions
16
+ */
17
+
5
18
  class SchemaArrayOptions extends SchemaTypeOptions {}
6
19
 
7
20
  const opts = {
@@ -2,6 +2,19 @@
2
2
 
3
3
  const SchemaTypeOptions = require('./SchemaTypeOptions');
4
4
 
5
+ /**
6
+ * The options defined on a Buffer schematype.
7
+ *
8
+ * ####Example:
9
+ *
10
+ * const schema = new Schema({ bitmap: Buffer });
11
+ * schema.path('bitmap').options; // SchemaBufferOptions instance
12
+ *
13
+ * @api public
14
+ * @inherits SchemaTypeOptions
15
+ * @constructor SchemaBufferOptions
16
+ */
17
+
5
18
  class SchemaBufferOptions extends SchemaTypeOptions {}
6
19
 
7
20
  const opts = {
@@ -2,6 +2,19 @@
2
2
 
3
3
  const SchemaTypeOptions = require('./SchemaTypeOptions');
4
4
 
5
+ /**
6
+ * The options defined on a Date schematype.
7
+ *
8
+ * ####Example:
9
+ *
10
+ * const schema = new Schema({ startedAt: Date });
11
+ * schema.path('startedAt').options; // SchemaDateOptions instance
12
+ *
13
+ * @api public
14
+ * @inherits SchemaTypeOptions
15
+ * @constructor SchemaDateOptions
16
+ */
17
+
5
18
  class SchemaDateOptions extends SchemaTypeOptions {}
6
19
 
7
20
  const opts = {