mongoose 6.1.7 → 6.2.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.
@@ -9,6 +9,8 @@ let Document;
9
9
  const getSymbol = require('../../helpers/symbols').getSymbol;
10
10
  const scopeSymbol = require('../../helpers/symbols').scopeSymbol;
11
11
 
12
+ const isPOJO = utils.isPOJO;
13
+
12
14
  /*!
13
15
  * exports
14
16
  */
@@ -22,12 +24,14 @@ exports.defineKey = defineKey;
22
24
 
23
25
  function compile(tree, proto, prefix, options) {
24
26
  Document = Document || require('../../document');
27
+ const typeKey = options.typeKey;
25
28
 
26
29
  for (const key of Object.keys(tree)) {
27
30
  const limb = tree[key];
28
31
 
29
- const hasSubprops = utils.isPOJO(limb) && Object.keys(limb).length &&
30
- (!limb[options.typeKey] || (options.typeKey === 'type' && limb.type.type));
32
+ const hasSubprops = isPOJO(limb) &&
33
+ Object.keys(limb).length > 0 &&
34
+ (!limb[typeKey] || (typeKey === 'type' && isPOJO(limb.type) && limb.type.type));
31
35
  const subprops = hasSubprops ? limb : null;
32
36
 
33
37
  defineKey({ prop: key, subprops: subprops, prototype: proto, prefix: prefix, options: options });
@@ -17,6 +17,7 @@ const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
17
17
  */
18
18
 
19
19
  module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) {
20
+
20
21
  if (!(schema && schema.instanceOfSchema)) {
21
22
  throw new Error('You must pass a valid discriminator Schema');
22
23
  }
@@ -201,7 +202,7 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
201
202
 
202
203
  model.schema.discriminators[name] = schema;
203
204
 
204
- if (model.discriminators[name]) {
205
+ if (model.discriminators[name] && !schema.options.overwriteModels) {
205
206
  throw new Error('Discriminator with name "' + name + '" already exists');
206
207
  }
207
208
 
@@ -1,9 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = function setDottedPath(obj, path, val) {
4
- const parts = path.indexOf('.') === -1 ? [path] : path.split('.');
4
+ if (path.indexOf('.') === -1) {
5
+ obj[path] = val;
6
+ return;
7
+ }
8
+ const parts = path.split('.');
9
+ const last = parts.pop();
5
10
  let cur = obj;
6
- for (const part of parts.slice(0, -1)) {
11
+ for (const part of parts) {
7
12
  if (cur[part] == null) {
8
13
  cur[part] = {};
9
14
  }
@@ -11,6 +16,5 @@ module.exports = function setDottedPath(obj, path, val) {
11
16
  cur = cur[part];
12
17
  }
13
18
 
14
- const last = parts[parts.length - 1];
15
19
  cur[last] = val;
16
20
  };
@@ -696,7 +696,7 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
696
696
  schematype.caster.discriminators != null &&
697
697
  Object.keys(schematype.caster.discriminators).length > 0) {
698
698
  const subdocs = utils.getValue(cur, doc);
699
- const remnant = options.path.substr(cur.length + 1);
699
+ const remnant = options.path.substring(cur.length + 1);
700
700
  const discriminatorKey = schematype.caster.schema.options.discriminatorKey;
701
701
  modelNames = [];
702
702
  for (const subdoc of subdocs) {
@@ -708,7 +708,7 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
708
708
  }
709
709
  const _path = discriminatorSchema.path(remnant);
710
710
  if (_path == null || _path.options.refPath == null) {
711
- const docValue = utils.getValue(data.localField.substr(cur.length + 1), subdoc);
711
+ const docValue = utils.getValue(data.localField.substring(cur.length + 1), subdoc);
712
712
  ret.forEach((v, i) => {
713
713
  if (v === docValue) {
714
714
  ret[i] = SkipPopulateValue(v);
@@ -35,8 +35,8 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
35
35
  for (let i = 0; i < chunks.length; i += 2) {
36
36
  const chunk = chunks[i];
37
37
  if (_remaining.startsWith(chunk + '.')) {
38
- _refPath += _remaining.substr(0, chunk.length) + chunks[i + 1];
39
- _remaining = _remaining.substr(chunk.length + 1);
38
+ _refPath += _remaining.substring(0, chunk.length) + chunks[i + 1];
39
+ _remaining = _remaining.substring(chunk.length + 1);
40
40
  } else if (i === chunks.length - 1) {
41
41
  _refPath += _remaining;
42
42
  _remaining = '';
@@ -0,0 +1,284 @@
1
+ 'use strict';
2
+
3
+ const CastError = require('../../error/cast');
4
+ const StrictModeError = require('../../error/strict');
5
+ const castNumber = require('../../cast/number');
6
+
7
+ const booleanComparison = new Set(['$and', '$or', '$not']);
8
+ const comparisonOperator = new Set(['$cmp', '$eq', '$lt', '$lte', '$gt', '$gte']);
9
+ const arithmeticOperatorArray = new Set([
10
+ // avoid casting '$add' or '$subtract', because expressions can be either number or date,
11
+ // and we don't have a good way of inferring which arguments should be numbers and which should
12
+ // be dates.
13
+ '$multiply',
14
+ '$divide',
15
+ '$log',
16
+ '$mod',
17
+ '$trunc',
18
+ '$avg',
19
+ '$max',
20
+ '$min',
21
+ '$stdDevPop',
22
+ '$stdDevSamp',
23
+ '$sum'
24
+ ]);
25
+ const arithmeticOperatorNumber = new Set([
26
+ '$abs',
27
+ '$exp',
28
+ '$ceil',
29
+ '$floor',
30
+ '$ln',
31
+ '$log10',
32
+ '$round',
33
+ '$sqrt',
34
+ '$sin',
35
+ '$cos',
36
+ '$tan',
37
+ '$asin',
38
+ '$acos',
39
+ '$atan',
40
+ '$atan2',
41
+ '$asinh',
42
+ '$acosh',
43
+ '$atanh',
44
+ '$sinh',
45
+ '$cosh',
46
+ '$tanh',
47
+ '$degreesToRadians',
48
+ '$radiansToDegrees'
49
+ ]);
50
+ const arrayElementOperators = new Set([
51
+ '$arrayElemAt',
52
+ '$first',
53
+ '$last'
54
+ ]);
55
+ const dateOperators = new Set([
56
+ '$year',
57
+ '$month',
58
+ '$week',
59
+ '$dayOfMonth',
60
+ '$dayOfYear',
61
+ '$hour',
62
+ '$minute',
63
+ '$second',
64
+ '$isoDayOfWeek',
65
+ '$isoWeekYear',
66
+ '$isoWeek',
67
+ '$millisecond'
68
+ ]);
69
+
70
+ module.exports = function cast$expr(val, schema, strictQuery) {
71
+ if (typeof val !== 'object' || val == null) {
72
+ throw new Error('`$expr` must be an object');
73
+ }
74
+
75
+ return _castExpression(val, schema, strictQuery);
76
+ };
77
+
78
+ function _castExpression(val, schema, strictQuery) {
79
+ if (isPath(val)) {
80
+ // Assume path
81
+ return val;
82
+ }
83
+
84
+ if (val.$cond != null) {
85
+ if (Array.isArray(val.$cond)) {
86
+ val.$cond = val.$cond.map(expr => _castExpression(expr, schema, strictQuery));
87
+ } else {
88
+ val.$cond.if = _castExpression(val.$cond.if, schema, strictQuery);
89
+ val.$cond.then = _castExpression(val.$cond.then, schema, strictQuery);
90
+ val.$cond.else = _castExpression(val.$cond.else, schema, strictQuery);
91
+ }
92
+ } else if (val.$ifNull != null) {
93
+ val.$ifNull.map(v => _castExpression(v, schema, strictQuery));
94
+ } else if (val.$switch != null) {
95
+ val.branches.map(v => _castExpression(v, schema, strictQuery));
96
+ val.default = _castExpression(val.default, schema, strictQuery);
97
+ }
98
+
99
+ const keys = Object.keys(val);
100
+ for (const key of keys) {
101
+ if (booleanComparison.has(key)) {
102
+ val[key] = val[key].map(v => _castExpression(v, schema, strictQuery));
103
+ } else if (comparisonOperator.has(key)) {
104
+ val[key] = castComparison(val[key], schema, strictQuery);
105
+ } else if (arithmeticOperatorArray.has(key)) {
106
+ val[key] = castArithmetic(val[key], schema, strictQuery);
107
+ } else if (arithmeticOperatorNumber.has(key)) {
108
+ val[key] = castNumberOperator(val[key], schema, strictQuery);
109
+ }
110
+ }
111
+
112
+ if (val.$in) {
113
+ val.$in = castIn(val.$in, schema, strictQuery);
114
+ }
115
+ if (val.$size) {
116
+ val.$size = castNumberOperator(val.$size, schema, strictQuery);
117
+ }
118
+
119
+ _omitUndefined(val);
120
+
121
+ return val;
122
+ }
123
+
124
+ function _omitUndefined(val) {
125
+ const keys = Object.keys(val);
126
+ for (const key of keys) {
127
+ if (val[key] === void 0) {
128
+ delete val[key];
129
+ }
130
+ }
131
+ }
132
+
133
+ // { $op: <number> }
134
+ function castNumberOperator(val) {
135
+ if (!isLiteral(val)) {
136
+ return val;
137
+ }
138
+
139
+ try {
140
+ return castNumber(val);
141
+ } catch (err) {
142
+ throw new CastError('Number', val);
143
+ }
144
+ }
145
+
146
+ function castIn(val, schema, strictQuery) {
147
+ let search = val[0];
148
+ let path = val[1];
149
+ if (!isPath(path)) {
150
+ return val;
151
+ }
152
+
153
+ path = path.slice(1);
154
+ const schematype = schema.path(path);
155
+ if (schematype == null) {
156
+ if (strictQuery === false) {
157
+ return val;
158
+ } else if (strictQuery === 'throw') {
159
+ throw new StrictModeError('$in');
160
+ }
161
+
162
+ return void 0;
163
+ }
164
+
165
+ if (!schematype.$isMongooseArray) {
166
+ throw new Error('Path must be an array for $in');
167
+ }
168
+
169
+ if (schematype.$isMongooseDocumentArray) {
170
+ search = schematype.$embeddedSchemaType.cast(search);
171
+ } else {
172
+ search = schematype.caster.cast(search);
173
+ }
174
+ return [search, val[1]];
175
+ }
176
+
177
+ // { $op: [<number>, <number>] }
178
+ function castArithmetic(val) {
179
+ if (!Array.isArray(val)) {
180
+ if (!isLiteral(val)) {
181
+ return val;
182
+ }
183
+ try {
184
+ return castNumber(val);
185
+ } catch (err) {
186
+ throw new CastError('Number', val);
187
+ }
188
+ }
189
+
190
+ return val.map(v => {
191
+ if (!isLiteral(v)) {
192
+ return v;
193
+ }
194
+ try {
195
+ return castNumber(v);
196
+ } catch (err) {
197
+ throw new CastError('Number', v);
198
+ }
199
+ });
200
+ }
201
+
202
+ // { $op: [expression, expression] }
203
+ function castComparison(val, schema, strictQuery) {
204
+ if (!Array.isArray(val) || val.length !== 2) {
205
+ throw new Error('Comparison operator must be an array of length 2');
206
+ }
207
+
208
+ val[0] = _castExpression(val[0], schema, strictQuery);
209
+ const lhs = val[0];
210
+
211
+ if (isLiteral(val[1])) {
212
+ let path = null;
213
+ let schematype = null;
214
+ let caster = null;
215
+ if (isPath(lhs)) {
216
+ path = lhs.slice(1);
217
+ schematype = schema.path(path);
218
+ } else if (typeof lhs === 'object' && lhs != null) {
219
+ for (const key of Object.keys(lhs)) {
220
+ if (dateOperators.has(key) && isPath(lhs[key])) {
221
+ path = lhs[key].slice(1) + '.' + key;
222
+ caster = castNumber;
223
+ } else if (arrayElementOperators.has(key) && isPath(lhs[key])) {
224
+ path = lhs[key].slice(1) + '.' + key;
225
+ schematype = schema.path(lhs[key].slice(1));
226
+ if (schematype != null) {
227
+ if (schematype.$isMongooseDocumentArray) {
228
+ schematype = schematype.$embeddedSchemaType;
229
+ } else if (schematype.$isMongooseArray) {
230
+ schematype = schematype.caster;
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+
237
+ const is$literal = typeof val[1] === 'object' && val[1] != null && val[1].$literal != null;
238
+ if (schematype != null) {
239
+ if (is$literal) {
240
+ val[1] = { $literal: schematype.cast(val[1].$literal) };
241
+ } else {
242
+ val[1] = schematype.cast(val[1]);
243
+ }
244
+ } else if (caster != null) {
245
+ if (is$literal) {
246
+ try {
247
+ val[1] = { $literal: caster(val[1].$literal) };
248
+ } catch (err) {
249
+ throw new CastError(caster.name.replace(/^cast/, ''), val[1], path + '.$literal');
250
+ }
251
+ } else {
252
+ try {
253
+ val[1] = caster(val[1]);
254
+ } catch (err) {
255
+ throw new CastError(caster.name.replace(/^cast/, ''), val[1], path);
256
+ }
257
+ }
258
+ } else if (path != null && strictQuery === true) {
259
+ return void 0;
260
+ } else if (path != null && strictQuery === 'throw') {
261
+ throw new StrictModeError(path);
262
+ }
263
+ } else {
264
+ val[1] = _castExpression(val[1]);
265
+ }
266
+
267
+ return val;
268
+ }
269
+
270
+ function isPath(val) {
271
+ return typeof val === 'string' && val.startsWith('$');
272
+ }
273
+
274
+ function isLiteral(val) {
275
+ if (typeof val === 'string' && val.startsWith('$')) {
276
+ return false;
277
+ }
278
+ if (typeof val === 'object' && val != null && Object.keys(val).find(key => key.startsWith('$'))) {
279
+ // The `$literal` expression can make an object a literal
280
+ // https://docs.mongodb.com/manual/reference/operator/aggregation/literal/#mongodb-expression-exp.-literal
281
+ return val.$literal != null;
282
+ }
283
+ return true;
284
+ }
@@ -7,7 +7,18 @@ module.exports = function applyPlugins(schema, plugins, options, cacheKey) {
7
7
  schema[cacheKey] = true;
8
8
 
9
9
  if (!options || !options.skipTopLevel) {
10
+ let pluginTags = null;
10
11
  for (const plugin of plugins) {
12
+ const tags = plugin[1] == null ? null : plugin[1].tags;
13
+ if (!Array.isArray(tags)) {
14
+ schema.plugin(plugin[0], plugin[1]);
15
+ continue;
16
+ }
17
+
18
+ pluginTags = pluginTags || new Set(schema.options.pluginTags || []);
19
+ if (!tags.find(tag => pluginTags.has(tag))) {
20
+ continue;
21
+ }
11
22
  schema.plugin(plugin[0], plugin[1]);
12
23
  }
13
24
  }
@@ -79,14 +79,23 @@ module.exports = function(filter, schema, castedDoc, options) {
79
79
  return;
80
80
  }
81
81
  const def = schemaType.getDefault(null, true);
82
- if (!isModified(modified, path) && typeof def !== 'undefined') {
83
- castedDoc = castedDoc || {};
84
- castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
85
- if (get(castedDoc, path) == null) {
86
- castedDoc.$setOnInsert[path] = def;
87
- }
88
- updatedValues[path] = def;
82
+ if (isModified(modified, path)) {
83
+ return;
84
+ }
85
+ if (typeof def === 'undefined') {
86
+ return;
87
+ }
88
+ if (schemaType.splitPath().includes('$*')) {
89
+ // Skip defaults underneath maps. We should never do `$setOnInsert` on a path with `$*`
90
+ return;
91
+ }
92
+
93
+ castedDoc = castedDoc || {};
94
+ castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
95
+ if (get(castedDoc, path) == null) {
96
+ castedDoc.$setOnInsert[path] = def;
89
97
  }
98
+ updatedValues[path] = def;
90
99
  });
91
100
 
92
101
  return castedDoc;
@@ -153,7 +153,7 @@ function applyTimestampsToUpdateKey(schema, key, update, now) {
153
153
  // Single nested is easy
154
154
  update[parentPath + '.' + updatedAt] = now;
155
155
  } else if (parentSchemaType.$isMongooseDocumentArray) {
156
- let childPath = key.substr(parentPath.length + 1);
156
+ let childPath = key.substring(parentPath.length + 1);
157
157
 
158
158
  if (/^\d+$/.test(childPath)) {
159
159
  update[parentPath + '.' + childPath][updatedAt] = now;
@@ -161,7 +161,7 @@ function applyTimestampsToUpdateKey(schema, key, update, now) {
161
161
  }
162
162
 
163
163
  const firstDot = childPath.indexOf('.');
164
- childPath = firstDot !== -1 ? childPath.substr(0, firstDot) : childPath;
164
+ childPath = firstDot !== -1 ? childPath.substring(0, firstDot) : childPath;
165
165
 
166
166
  update[parentPath + '.' + childPath + '.' + updatedAt] = now;
167
167
  }
@@ -51,7 +51,7 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt
51
51
  const dot = key.indexOf('.');
52
52
  let filterPath = dot === -1 ?
53
53
  updatedPathsByFilter[key] + '.0' :
54
- updatedPathsByFilter[key.substr(0, dot)] + '.0' + key.substr(dot);
54
+ updatedPathsByFilter[key.substring(0, dot)] + '.0' + key.substring(dot);
55
55
  if (filterPath == null) {
56
56
  throw new Error(`Filter path not found for ${key}`);
57
57
  }
@@ -19,7 +19,7 @@ module.exports = function updatedPathsByArrayFilter(update) {
19
19
  throw new Error(`Path '${path}' contains the same array filter multiple times`);
20
20
  }
21
21
  cur[match.substring(2, match.length - 1)] = path.
22
- substr(0, firstMatch - 1).
22
+ substring(0, firstMatch - 1).
23
23
  replace(/\$\[[^\]]+\]/g, '0');
24
24
  }
25
25
  return cur;
package/lib/index.js CHANGED
@@ -947,55 +947,29 @@ Mongoose.prototype.ObjectId = SchemaTypes.ObjectId;
947
947
  *
948
948
  * mongoose.isValidObjectId(new mongoose.Types.ObjectId()); // true
949
949
  * mongoose.isValidObjectId('0123456789ab'); // true
950
- * mongoose.isValidObjectId(6); // false
950
+ * mongoose.isValidObjectId(6); // true
951
+ * mongoose.isValidObjectId({ test: 42 }); // false
951
952
  *
952
953
  * @method isValidObjectId
953
954
  * @api public
954
955
  */
955
956
 
956
957
  Mongoose.prototype.isValidObjectId = function(v) {
957
- if (v == null) {
958
- return true;
959
- }
960
- const base = this || mongoose;
961
- const ObjectId = base.driver.get().ObjectId;
962
- if (v instanceof ObjectId) {
963
- return true;
964
- }
965
-
966
- if (v._id != null) {
967
- if (v._id instanceof ObjectId) {
968
- return true;
969
- }
970
- if (v._id.toString instanceof Function) {
971
- v = v._id.toString();
972
- if (typeof v === 'string' && v.length === 12) {
973
- return true;
974
- }
975
- if (typeof v === 'string' && /^[0-9A-Fa-f]{24}$/.test(v)) {
976
- return true;
977
- }
978
- return false;
979
- }
980
- }
981
-
982
- if (v.toString instanceof Function) {
983
- v = v.toString();
984
- }
985
-
986
- if (typeof v === 'string' && v.length === 12) {
987
- return true;
988
- }
989
- if (typeof v === 'string' && /^[0-9A-Fa-f]{24}$/.test(v)) {
990
- return true;
991
- }
992
-
993
- return false;
958
+ const _mongoose = this instanceof Mongoose ? this : mongoose;
959
+ return _mongoose.Types.ObjectId.isValid(v);
994
960
  };
995
961
 
996
- Mongoose.prototype.syncIndexes = function() {
962
+ /**
963
+ *
964
+ * Syncs all the indexes for the models registered with this connection.
965
+ *
966
+ * @param {Object} options
967
+ * @param {Boolean} options.continueOnError `false` by default. If set to `true`, mongoose will not throw an error if one model syncing failed, and will return an object where the keys are the names of the models, and the values are the results/errors for each model.
968
+ * @returns
969
+ */
970
+ Mongoose.prototype.syncIndexes = function(options) {
997
971
  const _mongoose = this instanceof Mongoose ? this : mongoose;
998
- return _mongoose.connection.syncIndexes();
972
+ return _mongoose.connection.syncIndexes(options);
999
973
  };
1000
974
 
1001
975
  /**
package/lib/model.js CHANGED
@@ -48,6 +48,7 @@ const modifiedPaths = require('./helpers/update/modifiedPaths');
48
48
  const parallelLimit = require('./helpers/parallelLimit');
49
49
  const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
50
50
  const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
51
+ const setDottedPath = require('./helpers/path/setDottedPath');
51
52
  const util = require('util');
52
53
  const utils = require('./utils');
53
54
 
@@ -832,7 +833,8 @@ Model.prototype.$__version = function(where, delta) {
832
833
  if (where === true) {
833
834
  // this is an insert
834
835
  if (key) {
835
- this.$__setValue(key, delta[key] = 0);
836
+ setDottedPath(delta, key, 0);
837
+ this.$__setValue(key, 0);
836
838
  }
837
839
  return;
838
840
  }
@@ -1059,8 +1061,8 @@ Model.prototype.model = function model(name) {
1059
1061
  };
1060
1062
 
1061
1063
  /**
1062
- * Returns true if at least one document exists in the database that matches
1063
- * the given `filter`, and false otherwise.
1064
+ * Returns a document with `_id` only if at least one document exists in the database that matches
1065
+ * the given `filter`, and `null` otherwise.
1064
1066
  *
1065
1067
  * Under the hood, `MyModel.exists({ answer: 42 })` is equivalent to
1066
1068
  * `MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean()`
@@ -1097,10 +1099,6 @@ Model.exists = function exists(filter, options, callback) {
1097
1099
  if (typeof callback === 'function') {
1098
1100
  return query.exec(callback);
1099
1101
  }
1100
- options = options || {};
1101
- if (!options.explain) {
1102
- return query.then(doc => !!doc);
1103
- }
1104
1102
 
1105
1103
  return query;
1106
1104
  };
@@ -1136,11 +1134,13 @@ Model.exists = function exists(filter, options, callback) {
1136
1134
  * @param {Object|String} [options] If string, same as `options.value`.
1137
1135
  * @param {String} [options.value] the string stored in the `discriminatorKey` property. If not specified, Mongoose uses the `name` parameter.
1138
1136
  * @param {Boolean} [options.clone=true] By default, `discriminator()` clones the given `schema`. Set to `false` to skip cloning.
1137
+ * @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.
1139
1138
  * @return {Model} The newly created discriminator model
1140
1139
  * @api public
1141
1140
  */
1142
1141
 
1143
1142
  Model.discriminator = function(name, schema, options) {
1143
+
1144
1144
  let model;
1145
1145
  if (typeof name === 'function') {
1146
1146
  model = name;
@@ -1164,7 +1164,7 @@ Model.discriminator = function(name, schema, options) {
1164
1164
  }
1165
1165
 
1166
1166
  schema = discriminator(this, name, schema, value, true);
1167
- if (this.db.models[name]) {
1167
+ if (this.db.models[name] && !schema.options.overwriteModels) {
1168
1168
  throw new OverwriteModelError(name);
1169
1169
  }
1170
1170
 
@@ -3069,10 +3069,10 @@ Model.create = function create(doc, options, callback) {
3069
3069
  options = {};
3070
3070
  // Handle falsy callbacks re: #5061
3071
3071
  if (typeof last === 'function' || (arguments.length > 1 && !last)) {
3072
- cb = last;
3073
- args = utils.args(arguments, 0, arguments.length - 1);
3072
+ args = [...arguments];
3073
+ cb = args.pop();
3074
3074
  } else {
3075
- args = utils.args(arguments);
3075
+ args = [...arguments];
3076
3076
  }
3077
3077
 
3078
3078
  if (args.length === 2 &&
@@ -3609,16 +3609,21 @@ Model.bulkWrite = function(ops, options, callback) {
3609
3609
  * `bulkSave` uses `bulkWrite` under the hood, so it's mostly useful when dealing with many documents (10K+)
3610
3610
  *
3611
3611
  * @param {[Document]} documents
3612
+ * @param {Object} [options] options passed to the underlying `bulkWrite()`
3613
+ * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](/docs/transactions.html).
3614
+ * @param {String|number} [options.w=1] The [write concern](https://docs.mongodb.com/manual/reference/write-concern/). See [`Query#w()`](/docs/api.html#query_Query-w) for more information.
3615
+ * @param {number} [options.wtimeout=null] The [write concern timeout](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout).
3616
+ * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://docs.mongodb.com/manual/reference/write-concern/#j-option)
3612
3617
  *
3613
3618
  */
3614
- Model.bulkSave = function(documents) {
3619
+ Model.bulkSave = function(documents, options) {
3615
3620
  const preSavePromises = documents.map(buildPreSavePromise);
3616
3621
 
3617
3622
  const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true });
3618
3623
 
3619
3624
  let bulkWriteResultPromise;
3620
3625
  return Promise.all(preSavePromises)
3621
- .then(() => bulkWriteResultPromise = this.bulkWrite(writeOperations))
3626
+ .then(() => bulkWriteResultPromise = this.bulkWrite(writeOperations, options))
3622
3627
  .then(() => documents.map(buildSuccessfulWriteHandlerPromise))
3623
3628
  .then(() => bulkWriteResultPromise)
3624
3629
  .catch((err) => {