mongoose 5.5.0 → 5.5.4

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,45 @@
1
+ 5.5.4 / 2019-04-25
2
+ ==================
3
+ * fix(document): avoid calling custom getters when saving #7719
4
+ * fix(timestamps): handle child schema timestamps correctly when reusing child schemas #7712
5
+ * fix(query): pass correct callback for _legacyFindAndModify #7736 [Fonger](https://github.com/Fonger)
6
+ * fix(model+query): allow setting `replacement` parameter for `findOneAndReplace()` #7654
7
+ * fix(map): make `delete()` unset the key in the database #7746 [Fonger](https://github.com/Fonger)
8
+ * fix(array): use symbol for `_schema` property to avoid confusing deep equality checks #7700
9
+ * fix(document): prevent `depopulate()` from removing fields with empty array #7741 #7740 [Fonger](https://github.com/Fonger)
10
+ * fix: make `MongooseArray#includes` support ObjectIds #7732 #6354 [Fonger](https://github.com/Fonger)
11
+ * fix(document): report correct validation error index when pushing onto doc array #7744 [Fonger](https://github.com/Fonger)
12
+
13
+ 5.5.3 / 2019-04-22
14
+ ==================
15
+ * fix: add findAndModify deprecation warning that references the useFindAndModify option #7644
16
+ * fix(document): handle pushing a doc onto a discriminator that contains a doc array #7704
17
+ * fix(update): run setters on array elements when doing $set #7679
18
+ * fix: correct usage of arguments while buffering commands #7718 [rzymek](https://github.com/rzymek)
19
+ * fix(document): avoid error clearing modified subpaths if doc not defined #7715 [bitflower](https://github.com/bitflower)
20
+ * refactor(array): move `_parent` property behind a symbol #7726 #7700
21
+ * docs(model): list out all operations and options for `bulkWrite()` #7055
22
+ * docs(aggregate): use `eachAsync()` instead of nonexistent `each()` #7699
23
+ * docs(validation): add CastError validation example #7514
24
+ * docs(query+model): list out all options and callback details for Model.updateX() and Query#updateX() #7646
25
+
26
+ 5.5.2 / 2019-04-16
27
+ ==================
28
+ * fix(document): support setting nested path to non-POJO object #7639
29
+ * perf(connection): remove leaked event handler in `Model.init()` so `deleteModel()` frees all memory #7682
30
+ * fix(timestamps): handle custom statics that conflict with built-in functions (like mongoose-delete plugin) #7698
31
+ * fix(populate): make `Document#populated()` work for populated subdocs #7685
32
+ * fix(document): support `.set()` on document array underneath embedded discriminator path #7656
33
+
34
+ 5.5.1 / 2019-04-11
35
+ ==================
36
+ * fix(document): correctly overwrite all properties when setting a single nested subdoc #7660 #7681
37
+ * fix(array): allow customization of array required validator #7696 [freewil](https://github.com/freewil)
38
+ * fix(discriminator): handle embedded discriminators when casting array defaults #7687
39
+ * fix(collection): ensure collection functions return a promise even if disconnected #7676
40
+ * fix(schematype): avoid indexing properties with `{ unique: false, index: false }` #7620
41
+ * fix(aggregate): make `Aggregate#model()` with no arguments return the aggregation's model #7608
42
+
1
43
  5.5.0 / 2019-04-08
2
44
  ==================
3
45
  * feat(model): support applying hooks to custom static functions #5982
@@ -9,7 +51,7 @@
9
51
  * feat(model): print warning when calling create() incorrectly with a session #7535
10
52
  * feat(document): add Document#isEmpty() and corresponding helpers for nested paths #5369
11
53
  * feat(document): add `getters` option to Document#get() #7233
12
- feat(query): add Query#projection() to get or overwrite the current projection #7384
54
+ * feat(query): add Query#projection() to get or overwrite the current projection #7384
13
55
  * fix(document): set full validator path on validatorProperties if `propsParameter` set on validator #7447
14
56
  * feat(document): add Document#directModifiedPaths() #7373
15
57
  * feat(document): add $locals property #7691
