mongoose 5.0.17 → 5.1.2

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.
@@ -232,9 +232,12 @@ SchemaDate.prototype.cast = function(value) {
232
232
 
233
233
  if (value instanceof Number || typeof value === 'number') {
234
234
  date = new Date(value);
235
+ } else if (typeof value === 'string' && !isNaN(Number(value)) && (Number(value) >= 275761 || Number(value) < -271820)) {
236
+ // string representation of milliseconds take this path
237
+ date = new Date(Number(value));
235
238
  } else if (typeof value.valueOf === 'function') {
236
- // support for moment.js. This is also the path strings will take because strings
237
- // have a `valueOf()`
239
+ // support for moment.js. This is also the path strings will take because
240
+ // strings have a `valueOf()`
238
241
  date = new Date(value.valueOf());
239
242
  } else {
240
243
  // fallback
@@ -128,6 +128,10 @@ Decimal128.prototype.cast = function(value, doc, init) {
128
128
  return Decimal128Type.fromString(String(value));
129
129
  }
130
130
 
131
+ if (typeof value.valueOf === 'function' && typeof value.valueOf() === 'string') {
132
+ return Decimal128Type.fromString(value.valueOf());
133
+ }
134
+
131
135
  throw new CastError('Decimal128', value, this.path);
132
136
  };
133
137
 
@@ -219,7 +219,7 @@ Embedded.prototype.castForQuery = function($conditional, val) {
219
219
  * @api private
220
220
  */
221
221
 
222
- Embedded.prototype.doValidate = function(value, fn, scope) {
222
+ Embedded.prototype.doValidate = function(value, fn, scope, options) {
223
223
  var Constructor = this.caster;
224
224
  var discriminatorKey = Constructor.schema.options.discriminatorKey;
225
225
  if (value != null &&
@@ -235,6 +235,13 @@ Embedded.prototype.doValidate = function(value, fn, scope) {
235
235
  }
236
236
  }
237
237
 
238
+ if (options && options.skipSchemaValidators) {
239
+ if (!(value instanceof Constructor)) {
240
+ value = new Constructor(value, null, scope);
241
+ }
242
+
243
+ return value.validate(fn);
244
+ }
238
245
 
239
246
  SchemaType.prototype.doValidate.call(this, value, function(error) {
240
247
  if (error) {
@@ -244,10 +251,7 @@ Embedded.prototype.doValidate = function(value, fn, scope) {
244
251
  return fn(null);
245
252
  }
246
253
 
247
- if (!(value instanceof Constructor)) {
248
- value = new Constructor(value);
249
- }
250
- value.validate({__noPromise: true}, fn);
254
+ value.validate(fn);
251
255
  }, scope);
252
256
  };
253
257
 
@@ -257,10 +261,12 @@ Embedded.prototype.doValidate = function(value, fn, scope) {
257
261
  * @api private
258
262
  */
259
263
 
260
- Embedded.prototype.doValidateSync = function(value, scope) {
261
- var schemaTypeError = SchemaType.prototype.doValidateSync.call(this, value, scope);
262
- if (schemaTypeError) {
263
- return schemaTypeError;
264
+ Embedded.prototype.doValidateSync = function(value, scope, options) {
265
+ if (!options || !options.skipSchemaValidators) {
266
+ var schemaTypeError = SchemaType.prototype.doValidateSync.call(this, value, scope);
267
+ if (schemaTypeError) {
268
+ return schemaTypeError;
269
+ }
264
270
  }
265
271
  if (!value) {
266
272
  return;
@@ -25,6 +25,8 @@ exports.Mixed = require('./mixed');
25
25
 
26
26
  exports.Decimal128 = exports.Decimal = require('./decimal128');
27
27
 
28
+ exports.Map = require('./map');
29
+
28
30
  // alias
29
31
 
30
32
  exports.Oid = exports.ObjectId;
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+
3
+ /*!
4
+ * ignore
5
+ */
6
+
7
+ const MongooseMap = require('../types/map');
8
+ const SchemaType = require('../schematype');
9
+
10
+ /*!
11
+ * ignore
12
+ */
13
+
14
+ class SchemaMap extends SchemaType {
15
+ constructor(key, options) {
16
+ super(key, options, 'Map');
17
+ this.$isSchemaMap = true;
18
+ }
19
+
20
+ cast(val, doc) {
21
+ if (val instanceof MongooseMap) {
22
+ return val;
23
+ }
24
+
25
+ return new MongooseMap(val, this.path, doc, this.$__schemaType);
26
+ }
27
+ }
28
+
29
+ module.exports = SchemaMap;
@@ -178,26 +178,6 @@ ObjectId.prototype.$conditionalHandlers =
178
178
  $lte: handleSingle
179
179
  });
180
180
 
181
- /**
182
- * Casts contents for queries.
183
- *
184
- * @param {String} $conditional
185
- * @param {any} [val]
186
- * @api private
187
- */
188
-
189
- ObjectId.prototype.castForQuery = function($conditional, val) {
190
- var handler;
191
- if (arguments.length === 2) {
192
- handler = this.$conditionalHandlers[$conditional];
193
- if (!handler) {
194
- throw new Error('Can\'t use ' + $conditional + ' with ObjectId.');
195
- }
196
- return handler.call(this, val);
197
- }
198
- return this._castForQuery($conditional);
199
- };
200
-
201
181
  /*!
202
182
  * ignore
203
183
  */
package/lib/schema.js CHANGED
@@ -1,3 +1,5 @@
1
+ 'use strict';
2
+
1
3
  /*!
2
4
  * Module dependencies.
3
5
  */
@@ -72,6 +74,7 @@ function Schema(obj, options) {
72
74
  this.callQueue = [];
73
75
  this._indexes = [];
74
76
  this.methods = {};
77
+ this.methodOptions = {};
75
78
  this.statics = {};
76
79
  this.tree = {};
77
80
  this.query = {};
@@ -239,6 +242,7 @@ Schema.prototype.clone = function() {
239
242
  s.options = utils.clone(this.options);
240
243
  s.callQueue = this.callQueue.map(function(f) { return f; });
241
244
  s.methods = utils.clone(this.methods);
245
+ s.methodOptions = utils.clone(this.methodOptions);
242
246
  s.statics = utils.clone(this.statics);
243
247
  s.query = utils.clone(this.query);
244
248
  s.plugins = Array.prototype.slice.call(this.plugins);
@@ -381,6 +385,7 @@ Schema.prototype.add = function add(obj, prefix) {
381
385
  */
382
386
 
383
387
  Schema.reserved = Object.create(null);
388
+ Schema.prototype.reserved = Schema.reserved;
384
389
  var reserved = Schema.reserved;
385
390
  // Core object
386
391
  reserved['prototype'] =
@@ -443,6 +448,17 @@ Schema.prototype.path = function(path, obj) {
443
448
  return this.singleNestedPaths[path];
444
449
  }
445
450
 
451
+ // Look for maps
452
+ for (let _path of Object.keys(this.paths)) {
453
+ if (!_path.includes('.$*')) {
454
+ continue;
455
+ }
456
+ const re = new RegExp('^' + _path.replace(/\.\$\*/g, '.[^.]+') + '$');
457
+ if (re.test(path)) {
458
+ return this.paths[_path];
459
+ }
460
+ }
461
+
446
462
  // subpaths?
447
463
  return /\.\d+\.?.*$/.test(path)
448
464
  ? getPositionalPath(this, path)
@@ -459,16 +475,16 @@ Schema.prototype.path = function(path, obj) {
459
475
  }
460
476
 
461
477
  // update the tree
462
- var subpaths = path.split(/\./),
463
- last = subpaths.pop(),
464
- branch = this.tree;
478
+ const subpaths = path.split(/\./);
479
+ let last = subpaths.pop();
480
+ let branch = this.tree;
465
481
 
466
482
  subpaths.forEach(function(sub, i) {
467
483
  if (!branch[sub]) {
468
484
  branch[sub] = {};
469
485
  }
470
486
  if (typeof branch[sub] !== 'object') {
471
- var msg = 'Cannot set nested path `' + path + '`. '
487
+ const msg = 'Cannot set nested path `' + path + '`. '
472
488
  + 'Parent path `'
473
489
  + subpaths.slice(0, i).concat([sub]).join('.')
474
490
  + '` already set to type ' + branch[sub].name
@@ -478,17 +494,26 @@ Schema.prototype.path = function(path, obj) {
478
494
  branch = branch[sub];
479
495
  });
480
496
 
481
-
482
497
  branch[last] = utils.clone(obj);
483
498
 
484
499
  this.paths[path] = Schema.interpretAsType(path, obj, this.options);
485
500
 
501
+ if (this.paths[path].$isSchemaMap) {
502
+ // Maps can have arbitrary keys, so `$*` is internal shorthand for "any key"
503
+ // The '$' is to imply this path should never be stored in MongoDB so we
504
+ // can easily build a regexp out of this path, and '*' to imply "any key."
505
+ const mapPath = path + '.$*';
506
+ this.paths[path + '.$*'] = Schema.interpretAsType(mapPath,
507
+ obj.of || { type: {} }, this.options);
508
+ this.paths[path].$__schemaType = this.paths[path + '.$*'];
509
+ }
510
+
486
511
  if (this.paths[path].$isSingleNested) {
487
- for (var key in this.paths[path].schema.paths) {
512
+ for (let key in this.paths[path].schema.paths) {
488
513
  this.singleNestedPaths[path + '.' + key] =
489
514
  this.paths[path].schema.paths[key];
490
515
  }
491
- for (key in this.paths[path].schema.singleNestedPaths) {
516
+ for (let key in this.paths[path].schema.singleNestedPaths) {
492
517
  this.singleNestedPaths[path + '.' + key] =
493
518
  this.paths[path].schema.singleNestedPaths[key];
494
519
  }
@@ -722,6 +747,17 @@ Schema.prototype.pathType = function(path) {
722
747
  return 'real';
723
748
  }
724
749
 
750
+ // Look for maps
751
+ for (let _path of Object.keys(this.paths)) {
752
+ if (!_path.includes('.$*')) {
753
+ continue;
754
+ }
755
+ const re = new RegExp('^' + _path.replace(/\.\$\*/g, '.[^.]+') + '$');
756
+ if (re.test(path)) {
757
+ return this.paths[_path];
758
+ }
759
+ }
760
+
725
761
  if (/\.\d+\.|\.\d+$/.test(path)) {
726
762
  return getPositionalPathType(this, path);
727
763
  }
@@ -1178,18 +1214,22 @@ Schema.prototype.plugin = function(fn, opts) {
1178
1214
  * fizz.purr();
1179
1215
  * fizz.scratch();
1180
1216
  *
1217
+ * NOTE: `Schema.method()` adds instance methods to the `Schema.methods` object. You can also add instance methods directly to the `Schema.methods` object as seen in the [guide](./guide.html#methods)
1218
+ *
1181
1219
  * @param {String|Object} method name
1182
1220
  * @param {Function} [fn]
1183
1221
  * @api public
1184
1222
  */
1185
1223
 
1186
- Schema.prototype.method = function(name, fn) {
1224
+ Schema.prototype.method = function(name, fn, options) {
1187
1225
  if (typeof name !== 'string') {
1188
- for (var i in name) {
1226
+ for (const i in name) {
1189
1227
  this.methods[i] = name[i];
1228
+ this.methodOptions[i] = utils.clone(options);
1190
1229
  }
1191
1230
  } else {
1192
1231
  this.methods[name] = fn;
1232
+ this.methodOptions[name] = utils.clone(options);
1193
1233
  }
1194
1234
  return this;
1195
1235
  };
package/lib/schematype.js CHANGED
@@ -1035,6 +1035,24 @@ function handleArray(val) {
1035
1035
  });
1036
1036
  }
1037
1037
 
1038
+ /*!
1039
+ * Just like handleArray, except also allows `[]` because surprisingly
1040
+ * `$in: [1, []]` works fine
1041
+ */
1042
+
1043
+ function handle$in(val) {
1044
+ var _this = this;
1045
+ if (!Array.isArray(val)) {
1046
+ return [this.castForQuery(val)];
1047
+ }
1048
+ return val.map(function(m) {
1049
+ if (Array.isArray(m) && m.length === 0) {
1050
+ return m;
1051
+ }
1052
+ return _this.castForQuery(m);
1053
+ });
1054
+ }
1055
+
1038
1056
  /*!
1039
1057
  * ignore
1040
1058
  */
@@ -1042,7 +1060,7 @@ function handleArray(val) {
1042
1060
  SchemaType.prototype.$conditionalHandlers = {
1043
1061
  $all: handleArray,
1044
1062
  $eq: handleSingle,
1045
- $in: handleArray,
1063
+ $in: handle$in,
1046
1064
  $ne: handleSingle,
1047
1065
  $nin: handleArray,
1048
1066
  $exists: $exists,
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const utils = require('../../utils');
4
+
3
5
  /*!
4
6
  * ignore
5
7
  */
@@ -54,6 +56,13 @@ function applyHooks(model, schema, options) {
54
56
 
55
57
  // Support hooks for custom methods
56
58
  const customMethods = Object.keys(schema.methods);
59
+ const customMethodOptions = Object.assign({}, kareemOptions, {
60
+ // Only use `checkForPromise` for custom methods, because mongoose
61
+ // query thunks are not as consistent as I would like about returning
62
+ // a nullish value rather than the query. If a query thunk returns
63
+ // a query, `checkForPromise` causes infinite recursion
64
+ checkForPromise: true
65
+ });
57
66
  for (const method of customMethods) {
58
67
  if (!schema.s.hooks.hasHooks(method)) {
59
68
  // Don't wrap if there are no hooks for the custom method to avoid
@@ -61,7 +70,18 @@ function applyHooks(model, schema, options) {
61
70
  // so wrapping a sync method would break it.
62
71
  continue;
63
72
  }
64
- objToDecorate[method] = schema.s.hooks.
65
- createWrapper(method, objToDecorate[method], null, kareemOptions);
73
+ const originalMethod = objToDecorate[method];
74
+ objToDecorate[method] = function() {
75
+ const args = Array.prototype.slice.call(arguments);
76
+ const cb = utils.last(args);
77
+ const argsWithoutCallback = cb == null ? args :
78
+ args.slice(0, args.length - 1);
79
+ return utils.promiseOrCallback(cb, callback => {
80
+ this[`$__${method}`].apply(this,
81
+ argsWithoutCallback.concat([callback]));
82
+ });
83
+ };
84
+ objToDecorate[`$__${method}`] = schema.s.hooks.
85
+ createWrapper(method, originalMethod, null, customMethodOptions);
66
86
  }
67
87
  }
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const get = require('lodash.get');
4
+
3
5
  /*!
4
6
  * Register methods for this model
5
7
  *
@@ -20,13 +22,21 @@ module.exports = function applyMethods(model, schema) {
20
22
  configurable: true
21
23
  });
22
24
  }
23
- for (var method in schema.methods) {
25
+ for (const method of Object.keys(schema.methods)) {
26
+ const fn = schema.methods[method];
24
27
  if (schema.tree.hasOwnProperty(method)) {
25
28
  throw new Error('You have a method and a property in your schema both ' +
26
29
  'named "' + method + '"');
27
30
  }
28
- if (typeof schema.methods[method] === 'function') {
29
- model.prototype[method] = schema.methods[method];
31
+ if (schema.reserved[method] &&
32
+ !get(schema, `methodOptions.${method}.suppressWarning`, false)) {
33
+ console.warn(`mongoose: the method name "${method}" is used by mongoose ` +
34
+ 'internally, overwriting it may cause bugs. If you\'re sure you know ' +
35
+ 'what you\'re doing, you can suppress this error by using ' +
36
+ `\`schema.method('${method}', fn, { suppressWarning: true })\`.`);
37
+ }
38
+ if (typeof fn === 'function') {
39
+ model.prototype[method] = fn;
30
40
  } else {
31
41
  apply(method, schema);
32
42
  }
@@ -34,7 +44,7 @@ module.exports = function applyMethods(model, schema) {
34
44
 
35
45
  // Recursively call `applyMethods()` on child schemas
36
46
  model.$appliedMethods = true;
37
- for (var i = 0; i < schema.childSchemas.length; ++i) {
47
+ for (let i = 0; i < schema.childSchemas.length; ++i) {
38
48
  if (schema.childSchemas[i].model.$appliedMethods) {
39
49
  continue;
40
50
  }
@@ -23,6 +23,10 @@ function getVirtual(schema, name) {
23
23
  continue;
24
24
  }
25
25
 
26
+ if (schema.nested[cur]) {
27
+ continue;
28
+ }
29
+
26
30
  if (schema.paths[cur] && schema.paths[cur].schema) {
27
31
  schema = schema.paths[cur].schema;
28
32
 
@@ -205,7 +205,7 @@ function walkUpdatePath(schema, obj, op, options, context, pref) {
205
205
  (utils.isObject(val) && Object.keys(val).length === 0);
206
206
  }
207
207
  } else {
208
- var checkPath = (key === '$each' || key === '$or' || key === '$and') ?
208
+ var checkPath = (key === '$each' || key === '$or' || key === '$and' || key === '$in') ?
209
209
  pref : prefix + key;
210
210
  schematype = schema._getSchema(checkPath);
211
211
 
@@ -0,0 +1,47 @@
1
+ 'use strict';
2
+
3
+ const helpers = require('../../queryhelpers');
4
+
5
+ module.exports = completeMany;
6
+
7
+ /*!
8
+ * Given a model and an array of docs, hydrates all the docs to be instances
9
+ * of the model. Used to initialize docs returned from the db from `find()`
10
+ *
11
+ * @param {Model} model
12
+ * @param {Array} docs
13
+ * @param {Object} fields the projection used, including `select` from schemas
14
+ * @param {Object} userProvidedFields the user-specified projection
15
+ * @param {Object} opts
16
+ * @param {Array} [opts.populated]
17
+ * @param {ClientSession} [opts.session]
18
+ * @param {Function} callback
19
+ */
20
+
21
+ function completeMany(model, docs, fields, userProvidedFields, opts, callback) {
22
+ const arr = [];
23
+ let count = docs.length;
24
+ const len = count;
25
+ let error = null;
26
+
27
+ function init(_error) {
28
+ if (_error != null) {
29
+ error = error || _error;
30
+ }
31
+ if (error != null) {
32
+ --count || process.nextTick(() => callback(error));
33
+ return;
34
+ }
35
+ --count || process.nextTick(() => callback(error, arr));
36
+ }
37
+
38
+ for (let i = 0; i < len; ++i) {
39
+ arr[i] = helpers.createModel(model, docs[i], fields, userProvidedFields);
40
+ try {
41
+ arr[i].init(docs[i], opts, init);
42
+ } catch (error) {
43
+ init(error);
44
+ }
45
+ arr[i].$session(opts.session);
46
+ }
47
+ }
@@ -167,7 +167,7 @@ MongooseBuffer.mixin = {
167
167
  'writeFloat writeDouble fill ' +
168
168
  'utf8Write binaryWrite asciiWrite set ' +
169
169
 
170
- // node >= 0.5
170
+ // node >= 0.5
171
171
  'writeUInt16LE writeUInt16BE writeUInt32LE writeUInt32BE ' +
172
172
  'writeInt16LE writeInt16BE writeInt32LE writeInt32BE ' +
173
173
  'writeFloatLE writeFloatBE writeDoubleLE writeDoubleBE'
@@ -12,6 +12,18 @@ const utils = require('../utils');
12
12
  const Document = require('../document');
13
13
  const getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByValue;
14
14
 
15
+ /*!
16
+ * ignore
17
+ */
18
+
19
+ class CoreMongooseArray extends Array {
20
+ get isMongooseArray() {
21
+ return true;
22
+ }
23
+
24
+ remove() {}
25
+ }
26
+
15
27
  /**
16
28
  * DocumentArray constructor
17
29
  *
@@ -25,11 +37,13 @@ const getDiscriminatorByValue = require('../queryhelpers').getDiscriminatorByVal
25
37
  */
26
38
 
27
39
  function MongooseDocumentArray(values, path, doc) {
28
- var arr = [].concat(values);
40
+ // TODO: replace this with `new CoreMongooseArray().concat()` when we remove
41
+ // support for node 4.x and 5.x, see https://i.imgur.com/UAAHk4S.png
42
+ var arr = new CoreMongooseArray();
43
+ if (Array.isArray(values)) values.forEach(v => arr.push(v));
29
44
  arr._path = path;
30
45
 
31
46
  var props = {
32
- isMongooseArray: true,
33
47
  isMongooseDocumentArray: true,
34
48
  validators: [],
35
49
  _atomics: {},
@@ -107,7 +107,20 @@ EmbeddedDocument.prototype.populate = function() {
107
107
  * @api private
108
108
  */
109
109
 
110
- EmbeddedDocument.prototype.save = function(fn) {
110
+ EmbeddedDocument.prototype.save = function(options, fn) {
111
+ if (typeof options === 'function') {
112
+ fn = options;
113
+ options = {};
114
+ }
115
+ options = options || {};
116
+
117
+ if (!options.suppressWarning) {
118
+ console.warn('mongoose: calling `save()` on a subdoc does **not** save ' +
119
+ 'the document to MongoDB, it only runs save middleware. ' +
120
+ 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' +
121
+ 'if you\'re sure this behavior is right for your app.');
122
+ }
123
+
111
124
  return utils.promiseOrCallback(fn, cb => {
112
125
  this.$__save(cb);
113
126
  });
@@ -274,6 +287,25 @@ EmbeddedDocument.prototype.$markValid = function(path) {
274
287
  }
275
288
  };
276
289
 
290
+ /*!
291
+ * ignore
292
+ */
293
+
294
+ EmbeddedDocument.prototype.$ignore = function(path) {
295
+ Document.prototype.$ignore.call(this, path);
296
+
297
+ if (!this.__parent) {
298
+ return;
299
+ }
300
+
301
+ var index = this.__index;
302
+ if (typeof index !== 'undefined') {
303
+ var parentPath = this.__parentArray._path;
304
+ var fullPath = [parentPath, index, path].join('.');
305
+ this.__parent.$ignore(fullPath);
306
+ }
307
+ };
308
+
277
309
  /**
278
310
  * Checks if a path is invalid
279
311
  *
@@ -13,4 +13,6 @@ exports.DocumentArray = require('./documentarray');
13
13
  exports.Decimal128 = require('./decimal128');
14
14
  exports.ObjectId = require('./objectid');
15
15
 
16
+ exports.Map = require('./map');
17
+
16
18
  exports.Subdocument = require('./subdocument');