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/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
  }
@@ -1183,13 +1219,15 @@ Schema.prototype.plugin = function(fn, opts) {
1183
1219
  * @api public
1184
1220
  */
1185
1221
 
1186
- Schema.prototype.method = function(name, fn) {
1222
+ Schema.prototype.method = function(name, fn, options) {
1187
1223
  if (typeof name !== 'string') {
1188
- for (var i in name) {
1224
+ for (const i in name) {
1189
1225
  this.methods[i] = name[i];
1226
+ this.methodOptions[i] = utils.clone(options);
1190
1227
  }
1191
1228
  } else {
1192
1229
  this.methods[name] = fn;
1230
+ this.methodOptions[name] = utils.clone(options);
1193
1231
  }
1194
1232
  return this;
1195
1233
  };
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 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
  }
@@ -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
+ }
@@ -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');
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ /*!
4
+ * ignore
5
+ */
6
+
7
+ class MongooseMap extends Map {
8
+ constructor(v, path, doc, schemaType) {
9
+ if (v != null && v.constructor.name === 'Object') {
10
+ v = Object.keys(v).reduce((arr, key) => arr.concat([[key, v[key]]]), []);
11
+ }
12
+ super(v);
13
+
14
+ this.$__parent = doc;
15
+ this.$__path = path;
16
+ this.$__schemaType = schemaType;
17
+
18
+ this.$__runDeferred();
19
+ }
20
+
21
+ $init(key, value) {
22
+ if (key.startsWith('$')) {
23
+ throw new Error('Mongoose maps do not support keys that start with ' +
24
+ '`$`, got "' + key + '"');
25
+ }
26
+ if (key.indexOf('.') !== -1) {
27
+ throw new Error('Mongoose maps do not support keys that contain `.`, ' +
28
+ 'got "' + key + '"');
29
+ }
30
+
31
+ super.set(key, value);
32
+
33
+ if (value != null && value.$isSingleNested) {
34
+ value.$basePath = this.$__path + '.' + key;
35
+ }
36
+ }
37
+
38
+ set(key, value) {
39
+ if (key.startsWith('$')) {
40
+ throw new Error('Mongoose maps do not support keys that start with ' +
41
+ '`$`, got "' + key + '"');
42
+ }
43
+ if (key.indexOf('.') !== -1) {
44
+ throw new Error('Mongoose maps do not support keys that contain `.`, ' +
45
+ 'got "' + key + '"');
46
+ }
47
+
48
+ // Weird, but because you can't assign to `this` before calling `super()`
49
+ // you can't get access to `$__schemaType` to cast in the initial call to
50
+ // `set()` from the `super()` constructor.
51
+
52
+ if (this.$__schemaType == null) {
53
+ this.$__deferred = this.$__deferred || [];
54
+ this.$__deferred.push({ key: key, value: value });
55
+ return;
56
+ }
57
+
58
+ const fullPath = this.$__path + '.' + key;
59
+ const populated = this.$__parent != null && this.$__parent.$__ ?
60
+ this.$__parent.populated(fullPath) || this.$__parent.populated(this.$__path) :
61
+ null;
62
+
63
+ if (populated != null) {
64
+ if (value.$__ == null) {
65
+ value = new populated.options.model(value);
66
+ }
67
+ value.$__.wasPopulated = true;
68
+ } else {
69
+ try {
70
+ value = this.$__schemaType.
71
+ applySetters(value, this.$__parent, false, this.get(key));
72
+ } catch (error) {
73
+ this.$__parent.invalidate(fullPath, error);
74
+ return;
75
+ }
76
+ }
77
+
78
+ super.set(key, value);
79
+
80
+ if (value != null && value.$isSingleNested) {
81
+ value.$basePath = this.$__path + '.' + key;
82
+ }
83
+
84
+ if (this.$__parent != null && this.$__parent.$__) {
85
+ this.$__parent.markModified(this.$__path + '.' + key);
86
+ }
87
+ }
88
+
89
+ toBSON() {
90
+ return new Map(this);
91
+ }
92
+
93
+ toJSON() {
94
+ return new Map(this);
95
+ }
96
+
97
+ inspect() {
98
+ return new Map(this);
99
+ }
100
+
101
+ $__runDeferred() {
102
+ if (!this.$__deferred) {
103
+ return;
104
+ }
105
+ for (let i = 0; i < this.$__deferred.length; ++i) {
106
+ this.set(this.$__deferred[i].key, this.$__deferred[i].value);
107
+ }
108
+ this.$__deferred = null;
109
+ }
110
+ }
111
+
112
+ Object.defineProperty(MongooseMap.prototype, '$__parent', {
113
+ enumerable: false,
114
+ writable: true,
115
+ configurable: false
116
+ });
117
+
118
+ Object.defineProperty(MongooseMap.prototype, '$__path', {
119
+ enumerable: false,
120
+ writable: true,
121
+ configurable: false
122
+ });
123
+
124
+ Object.defineProperty(MongooseMap.prototype, '$__schemaType', {
125
+ enumerable: false,
126
+ writable: true,
127
+ configurable: false
128
+ });
129
+
130
+ Object.defineProperty(MongooseMap.prototype, '$isMongooseMap', {
131
+ enumerable: false,
132
+ writable: false,
133
+ configurable: false,
134
+ value: true
135
+ });
136
+
137
+ Object.defineProperty(MongooseMap.prototype, '$__deferredCalls', {
138
+ enumerable: false,
139
+ writable: false,
140
+ configurable: false,
141
+ value: true
142
+ });
143
+
144
+ module.exports = MongooseMap;
@@ -10,4 +10,16 @@
10
10
 
11
11
  var ObjectId = require('../drivers').ObjectId;
12
12
 
13
+ /*!
14
+ * Getter for convenience with populate, see gh-6115
15
+ */
16
+
17
+ Object.defineProperty(ObjectId.prototype, '_id', {
18
+ enumerable: false,
19
+ configurable: true,
20
+ get: function() {
21
+ return this;
22
+ }
23
+ });
24
+
13
25
  module.exports = ObjectId;
@@ -36,7 +36,20 @@ Subdocument.prototype.toBSON = function() {
36
36
  * @api private
37
37
  */
38
38
 
39
- Subdocument.prototype.save = function(fn) {
39
+ Subdocument.prototype.save = function(options, fn) {
40
+ if (typeof options === 'function') {
41
+ fn = options;
42
+ options = {};
43
+ }
44
+ options = options || {};
45
+
46
+ if (!options.suppressWarning) {
47
+ console.warn('mongoose: calling `save()` on a subdoc does **not** save ' +
48
+ 'the document to MongoDB, it only runs save middleware. ' +
49
+ 'Use `subdoc.save({ suppressWarning: true })` to hide this warning ' +
50
+ 'if you\'re sure this behavior is right for your app.');
51
+ }
52
+
40
53
  return utils.promiseOrCallback(fn, cb => {
41
54
  this.$__save(cb);
42
55
  });
@@ -67,6 +80,7 @@ Subdocument.prototype.$isValid = function(path) {
67
80
 
68
81
  Subdocument.prototype.markModified = function(path) {
69
82
  Document.prototype.markModified.call(this, path);
83
+
70
84
  if (this.$parent && this.$basePath) {
71
85
  if (this.$parent.isDirectModified(this.$basePath)) {
72
86
  return;
@@ -82,6 +96,10 @@ Subdocument.prototype.$markValid = function(path) {
82
96
  }
83
97
  };
84
98
 
99
+ /*!
100
+ * ignore
101
+ */
102
+
85
103
  Subdocument.prototype.invalidate = function(path, err, val) {
86
104
  // Hack: array subdocuments' validationError is equal to the owner doc's,
87
105
  // so validating an array subdoc gives the top-level doc back. Temporary
@@ -97,6 +115,17 @@ Subdocument.prototype.invalidate = function(path, err, val) {
97
115
  }
98
116
  };
99
117
 
118
+ /*!
119
+ * ignore
120
+ */
121
+
122
+ Subdocument.prototype.$ignore = function(path) {
123
+ Document.prototype.$ignore.call(this, path);
124
+ if (this.$parent && this.$basePath) {
125
+ this.$parent.$ignore([this.$basePath, path].join('.'));
126
+ }
127
+ };
128
+
100
129
  /**
101
130
  * Returns the top level document of this sub-document.
102
131
  *
package/lib/utils.js CHANGED
@@ -446,9 +446,11 @@ exports.isMongooseObject = function(v) {
446
446
  MongooseArray || (MongooseArray = require('./types').Array);
447
447
  MongooseBuffer || (MongooseBuffer = require('./types').Buffer);
448
448
 
449
- return v instanceof Document ||
450
- (v && v.isMongooseArray) ||
451
- (v && v.isMongooseBuffer);
449
+ if (v == null) {
450
+ return false;
451
+ }
452
+
453
+ return v.$__ != null || v.isMongooseArray || v.isMongooseBuffer;
452
454
  };
453
455
  var isMongooseObject = exports.isMongooseObject;
454
456
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "5.0.18",
4
+ "version": "5.1.0",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",