package/lib/aggregate.js CHANGED
@@ -80,14 +80,26 @@ function Aggregate(pipeline) {
80
80
  Aggregate.prototype.options;
81
81
 
82
82
  /**
83
- * Binds this aggregate to a model.
83
+ * Get/set the model that this aggregation will execute on.
84
84
  *
85
- * @param {Model} model the model to which the aggregate is to be bound
86
- * @return {Aggregate}
85
+ * ####Example:
86
+ * const aggregate = MyModel.aggregate([{ $match: { answer: 42 } }]);
87
+ * aggregate.model() === MyModel; // true
88
+ *
89
+ * // Change the model. There's rarely any reason to do this.
90
+ * aggregate.model(SomeOtherModel);
91
+ * aggregate.model() === SomeOtherModel; // true
92
+ *
93
+ * @param {Model} [model] the model to which the aggregate is to be bound
94
+ * @return {Aggregate|Model} if model is passed, will return `this`, otherwise will return the model
87
95
  * @api public
88
96
  */
89
97
 
90
98
  Aggregate.prototype.model = function(model) {
99
+ if (arguments.length === 0) {
100
+ return this._model;
101
+ }
102
+
91
103
  this._model = model;
92
104
  if (model.schema != null) {
93
105
  if (this.options.readPreference == null &&
@@ -790,7 +802,7 @@ Aggregate.prototype.option = function(value) {
790
802
  * ####Example:
791
803
  *
792
804
  * var cursor = Model.aggregate(..).cursor({ batchSize: 1000 }).exec();
793
- * cursor.each(function(error, doc) {
805
+ * cursor.eachAsync(function(error, doc) {
794
806
  * // use doc
795
807
  * });
796
808
  *
package/lib/connection.js CHANGED
@@ -361,7 +361,13 @@ Connection.prototype.dropCollection = _wrapConnHelper(function dropCollection(co
361
361
  */
362
362
 
363
363
  Connection.prototype.dropDatabase = _wrapConnHelper(function dropDatabase(cb) {
364
- this.$internalEmitter.emit('dropDatabase');
364
+ // If `dropDatabase()` is called, this model's collection will not be
365
+ // init-ed. It is sufficiently common to call `dropDatabase()` after
366
+ // `mongoose.connect()` but before creating models that we want to
367
+ // support this. See gh-6967
368
+ for (const name of Object.keys(this.models)) {
369
+ delete this.models[name].$init;
370
+ }
365
371
  this.db.dropDatabase(cb);
366
372
  });
367
373
 
package/lib/document.js CHANGED
@@ -100,13 +100,15 @@ function Document(obj, fields, skipId, options) {
100
100
  $__hasIncludedChildren(fields) :
101
101
  {};
102
102
 
103
- this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false);
103
+ if (this._doc == null) {
104
+ this.$__buildDoc(obj, fields, skipId, exclude, hasIncludedChildren, false);
104
105
 
105
- // By default, defaults get applied **before** setting initial values
106
- // Re: gh-6155
107
- $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, {
108
- isNew: this.isNew
109
- });
106
+ // By default, defaults get applied **before** setting initial values
107
+ // Re: gh-6155
108
+ $__applyDefaults(this, fields, skipId, exclude, hasIncludedChildren, true, {
109
+ isNew: this.isNew
110
+ });
111
+ }
110
112
 
111
113
  if (obj) {
112
114
  if (obj instanceof Document) {
@@ -788,6 +790,8 @@ Document.prototype.$set = function $set(path, val, type, options) {
788
790
 
789
791
  return this;
790
792
  }
793
+ } else {
794
+ this.$__.$setCalled.add(path);
791
795
  }
792
796
 
793
797
  function _handleIndex(i) {
@@ -806,7 +810,8 @@ Document.prototype.$set = function $set(path, val, type, options) {
806
810
  delete this._doc[key];
807
811
  }
808
812
 
809
- if (utils.isPOJO(path[key]) &&
813
+ if (typeof path[key] === 'object' &&
814
+ path[key] != null &&
810
815
  pathtype !== 'virtual' &&
811
816
  pathtype !== 'real' &&
812
817
  !(this.$__path(pathName) instanceof MixedSchema) &&
@@ -849,7 +854,10 @@ Document.prototype.$set = function $set(path, val, type, options) {
849
854
  }
850
855
  }
851
856
 
852
- const pathType = this.schema.pathType(path);
857
+ let pathType = this.schema.pathType(path);
858
+ if (pathType === 'adhocOrUndefined') {
859
+ pathType = getEmbeddedDiscriminatorPath(this, path, { typeOnly: true });
860
+ }
853
861
 
854
862
  // Assume this is a Mongoose document that was copied into a POJO using
855
863
  // `Object.assign()` or `{...doc}`
@@ -858,7 +866,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
858
866
  }
859
867
 
860
868
  if (pathType === 'nested' && val) {
861
- if (utils.isPOJO(val)) {
869
+ if (typeof val === 'object' && val != null) {
862
870
  if (!merge) {
863
871
  this.setValue(path, null);
864
872
  cleanModifiedSubpaths(this, path);
@@ -1056,25 +1064,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1056
1064
  // a single nested doc. That's to make sure we get the correct context.
1057
1065
  // Otherwise we would double-call the setter, see gh-7196.
1058
1066
  if (this.schema.singleNestedPaths[path] == null) {
1059
- // Init the new subdoc to the previous values of the doc, so
1060
- // getters/setters see the correct current state. We pass the new subdoc
1061
- // instead of the old subdoc because otherwise side effects in setters
1062
- // wouldn't work, see gh-7585
1063
- if (constructing && this.$__.$options.priorDoc) {
1064
- const priorVal = Object.assign({}, this.$__.$options.priorDoc._doc);
1065
- delete priorVal[this.schema.options.discriminatorKey];
1066
- init(this, priorVal, this._doc);
1067
- }
1068
-
1069
1067
  val = schema.applySetters(val, this, false, priorVal);
1070
-
1071
- if (constructing && this.$__.$options.priorDoc) {
1072
- // Clear init-ed paths afterwards, because those should be paths that
1073
- // were in the previous doc and should not be in the new one.
1074
- for (const path of Object.keys(this.$__.activePaths.states.init)) {
1075
- delete this._doc[path];
1076
- }
1077
- }
1078
1068
  }
1079
1069
 
1080
1070
  if (!didPopulate && this.$__.populated) {
@@ -1495,6 +1485,8 @@ Document.prototype.directModifiedPaths = function() {
1495
1485
  * doc.$isEmpty('nested'); // false
1496
1486
  * doc.nested.$isEmpty(); // false
1497
1487
  *
1488
+ * @memberOf Document
1489
+ * @instance
1498
1490
  * @api public
1499
1491
  * @method $isEmpty
1500
1492
  * @return {Boolean}
@@ -2555,22 +2547,22 @@ Document.prototype.$__getAllSubdocs = function() {
2555
2547
  Embedded = Embedded || require('./types/embedded');
2556
2548
 
2557
2549
  function docReducer(doc, seed, path) {
2558
- const val = path ? doc[path] : doc;
2550
+ let val = doc;
2551
+ if (path) {
2552
+ val = doc instanceof Document ? doc._doc[path] : doc[path];
2553
+ }
2559
2554
  if (val instanceof Embedded) {
2560
2555
  seed.push(val);
2561
- }
2562
- else if (val instanceof Map) {
2556
+ } else if (val instanceof Map) {
2563
2557
  seed = Array.from(val.keys()).reduce(function(seed, path) {
2564
2558
  return docReducer(val.get(path), seed, null);
2565
2559
  }, seed);
2566
- }
2567
- else if (val && val.$isSingleNested) {
2560
+ } else if (val && val.$isSingleNested) {
2568
2561
  seed = Object.keys(val._doc).reduce(function(seed, path) {
2569
2562
  return docReducer(val._doc, seed, path);
2570
2563
  }, seed);
2571
2564
  seed.push(val);
2572
- }
2573
- else if (val && val.isMongooseDocumentArray) {
2565
+ } else if (val && val.isMongooseDocumentArray) {
2574
2566
  val.forEach(function _docReduce(doc) {
2575
2567
  if (!doc || !doc._doc) {
2576
2568
  return;
@@ -3262,7 +3254,6 @@ Document.prototype.populated = function(path, val, options) {
3262
3254
  }
3263
3255
 
3264
3256
  // internal
3265
-
3266
3257
  if (val === true) {
3267
3258
  if (!this.$__.populated) {
3268
3259
  return undefined;
@@ -3272,6 +3263,22 @@ Document.prototype.populated = function(path, val, options) {
3272
3263
 
3273
3264
  this.$__.populated || (this.$__.populated = {});
3274
3265
  this.$__.populated[path] = {value: val, options: options};
3266
+
3267
+ // If this was a nested populate, make sure each populated doc knows
3268
+ // about its populated children (gh-7685)
3269
+ const pieces = path.split('.');
3270
+ for (let i = 0; i < pieces.length - 1; ++i) {
3271
+ const subpath = pieces.slice(0, i + 1).join('.');
3272
+ const subdoc = this.get(subpath);
3273
+ if (subdoc != null && subdoc.$__ != null && this.populated(subpath)) {
3274
+ const rest = pieces.slice(i + 1).join('.');
3275
+ subdoc.populated(rest, val, options);
3276
+ // No need to continue because the above recursion should take care of
3277
+ // marking the rest of the docs as populated
3278
+ break;
3279
+ }
3280
+ }
3281
+
3275
3282
  return val;
3276
3283
  };
3277
3284
 
@@ -3332,7 +3339,7 @@ Document.prototype.depopulate = function(path) {
3332
3339
  if (virtualKeys.indexOf(path[i]) !== -1) {
3333
3340
  delete this.$$populatedVirtuals[path[i]];
3334
3341
  delete this._doc[path[i]];
3335
- } else {
3342
+ } else if (populatedIds) {
3336
3343
  this.$set(path[i], populatedIds);
3337
3344
  }
3338
3345
  }
@@ -20,8 +20,9 @@ const util = require('util');
20
20
  * @api private
21
21
  */
22
22
 
23
- function NativeCollection() {
23
+ function NativeCollection(name, options) {
24
24
  this.collection = null;
25
+ this.Promise = options.Promise || Promise;
25
26
  MongooseCollection.apply(this, arguments);
26
27
  }
27
28
 
@@ -115,9 +116,10 @@ const syncCollectionMethods = { watch: true };
115
116
  function iter(i) {
116
117
  NativeCollection.prototype[i] = function() {
117
118
  const collection = this.collection;
118
- const args = arguments;
119
+ const args = Array.from(arguments);
119
120
  const _this = this;
120
121
  const debug = get(_this, 'conn.base.options.debug');
122
+ const lastArg = arguments[arguments.length - 1];
121
123
 
122
124
  // If user force closed, queueing will hang forever. See #5664
123
125
  if (this.opts.$wasForceClosed) {
@@ -127,8 +129,18 @@ function iter(i) {
127
129
  if (syncCollectionMethods[i]) {
128
130
  throw new Error('Collection method ' + i + ' is synchronous');
129
131
  }
130
- this.addQueue(i, arguments);
131
- return;
132
+ if (typeof lastArg === 'function') {
133
+ this.addQueue(i, args);
134
+ return;
135
+ }
136
+ return new this.Promise((resolve, reject) => {
137
+ this.addQueue(i, [].concat(args).concat([(err, res) => {
138
+ if (err != null) {
139
+ return reject(err);
140
+ }
141
+ resolve(res);
142
+ }]));
143
+ });
132
144
  }
133
145
 
134
146
  if (debug) {
@@ -8,4 +8,4 @@ exports.Binary = require('./binary');
8
8
  exports.Collection = require('./collection');
9
9
  exports.Decimal128 = require('./decimal128');
10
10
  exports.ObjectId = require('./objectid');
11
- exports.ReadPreference = require('./ReadPreference');
11
+ exports.ReadPreference = require('./ReadPreference');
@@ -9,6 +9,9 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) {
9
9
  const skipDocArrays = options.skipDocArrays;
10
10
 
11
11
  let deleted = 0;
12
+ if (!doc) {
13
+ return deleted;
14
+ }
12
15
  for (const modifiedPath of Object.keys(doc.$__.activePaths.states.modify)) {
13
16
  if (skipDocArrays) {
14
17
  const schemaType = doc.schema.path(modifiedPath);
@@ -18,8 +18,12 @@ module.exports = function getEmbeddedDiscriminatorPath(doc, path, options) {
18
18
  const subpath = parts.slice(0, i + 1).join('.');
19
19
  schema = doc.schema.path(subpath);
20
20
  if (schema == null) {
21
+ type = 'adhocOrUndefined';
21
22
  continue;
22
23
  }
24
+ if (schema.instance === 'Mixed') {
25
+ return typeOnly ? 'real' : schema;
26
+ }
23
27
  type = doc.schema.pathType(subpath);
24
28
  if ((schema.$isSingleNested || schema.$isMongooseDocumentArrayElement) &&
25
29
  schema.schema.discriminators != null) {
@@ -30,11 +34,7 @@ module.exports = function getEmbeddedDiscriminatorPath(doc, path, options) {
30
34
  continue;
31
35
  }
32
36
  const rest = parts.slice(i + 1).join('.');
33
- schema = discriminators[discriminatorKey].path(rest);
34
- if (schema != null) {
35
- type = discriminators[discriminatorKey].pathType(rest);
36
- break;
37
- }
37
+ return getEmbeddedDiscriminatorPath(doc.get(subpath), rest, options);
38
38
  }
39
39
  }
40
40
 
@@ -7,6 +7,9 @@ module.exports = function applyStaticHooks(model, hooks, statics) {
7
7
  useErrorHandlers: true,
8
8
  numCallbackParams: 1
9
9
  };
10
+
11
+ hooks = hooks.filter(hook => hook.model !== false);
12
+
10
13
  model.$__insertMany = hooks.createWrapper('insertMany',
11
14
  model.$__insertMany, model, kareemOptions);
12
15
 
@@ -392,8 +392,6 @@ function castUpdateVal(schema, val, op, $conditional, context, path) {
392
392
  tmp = tmp[0];
393
393
  }
394
394
  return tmp;
395
- } else if (cond && op === '$set') {
396
- return schema.cast(val);
397
395
  }
398
396
 
399
397
  if (op in noCastOps) {
@@ -9,19 +9,19 @@ const hasParentPointers = Symbol('Mongoose.helpers.setParentPointers');
9
9
  * This is a slow path function, should only run when model is compiled
10
10
  */
11
11
 
12
- module.exports = function setParentPointers(schema) {
12
+ module.exports = function setParentPointers(schema, parentSchemaType) {
13
13
  if (schema[hasParentPointers]) {
14
14
  return;
15
15
  }
16
16
  schema[hasParentPointers] = true;
17
17
  for (const path of Object.keys(schema.paths)) {
18
18
  const schemaType = schema.paths[path];
19
- if (schemaType.schema != null) {
20
- Object.defineProperty(schemaType.schema, '$schemaType', {
19
+ if (parentSchemaType != null) {
20
+ Object.defineProperty(schemaType, '$parentSchemaType', {
21
21
  configurable: true,
22
22
  writable: false,
23
23
  enumerable: false,
24
- value: schemaType
24
+ value: parentSchemaType
25
25
  });
26
26
  }
27
27
  Object.defineProperty(schemaType, '$parentSchema', {
@@ -35,7 +35,7 @@ module.exports = function setParentPointers(schema) {
35
35
  for (const path of Object.keys(schema.paths)) {
36
36
  const type = schema.paths[path];
37
37
  if (type.$isSingleNested || type.$isMongooseDocumentArray) {
38
- setParentPointers(type.schema, false);
38
+ setParentPointers(type.schema, type);
39
39
  }
40
40
  }
41
41
  };
@@ -1,13 +1,10 @@
1
1
  'use strict';
2
2
 
3
- exports.validatorErrorSymbol = Symbol.for('mongoose:validatorError');
4
-
5
- exports.documentArrayParent = Symbol.for('mongoose:documentArrayParent');
6
-
7
- exports.modelSymbol = Symbol.for('mongoose#Model');
8
-
9
- exports.getSymbol = Symbol.for('mongoose#Document#get');
10
-
11
- exports.objectIdSymbol = Symbol.for('mongoose#ObjectId');
12
-
13
- exports.schemaTypeSymbol = Symbol.for('mongoose#schemaType');
3
+ exports.arrayParentSymbol = Symbol('mongoose#Array#_parent');
4
+ exports.arraySchemaSymbol = Symbol('mongoose#Array#_schema');
5
+ exports.documentArrayParent = Symbol('mongoose:documentArrayParent');
6
+ exports.getSymbol = Symbol('mongoose#Document#get');
7
+ exports.modelSymbol = Symbol('mongoose#Model');
8
+ exports.objectIdSymbol = Symbol('mongoose#ObjectId');
9
+ exports.schemaTypeSymbol = Symbol('mongoose#schemaType');
10
+ exports.validatorErrorSymbol = Symbol('mongoose:validatorError');
@@ -63,18 +63,24 @@ function applyTimestampsToChildren(now, update, schema) {
63
63
  if (!path) {
64
64
  continue;
65
65
  }
66
+
67
+ let parentSchemaType = null;
68
+ const pieces = keyToSearch.split('.');
69
+ for (let i = pieces.length - 1; i > 0; --i) {
70
+ const s = schema.path(pieces.slice(0, i).join('.'));
71
+ if (s != null &&
72
+ (s.$isMongooseDocumentArray || s.$isSingleNested)) {
73
+ parentSchemaType = s;
74
+ break;
75
+ }
76
+ }
77
+
66
78
  if (Array.isArray(update.$set[key]) && path.$isMongooseDocumentArray) {
67
79
  applyTimestampsToDocumentArray(update.$set[key], path, now);
68
80
  } else if (update.$set[key] && path.$isSingleNested) {
69
81
  applyTimestampsToSingleNested(update.$set[key], path, now);
70
- } else if (path.$parentSchema !== schema && path.$parentSchema != null) {
71
- const parentPath = path.$parentSchema.$schemaType;
72
-
73
- if (parentPath == null) {
74
- continue;
75
- }
76
-
77
- timestamps = parentPath.schema.options.timestamps;
82
+ } else if (parentSchemaType != null) {
83
+ timestamps = parentSchemaType.schema.options.timestamps;
78
84
  createdAt = handleTimestampOption(timestamps, 'createdAt');
79
85
  updatedAt = handleTimestampOption(timestamps, 'updatedAt');
80
86
 
@@ -82,23 +88,23 @@ function applyTimestampsToChildren(now, update, schema) {
82
88
  continue;
83
89
  }
84
90
 
85
- if (parentPath.$isSingleNested) {
91
+ if (parentSchemaType.$isSingleNested) {
86
92
  // Single nested is easy
87
- update.$set[parentPath.path + '.' + updatedAt] = now;
93
+ update.$set[parentSchemaType.path + '.' + updatedAt] = now;
88
94
  continue;
89
95
  }
90
96
 
91
- let childPath = key.substr(parentPath.path.length + 1);
92
- const firstDot = childPath.indexOf('.');
97
+ let childPath = key.substr(parentSchemaType.path.length + 1);
93
98
 
94
- // Shouldn't happen, but if it does ignore this path
95
- if (firstDot === -1) {
99
+ if (/^\d+$/.test(childPath)) {
100
+ update.$set[parentSchemaType.path + '.' + childPath][updatedAt] = now;
96
101
  continue;
97
102
  }
98
103
 
99
- childPath = childPath.substr(0, firstDot);
104
+ const firstDot = childPath.indexOf('.');
105
+ childPath = firstDot !== -1 ? childPath.substr(0, firstDot) : childPath;
100
106
 
101
- update.$set[parentPath.path + '.' + childPath + '.' + updatedAt] = now;
107
+ update.$set[parentSchemaType.path + '.' + childPath + '.' + updatedAt] = now;
102
108
  } else if (path.schema != null && path.schema != schema && update.$set[key]) {
103
109
  timestamps = path.schema.options.timestamps;
104
110
  createdAt = handleTimestampOption(timestamps, 'createdAt');
package/lib/internal.js CHANGED
@@ -30,6 +30,7 @@ function InternalCache() {
30
30
  this.pathsToScopes = {};
31
31
  this.cachedRequired = {};
32
32
  this.session = null;
33
+ this.$setCalled = new Set();
33
34
 
34
35
  // embedded docs
35
36
  this.ownerDocument = undefined;