mongoose 5.0.18 → 5.1.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/model.js CHANGED
@@ -11,6 +11,7 @@ var DocumentNotFoundError = require('./error').DocumentNotFoundError;
11
11
  var DivergentArrayError = require('./error').DivergentArrayError;
12
12
  var Error = require('./error');
13
13
  var EventEmitter = require('events').EventEmitter;
14
+ var MongooseMap = require('./types/map');
14
15
  var OverwriteModelError = require('./error').OverwriteModelError;
15
16
  var PromiseProvider = require('./promise_provider');
16
17
  var Query = require('./query');
@@ -129,21 +130,27 @@ Model.prototype.baseModelName;
129
130
  */
130
131
 
131
132
  Model.prototype.$__handleSave = function(options, callback) {
132
- var _this = this;
133
- var i;
134
- var keys;
135
- var len;
133
+ const _this = this;
134
+ let i;
135
+ let keys;
136
+ let len;
136
137
  if (!options.safe && this.schema.options.safe) {
137
138
  options.safe = this.schema.options.safe;
138
139
  }
139
140
  if (typeof options.safe === 'boolean') {
140
141
  options.safe = null;
141
142
  }
142
- var safe = options.safe ? utils.clone(options.safe) : options.safe;
143
+ let safe = options.safe ? utils.clone(options.safe) : options.safe;
144
+
145
+ const session = 'session' in options ? options.session : this.$session();
146
+ if (session != null) {
147
+ safe = typeof safe === 'object' && safe != null ? safe : {};
148
+ safe.session = session;
149
+ }
143
150
 
144
151
  if (this.isNew) {
145
152
  // send entire doc
146
- var obj = this.toObject(internalToObjectOptions);
153
+ const obj = this.toObject(internalToObjectOptions);
147
154
 
148
155
  if ((obj || {})._id === void 0) {
149
156
  // documents must have an _id else mongoose won't know
@@ -158,7 +165,7 @@ Model.prototype.$__handleSave = function(options, callback) {
158
165
  }
159
166
 
160
167
  this.$__version(true, obj);
161
- this.collection.insert(obj, safe, function(err, ret) {
168
+ this.collection.insertOne(obj, safe, function(err, ret) {
162
169
  if (err) {
163
170
  _this.isNew = true;
164
171
  _this.emit('isNew', true);
@@ -181,7 +188,7 @@ Model.prototype.$__handleSave = function(options, callback) {
181
188
  // since it already exists
182
189
  this.$__.inserting = false;
183
190
 
184
- var delta = this.$__delta();
191
+ const delta = this.$__delta();
185
192
 
186
193
  if (delta) {
187
194
  if (delta instanceof Error) {
@@ -189,7 +196,7 @@ Model.prototype.$__handleSave = function(options, callback) {
189
196
  return;
190
197
  }
191
198
 
192
- var where = this.$__where(delta[0]);
199
+ const where = this.$__where(delta[0]);
193
200
  if (where instanceof Error) {
194
201
  callback(where);
195
202
  return;
@@ -203,7 +210,7 @@ Model.prototype.$__handleSave = function(options, callback) {
203
210
  }
204
211
  }
205
212
 
206
- this.collection.update(where, delta[1], safe, function(err, ret) {
213
+ this.collection.updateOne(where, delta[1], safe, function(err, ret) {
207
214
  if (err) {
208
215
  callback(err);
209
216
  return;
@@ -257,16 +264,17 @@ Model.prototype.$__save = function(options, callback) {
257
264
  let doIncrement = VERSION_INC === (VERSION_INC & this.$__.version);
258
265
  this.$__.version = undefined;
259
266
 
267
+ let key = this.schema.options.versionKey;
268
+ let version = this.getValue(key) || 0;
269
+
260
270
  if (numAffected <= 0) {
261
271
  // the update failed. pass an error back
262
- let err = new VersionError(this);
272
+ let err = new VersionError(this, version);
263
273
  return callback(err);
264
274
  }
265
275
 
266
276
  // increment version if was successful
267
277
  if (doIncrement) {
268
- let key = this.schema.options.versionKey;
269
- let version = this.getValue(key) | 0;
270
278
  this.setValue(key, version + 1);
271
279
  }
272
280
  }
@@ -321,7 +329,9 @@ Model.prototype.save = function(options, fn) {
321
329
  options = undefined;
322
330
  }
323
331
 
324
- if (!options) {
332
+ if (options != null) {
333
+ options = utils.clone(options);
334
+ } else {
325
335
  options = {};
326
336
  }
327
337
 
@@ -1068,8 +1078,8 @@ function _ensureIndexes(model, options, callback) {
1068
1078
  var index = indexes.shift();
1069
1079
  if (!index) return done();
1070
1080
 
1071
- var indexFields = index[0];
1072
- var indexOptions = index[1];
1081
+ var indexFields = utils.clone(index[0]);
1082
+ var indexOptions = utils.clone(index[1]);
1073
1083
  _handleSafe(options);
1074
1084
 
1075
1085
  indexSingleStart(indexFields, options);
@@ -1847,6 +1857,126 @@ Model.findByIdAndUpdate = function(id, update, options, callback) {
1847
1857
  return this.findOneAndUpdate.call(this, {_id: id}, update, options, callback);
1848
1858
  };
1849
1859
 
1860
+ /**
1861
+ * Issue a MongoDB `findOneAndDelete()` command.
1862
+ *
1863
+ * Finds a matching document, removes it, and passes the found document
1864
+ * (if any) to the callback.
1865
+ *
1866
+ * Executes immediately if `callback` is passed else a Query object is returned.
1867
+ *
1868
+ * This function triggers the following middleware.
1869
+ *
1870
+ * - `findOneAndDelete()`
1871
+ *
1872
+ * This function differs slightly from `Model.findOneAndRemove()` in that
1873
+ * `findOneAndRemove()` becomes a [MongoDB `findAndModify()` command](https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/),
1874
+ * as opposed to a `findOneAndDelete()` command. For most mongoose use cases,
1875
+ * this distinction is purely pedantic. You should use `findOneAndDelete()`
1876
+ * unless you have a good reason not to.
1877
+ *
1878
+ * ####Options:
1879
+ *
1880
+ * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
1881
+ * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
1882
+ * - `select`: sets the document fields to return
1883
+ * - `rawResult`: if true, returns the [raw result from the MongoDB driver](http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findAndModify)
1884
+ * - `strict`: overwrites the schema's [strict mode option](http://mongoosejs.com/docs/guide.html#strict) for this update
1885
+ *
1886
+ * ####Examples:
1887
+ *
1888
+ * A.findOneAndDelete(conditions, options, callback) // executes
1889
+ * A.findOneAndDelete(conditions, options) // return Query
1890
+ * A.findOneAndDelete(conditions, callback) // executes
1891
+ * A.findOneAndDelete(conditions) // returns Query
1892
+ * A.findOneAndDelete() // returns Query
1893
+ *
1894
+ * Values are cast to their appropriate types when using the findAndModify helpers.
1895
+ * However, the below are not executed by default.
1896
+ *
1897
+ * - defaults. Use the `setDefaultsOnInsert` option to override.
1898
+ *
1899
+ * `findAndModify` helpers support limited validation. You can
1900
+ * enable these by setting the `runValidators` options,
1901
+ * respectively.
1902
+ *
1903
+ * If you need full-fledged validation, use the traditional approach of first
1904
+ * retrieving the document.
1905
+ *
1906
+ * Model.findById(id, function (err, doc) {
1907
+ * if (err) ..
1908
+ * doc.name = 'jason bourne';
1909
+ * doc.save(callback);
1910
+ * });
1911
+ *
1912
+ * @param {Object} conditions
1913
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
1914
+ * @param {Function} [callback]
1915
+ * @return {Query}
1916
+ * @api public
1917
+ */
1918
+
1919
+ Model.findOneAndDelete = function(conditions, options, callback) {
1920
+ if (arguments.length === 1 && typeof conditions === 'function') {
1921
+ var msg = 'Model.findOneAndDelete(): First argument must not be a function.\n\n'
1922
+ + ' ' + this.modelName + '.findOneAndDelete(conditions, callback)\n'
1923
+ + ' ' + this.modelName + '.findOneAndDelete(conditions)\n'
1924
+ + ' ' + this.modelName + '.findOneAndDelete()\n';
1925
+ throw new TypeError(msg);
1926
+ }
1927
+
1928
+ if (typeof options === 'function') {
1929
+ callback = options;
1930
+ options = undefined;
1931
+ }
1932
+ if (callback) {
1933
+ callback = this.$wrapCallback(callback);
1934
+ }
1935
+
1936
+ var fields;
1937
+ if (options) {
1938
+ fields = options.select;
1939
+ options.select = undefined;
1940
+ }
1941
+
1942
+ var mq = new this.Query({}, {}, this, this.collection);
1943
+ mq.select(fields);
1944
+
1945
+ return mq.findOneAndDelete(conditions, options, callback);
1946
+ };
1947
+
1948
+ /**
1949
+ * Issue a MongoDB `findOneAndDelete()` command by a document's _id field.
1950
+ * In other words, `findByIdAndDelete(id)` is a shorthand for
1951
+ * `findOneAndDelete({ _id: id })`.
1952
+ *
1953
+ * This function triggers the following middleware.
1954
+ *
1955
+ * - `findOneAndDelete()`
1956
+ *
1957
+ * @param {Object|Number|String} id value of `_id` to query by
1958
+ * @param {Object} [options] optional see [`Query.prototype.setOptions()`](http://mongoosejs.com/docs/api.html#query_Query-setOptions)
1959
+ * @param {Function} [callback]
1960
+ * @return {Query}
1961
+ * @see Model.findOneAndRemove #model_Model.findOneAndRemove
1962
+ * @see mongodb http://www.mongodb.org/display/DOCS/findAndModify+Command
1963
+ */
1964
+
1965
+ Model.findByIdAndDelete = function(id, options, callback) {
1966
+ if (arguments.length === 1 && typeof id === 'function') {
1967
+ var msg = 'Model.findByIdAndDelete(): First argument must not be a function.\n\n'
1968
+ + ' ' + this.modelName + '.findByIdAndDelete(id, callback)\n'
1969
+ + ' ' + this.modelName + '.findByIdAndDelete(id)\n'
1970
+ + ' ' + this.modelName + '.findByIdAndDelete()\n';
1971
+ throw new TypeError(msg);
1972
+ }
1973
+ if (callback) {
1974
+ callback = this.$wrapCallback(callback);
1975
+ }
1976
+
1977
+ return this.findOneAndDelete({_id: id}, options, callback);
1978
+ };
1979
+
1850
1980
  /**
1851
1981
  * Issue a mongodb findAndModify remove command.
1852
1982
  *
@@ -2125,6 +2255,41 @@ Model.watch = function(pipeline, options) {
2125
2255
  return new ChangeStream(this, pipeline, options);
2126
2256
  };
2127
2257
 
2258
+ /**
2259
+ * _Requires MongoDB >= 3.6.0._ Starts a [MongoDB session](https://docs.mongodb.com/manual/release-notes/3.6/#client-sessions)
2260
+ * for benefits like causal consistency and [retryable writes](https://docs.mongodb.com/manual/core/retryable-writes/).
2261
+ *
2262
+ * This function does not trigger any middleware.
2263
+ *
2264
+ * ####Example:
2265
+ *
2266
+ * const session = await Person.startSession();
2267
+ * let doc = await Person.findOne({ name: 'Ned Stark' }, { session });
2268
+ * await doc.remove();
2269
+ * // `doc` will always be null, even if reading from a replica set
2270
+ * // secondary. Without causal consistency, it is possible to
2271
+ * // get a doc back from the below query if the query reads from a
2272
+ * // secondary that is experiencing replication lag.
2273
+ * doc = await Person.findOne({ name: 'Ned Stark' }, { readPreference: 'secondary' });
2274
+ *
2275
+ * @param {Object} [options] see the [mongodb driver options](http://mongodb.github.io/node-mongodb-native/3.0/api/MongoClient.html#startSession)
2276
+ * @param {Boolean} [options.causalConsistency=true] set to false to disable causal consistency
2277
+ * @param {Function} [callback]
2278
+ * @return {Promise<ClientSession>} promise that resolves to a MongoDB driver `ClientSession`
2279
+ * @api public
2280
+ */
2281
+
2282
+ Model.startSession = function(options, callback) {
2283
+ return utils.promiseOrCallback(callback, cb => {
2284
+ if (this.collection.buffer) {
2285
+ return this.collection.addQueue(() => {
2286
+ cb(null, this.db.client.startSession(options));
2287
+ });
2288
+ }
2289
+ return cb(null, this.db.client.startSession(options));
2290
+ });
2291
+ };
2292
+
2128
2293
  /**
2129
2294
  * Shortcut for validating an array of documents and inserting them into
2130
2295
  * MongoDB if they're all valid. This function is faster than `.create()`
@@ -3051,6 +3216,7 @@ function populate(model, docs, options, callback) {
3051
3216
  }
3052
3217
 
3053
3218
  modelsMap = getModelsMapForPopulate(model, docs, options);
3219
+
3054
3220
  if (modelsMap instanceof Error) {
3055
3221
  return utils.immediate(function() {
3056
3222
  callback(modelsMap);
@@ -3266,9 +3432,9 @@ function assignVals(o) {
3266
3432
  return valueFilter(val, options, o.justOne);
3267
3433
  }
3268
3434
 
3269
- for (var i = 0; i < docs.length; ++i) {
3270
- if (utils.getValue(o.path, docs[i]) == null &&
3271
- !getVirtual(o.originalModel.schema, o.path)) {
3435
+ for (let i = 0; i < docs.length; ++i) {
3436
+ const existingVal = utils.getValue(o.path, docs[i]);
3437
+ if (existingVal == null && !getVirtual(o.originalModel.schema, o.path)) {
3272
3438
  continue;
3273
3439
  }
3274
3440
 
@@ -3282,6 +3448,23 @@ function assignVals(o) {
3282
3448
  rawIds[i] = rawIds[i][0];
3283
3449
  }
3284
3450
 
3451
+
3452
+ // If we're populating a map, the existing value will be an object, so
3453
+ // we need to transform again
3454
+ const isMap = docs[i].constructor.name === 'model' ?
3455
+ existingVal instanceof Map :
3456
+ existingVal != null && existingVal.constructor.name === 'Object';
3457
+ if (!o.isVirtual && isMap) {
3458
+ const _keys = existingVal instanceof Map ?
3459
+ Array.from(existingVal.keys()) :
3460
+ Object.keys(existingVal);
3461
+ rawIds[i] = rawIds[i].reduce((cur, v, i) => {
3462
+ // Avoid casting because that causes infinite recursion
3463
+ cur.$init(_keys[i], v);
3464
+ return cur;
3465
+ }, new MongooseMap({}, docs[i]));
3466
+ }
3467
+
3285
3468
  if (o.isVirtual && docs[i].constructor.name === 'model') {
3286
3469
  // If virtual populate and doc is already init-ed, need to walk through
3287
3470
  // the actual doc to set rather than setting `_doc` directly
@@ -3298,6 +3481,7 @@ function assignVals(o) {
3298
3481
  if (docs[i].$__) {
3299
3482
  docs[i].populated(o.path, o.allIds[i], o.allOptions);
3300
3483
  }
3484
+
3301
3485
  utils.setValue(o.path, rawIds[i], docs[i], setValue, false);
3302
3486
  }
3303
3487
  }
@@ -3481,6 +3665,7 @@ function getModelsMapForPopulate(model, docs, options) {
3481
3665
  if (typeof foreignField === 'function') {
3482
3666
  foreignField = foreignField.call(doc);
3483
3667
  }
3668
+
3484
3669
  const ret = convertTo_id(utils.getValue(localField, doc));
3485
3670
  const id = String(utils.getValue(foreignField, doc));
3486
3671
  options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
@@ -3543,6 +3728,9 @@ function getModelsMapForPopulate(model, docs, options) {
3543
3728
  if (schema && schema.caster) {
3544
3729
  schema = schema.caster;
3545
3730
  }
3731
+ if (schema && schema.$isSchemaMap) {
3732
+ schema = schema.$__schemaType;
3733
+ }
3546
3734
 
3547
3735
  if (!schema && model.discriminators) {
3548
3736
  discriminatorKey = model.schema.discriminatorMapping.key;
@@ -3631,7 +3819,7 @@ function convertTo_id(val) {
3631
3819
  if (val instanceof Model) return val._id;
3632
3820
 
3633
3821
  if (Array.isArray(val)) {
3634
- for (var i = 0; i < val.length; ++i) {
3822
+ for (let i = 0; i < val.length; ++i) {
3635
3823
  if (val[i] instanceof Model) {
3636
3824
  val[i] = val[i]._id;
3637
3825
  }
@@ -3643,6 +3831,21 @@ function convertTo_id(val) {
3643
3831
  return [].concat(val);
3644
3832
  }
3645
3833
 
3834
+ // `populate('map')` may be an object if populating on a doc that hasn't
3835
+ // been hydrated yet
3836
+ if (val != null && val.constructor.name === 'Object') {
3837
+ const ret = [];
3838
+ for (const key of Object.keys(val)) {
3839
+ ret.push(val[key]);
3840
+ }
3841
+ return ret;
3842
+ }
3843
+ // If doc has already been hydrated, e.g. `doc.populate('map').execPopulate()`
3844
+ // then `val` will already be a map
3845
+ if (val instanceof Map) {
3846
+ return Array.from(val.values());
3847
+ }
3848
+
3646
3849
  return val;
3647
3850
  }
3648
3851
 
@@ -3874,24 +4077,24 @@ function applyQueryMiddleware(Query, model) {
3874
4077
  nullResultByDefault: true
3875
4078
  };
3876
4079
 
3877
- Query.prototype._count = model.hooks.createWrapper('count',
3878
- Query.prototype._count, null, kareemOptions);
4080
+ // `update()` thunk has a different name because `_update` was already taken
3879
4081
  Query.prototype._execUpdate = model.hooks.createWrapper('update',
3880
4082
  Query.prototype._execUpdate, null, kareemOptions);
3881
- Query.prototype._find = model.hooks.createWrapper('find',
3882
- Query.prototype._find, null, kareemOptions);
3883
- Query.prototype._findOne = model.hooks.createWrapper('findOne',
3884
- Query.prototype._findOne, null, kareemOptions);
3885
- Query.prototype._findOneAndRemove = model.hooks.createWrapper('findOneAndRemove',
3886
- Query.prototype._findOneAndRemove, null, kareemOptions);
3887
- Query.prototype._findOneAndUpdate = model.hooks.createWrapper('findOneAndUpdate',
3888
- Query.prototype._findOneAndUpdate, null, kareemOptions);
3889
- Query.prototype._replaceOne = model.hooks.createWrapper('replaceOne',
3890
- Query.prototype._replaceOne, null, kareemOptions);
3891
- Query.prototype._updateMany = model.hooks.createWrapper('updateMany',
3892
- Query.prototype._updateMany, null, kareemOptions);
3893
- Query.prototype._updateOne = model.hooks.createWrapper('updateOne',
3894
- Query.prototype._updateOne, null, kareemOptions);
4083
+
4084
+ [
4085
+ 'count',
4086
+ 'find',
4087
+ 'findOne',
4088
+ 'findOneAndDelete',
4089
+ 'findOneAndRemove',
4090
+ 'findOneAndUpdate',
4091
+ 'replaceOne',
4092
+ 'updateMany',
4093
+ 'updateOne'
4094
+ ].forEach(fn => {
4095
+ Query.prototype[`_${fn}`] = model.hooks.createWrapper(fn,
4096
+ Query.prototype[`_${fn}`], null, kareemOptions);
4097
+ });
3895
4098
  }
3896
4099
 
3897
4100
  /*!
@@ -23,7 +23,7 @@ module.exports = function(schema) {
23
23
  }
24
24
 
25
25
  each(subdocs, function(subdoc, cb) {
26
- subdoc.save(function(err) {
26
+ subdoc.save({ suppressWarning: true }, function(err) {
27
27
  cb(err);
28
28
  });
29
29
  }, function(error) {