mongoose 6.3.7 → 6.4.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/.eslintrc.json CHANGED
@@ -4,43 +4,70 @@
4
4
  ],
5
5
  "ignorePatterns": [
6
6
  "docs",
7
+ "tools",
7
8
  "dist",
8
- "test/files/*"
9
+ "website.js",
10
+ "test/files/*",
11
+ "benchmarks"
9
12
  ],
10
- "overrides": [{
11
- "files": [
12
- "**/*.{ts,tsx}"
13
- ],
14
- "extends": [
15
- "plugin:@typescript-eslint/eslint-recommended",
16
- "plugin:@typescript-eslint/recommended"
17
- ],
18
- "plugins": [
19
- "@typescript-eslint"
20
- ],
21
- "rules": {
22
- "@typescript-eslint/triple-slash-reference": "off",
23
- "spaced-comment": ["error", "always", {
24
- "markers": ["/"]
25
- }],
26
- "@typescript-eslint/no-explicit-any": "off",
27
- "@typescript-eslint/ban-types": "off",
28
- "@typescript-eslint/no-unused-vars": "off",
29
- "@typescript-eslint/explicit-module-boundary-types": "off",
30
- "@typescript-eslint/indent": ["error", 2, {
31
- "SwitchCase": 1
32
- }],
33
- "@typescript-eslint/prefer-optional-chain": "error",
34
- "@typescript-eslint/brace-style": "error",
35
- "@typescript-eslint/no-dupe-class-members": "error",
36
- "@typescript-eslint/no-redeclare": "error",
37
- "@typescript-eslint/type-annotation-spacing": "error",
38
- "@typescript-eslint/object-curly-spacing": ["error", "always"],
39
- "@typescript-eslint/semi": "error",
40
- "@typescript-eslint/space-before-function-paren": ["error", "never"],
41
- "@typescript-eslint/space-infix-ops": "off"
13
+ "overrides": [
14
+ {
15
+ "files": [
16
+ "**/*.{ts,tsx}"
17
+ ],
18
+ "extends": [
19
+ "plugin:@typescript-eslint/eslint-recommended",
20
+ "plugin:@typescript-eslint/recommended"
21
+ ],
22
+ "plugins": [
23
+ "@typescript-eslint"
24
+ ],
25
+ "rules": {
26
+ "@typescript-eslint/triple-slash-reference": "off",
27
+ "spaced-comment": [
28
+ "error",
29
+ "always",
30
+ {
31
+ "block": {
32
+ "markers": [
33
+ "!"
34
+ ],
35
+ "balanced": true
36
+ },
37
+ "markers": [
38
+ "/"
39
+ ]
40
+ }
41
+ ],
42
+ "@typescript-eslint/no-explicit-any": "off",
43
+ "@typescript-eslint/ban-types": "off",
44
+ "@typescript-eslint/no-unused-vars": "off",
45
+ "@typescript-eslint/explicit-module-boundary-types": "off",
46
+ "@typescript-eslint/indent": [
47
+ "error",
48
+ 2,
49
+ {
50
+ "SwitchCase": 1
51
+ }
52
+ ],
53
+ "@typescript-eslint/prefer-optional-chain": "error",
54
+ "@typescript-eslint/brace-style": "error",
55
+ "@typescript-eslint/no-dupe-class-members": "error",
56
+ "@typescript-eslint/no-redeclare": "error",
57
+ "@typescript-eslint/type-annotation-spacing": "error",
58
+ "@typescript-eslint/object-curly-spacing": [
59
+ "error",
60
+ "always"
61
+ ],
62
+ "@typescript-eslint/semi": "error",
63
+ "@typescript-eslint/space-before-function-paren": [
64
+ "error",
65
+ "never"
66
+ ],
67
+ "@typescript-eslint/space-infix-ops": "off"
68
+ }
42
69
  }
43
- }],
70
+ ],
44
71
  "plugins": [
45
72
  "mocha-no-only"
46
73
  ],
package/lib/connection.js CHANGED
@@ -9,6 +9,7 @@ const EventEmitter = require('events').EventEmitter;
9
9
  const Schema = require('./schema');
10
10
  const STATES = require('./connectionstate');
11
11
  const MongooseError = require('./error/index');
12
+ const DisconnectedError = require('./error/disconnected');
12
13
  const SyncIndexesError = require('./error/syncIndexes');
13
14
  const PromiseProvider = require('./promise_provider');
14
15
  const ServerSelectionError = require('./error/serverSelection');
@@ -565,8 +566,7 @@ function _wrapConnHelper(fn) {
565
566
  const argsWithoutCb = typeof cb === 'function' ?
566
567
  Array.prototype.slice.call(arguments, 0, arguments.length - 1) :
567
568
  Array.prototype.slice.call(arguments);
568
- const disconnectedError = new MongooseError('Connection ' + this.id +
569
- ' was disconnected when calling `' + fn.name + '`');
569
+ const disconnectedError = new DisconnectedError(this.id, fn.name);
570
570
 
571
571
  return promiseOrCallback(cb, cb => {
572
572
  immediate(() => {
@@ -697,6 +697,18 @@ Connection.prototype.openUri = function(uri, options, callback) {
697
697
  typeof callback + '"');
698
698
  }
699
699
 
700
+ if (this._destroyCalled) {
701
+ const error = 'Connection has been closed and destroyed, and cannot be used for re-opening the connection. ' +
702
+ 'Please create a new connection with `mongoose.createConnection()` or `mongoose.connect()`.';
703
+ if (typeof callback === 'function') {
704
+ callback(error);
705
+ return;
706
+ }
707
+ else {
708
+ throw new MongooseError(error);
709
+ }
710
+ }
711
+
700
712
  if (this.readyState === STATES.connecting || this.readyState === STATES.connected) {
701
713
  if (this._connectionString !== uri) {
702
714
  throw new MongooseError('Can\'t call `openUri()` on an active connection with ' +
@@ -901,6 +913,23 @@ function _setClient(conn, client, options, dbName) {
901
913
  }
902
914
  }
903
915
 
916
+ Connection.prototype.destroy = function(force, callback) {
917
+ if (typeof force === 'function') {
918
+ callback = force;
919
+ force = false;
920
+ }
921
+
922
+ if (force != null && typeof force === 'object') {
923
+ this.$wasForceClosed = !!force.force;
924
+ } else {
925
+ this.$wasForceClosed = !!force;
926
+ }
927
+
928
+ return promiseOrCallback(callback, cb => {
929
+ this._close(force, true, cb);
930
+ });
931
+ };
932
+
904
933
  /**
905
934
  * Closes the connection
906
935
  *
@@ -923,7 +952,7 @@ Connection.prototype.close = function(force, callback) {
923
952
  }
924
953
 
925
954
  return promiseOrCallback(callback, cb => {
926
- this._close(force, cb);
955
+ this._close(force, false, cb);
927
956
  });
928
957
  };
929
958
 
@@ -931,19 +960,26 @@ Connection.prototype.close = function(force, callback) {
931
960
  * Handles closing the connection
932
961
  *
933
962
  * @param {Boolean} force
963
+ * @param {Boolean} destroy
934
964
  * @param {Function} callback
935
965
  * @api private
936
966
  */
937
- Connection.prototype._close = function(force, callback) {
967
+ Connection.prototype._close = function(force, destroy, callback) {
938
968
  const _this = this;
939
969
  const closeCalled = this._closeCalled;
940
970
  this._closeCalled = true;
971
+ this._destroyCalled = destroy;
941
972
  if (this.client != null) {
942
973
  this.client._closeCalled = true;
974
+ this.client._destroyCalled = destroy;
943
975
  }
944
976
 
977
+ const conn = this;
945
978
  switch (this.readyState) {
946
979
  case STATES.disconnected:
980
+ if (destroy && this.base.connections.indexOf(conn) !== -1) {
981
+ this.base.connections.splice(this.base.connections.indexOf(conn), 1);
982
+ }
947
983
  if (closeCalled) {
948
984
  callback();
949
985
  } else {
@@ -963,6 +999,9 @@ Connection.prototype._close = function(force, callback) {
963
999
  if (err) {
964
1000
  return callback(err);
965
1001
  }
1002
+ if (destroy && _this.base.connections.indexOf(conn) !== -1) {
1003
+ _this.base.connections.splice(_this.base.connections.indexOf(conn), 1);
1004
+ }
966
1005
  _this.onClose(force);
967
1006
  callback(null);
968
1007
  });
@@ -970,12 +1009,15 @@ Connection.prototype._close = function(force, callback) {
970
1009
  break;
971
1010
  case STATES.connecting:
972
1011
  this.once('open', function() {
973
- _this.close(callback);
1012
+ destroy ? _this.destroy(force, callback) : _this.close(force, callback);
974
1013
  });
975
1014
  break;
976
1015
 
977
1016
  case STATES.disconnecting:
978
1017
  this.once('close', function() {
1018
+ if (destroy && _this.base.connections.indexOf(conn) !== -1) {
1019
+ _this.base.connections.splice(_this.base.connections.indexOf(conn), 1);
1020
+ }
979
1021
  callback();
980
1022
  });
981
1023
  break;
@@ -1004,7 +1046,7 @@ Connection.prototype.onClose = function(force) {
1004
1046
  this.emit('close', force);
1005
1047
 
1006
1048
  for (const db of this.otherDbs) {
1007
- db.close({ force: force, skipCloseClient: true });
1049
+ this._destroyCalled ? db.destroy({ force: force, skipCloseClient: true }) : db.close({ force: force, skipCloseClient: true });
1008
1050
  }
1009
1051
  };
1010
1052
 
@@ -1026,7 +1068,7 @@ Connection.prototype.collection = function(name, options) {
1026
1068
  };
1027
1069
  options = Object.assign({}, defaultOptions, options ? utils.clone(options) : {});
1028
1070
  options.$wasForceClosed = this.$wasForceClosed;
1029
- const Collection = driver.get().Collection;
1071
+ const Collection = this.base && this.base.__driver && this.base.__driver.Collection || driver.get().Collection;
1030
1072
  if (!(name in this.collections)) {
1031
1073
  this.collections[name] = new Collection(name, this, options);
1032
1074
  }
@@ -1260,8 +1302,7 @@ Connection.prototype.deleteModel = function(name) {
1260
1302
  */
1261
1303
 
1262
1304
  Connection.prototype.watch = function(pipeline, options) {
1263
- const disconnectedError = new MongooseError('Connection ' + this.id +
1264
- ' was disconnected when calling `watch()`');
1305
+ const disconnectedError = new DisconnectedError(this.id, 'watch');
1265
1306
 
1266
1307
  const changeStreamThunk = cb => {
1267
1308
  immediate(() => {
package/lib/document.js CHANGED
@@ -79,6 +79,7 @@ function Document(obj, fields, skipId, options) {
79
79
  options = skipId;
80
80
  skipId = options.skipId;
81
81
  }
82
+ options = Object.assign({}, options);
82
83
 
83
84
  // Support `browserDocument.js` syntax
84
85
  if (this.$__schema == null) {
@@ -92,9 +93,9 @@ function Document(obj, fields, skipId, options) {
92
93
  }
93
94
 
94
95
  this.$__ = new InternalCache();
95
- this.$isNew = options && options.isNew != null ? options.isNew : true;
96
+ this.$isNew = 'isNew' in options ? options.isNew : true;
96
97
 
97
- if (options && options.priorDoc != null) {
98
+ if (options.priorDoc != null) {
98
99
  this.$__.priorDoc = options.priorDoc;
99
100
  }
100
101
 
@@ -107,20 +108,18 @@ function Document(obj, fields, skipId, options) {
107
108
  }
108
109
 
109
110
  let defaults = true;
110
- if (options && options.defaults !== undefined) {
111
+ if (options.defaults !== undefined) {
111
112
  this.$__.defaults = options.defaults;
112
113
  defaults = options.defaults;
113
114
  }
114
115
 
115
116
  const schema = this.$__schema;
116
117
 
117
- let strict;
118
118
  if (typeof fields === 'boolean' || fields === 'throw') {
119
119
  this.$__.strictMode = fields;
120
- strict = fields;
121
120
  fields = undefined;
122
121
  } else {
123
- strict = schema.options.strict;
122
+ this.$__.strictMode = schema.options.strict;
124
123
  if (fields != null) {
125
124
  this.$__.selected = fields;
126
125
  }
@@ -170,15 +169,15 @@ function Document(obj, fields, skipId, options) {
170
169
  // Function defaults get applied **after** setting initial values so they
171
170
  // see the full doc rather than an empty one, unless they opt out.
172
171
  // Re: gh-3781, gh-6155
173
- if (options && options.willInit && defaults) {
172
+ if (options.willInit && defaults) {
174
173
  if (options.skipDefaults) {
175
174
  this.$__.skipDefaults = options.skipDefaults;
176
175
  }
177
176
  } else if (defaults) {
178
- $__applyDefaults(this, fields, exclude, hasIncludedChildren, false, options && options.skipDefaults);
177
+ $__applyDefaults(this, fields, exclude, hasIncludedChildren, false, options.skipDefaults);
179
178
  }
180
179
 
181
- if (!strict && obj) {
180
+ if (!this.$__.strictMode && obj) {
182
181
  const _this = this;
183
182
  const keys = Object.keys(this._doc);
184
183
 
@@ -2487,7 +2486,7 @@ function _getPathsToValidate(doc) {
2487
2486
  const fullPathToSubdoc = subdoc.$__fullPathWithIndexes();
2488
2487
 
2489
2488
  for (const p of paths) {
2490
- if (p === null || p.startsWith(fullPathToSubdoc + '.')) {
2489
+ if (p == null || p.startsWith(fullPathToSubdoc + '.')) {
2491
2490
  paths.delete(p);
2492
2491
  }
2493
2492
  }
@@ -2508,6 +2507,14 @@ function _getPathsToValidate(doc) {
2508
2507
  continue;
2509
2508
  }
2510
2509
 
2510
+ if (_pathType.$isMongooseDocumentArray) {
2511
+ for (const p of paths) {
2512
+ if (p == null || p.startsWith(_pathType.path + '.')) {
2513
+ paths.delete(p);
2514
+ }
2515
+ }
2516
+ }
2517
+
2511
2518
  // Optimization: if primitive path with no validators, or array of primitives
2512
2519
  // with no validators, skip validating this path entirely.
2513
2520
  if (!_pathType.caster && _pathType.validators.length === 0) {
@@ -3146,8 +3153,7 @@ Document.prototype.$__reset = function reset() {
3146
3153
  if (subdoc.$isDocumentArrayElement) {
3147
3154
  if (!resetArrays.has(subdoc.parentArray())) {
3148
3155
  const array = subdoc.parentArray();
3149
- // Mark path to array as init for gh-6818
3150
- this.$__.activePaths.init(fullPathWithIndexes.replace(/\.\d+$/, '').slice(-subdoc.$basePath - 1));
3156
+ this.$__.activePaths.clearPath(fullPathWithIndexes.replace(/\.\d+$/, '').slice(-subdoc.$basePath - 1));
3151
3157
  array[arrayAtomicsBackupSymbol] = array[arrayAtomicsSymbol];
3152
3158
  array[arrayAtomicsSymbol] = {};
3153
3159
 
@@ -3155,7 +3161,7 @@ Document.prototype.$__reset = function reset() {
3155
3161
  }
3156
3162
  } else {
3157
3163
  if (subdoc.$parent() === this) {
3158
- this.$__.activePaths.init(subdoc.$basePath);
3164
+ this.$__.activePaths.clearPath(subdoc.$basePath);
3159
3165
  } else if (subdoc.$parent() != null && subdoc.$parent().$isSubdocument) {
3160
3166
  // If map path underneath subdocument, may end up with a case where
3161
3167
  // map path is modified but parent still needs to be reset. See gh-10295
@@ -3389,7 +3395,7 @@ Document.prototype.$getAllSubdocs = function() {
3389
3395
  }, seed);
3390
3396
  } else if (val && !Array.isArray(val) && val.$isSingleNested) {
3391
3397
  seed = Object.keys(val._doc).reduce(function(seed, path) {
3392
- return docReducer(val._doc, seed, path);
3398
+ return docReducer(val, seed, path);
3393
3399
  }, seed);
3394
3400
  seed.push(val);
3395
3401
  } else if (val && utils.isMongooseDocumentArray(val)) {
@@ -4263,10 +4269,10 @@ Document.prototype.$getPopulatedDocs = function $getPopulatedDocs() {
4263
4269
  *
4264
4270
  * #### Example:
4265
4271
  *
4266
- * Model.findOne().populate('author').exec(function (err, doc) {
4267
- * console.log(doc.author.name) // Dr.Seuss
4268
- * console.log(doc.populated('author')) // '5144cf8050f071d979c118a7'
4269
- * })
4272
+ * const doc = await Model.findOne().populate('author');
4273
+ *
4274
+ * console.log(doc.author.name); // Dr.Seuss
4275
+ * console.log(doc.populated('author')); // '5144cf8050f071d979c118a7'
4270
4276
  *
4271
4277
  * If the path was not populated, returns `undefined`.
4272
4278
  *
@@ -4320,6 +4326,37 @@ Document.prototype.populated = function(path, val, options) {
4320
4326
 
4321
4327
  Document.prototype.$populated = Document.prototype.populated;
4322
4328
 
4329
+ /**
4330
+ * Throws an error if a given path is not populated
4331
+ *
4332
+ * #### Example:
4333
+ *
4334
+ * const doc = await Model.findOne().populate('author');
4335
+ *
4336
+ * doc.$assertPopulated('author'); // does not throw
4337
+ * doc.$assertPopulated('other path'); // throws an error
4338
+ *
4339
+ *
4340
+ * @param {String | Array<String>} path
4341
+ * @return {Document} this
4342
+ * @memberOf Document
4343
+ * @instance
4344
+ * @api public
4345
+ */
4346
+
4347
+ Document.prototype.$assertPopulated = function $assertPopulated(paths) {
4348
+ if (Array.isArray(paths)) {
4349
+ paths.forEach(path => this.$assertPopulated(path));
4350
+ return this;
4351
+ }
4352
+
4353
+ if (!this.$populated(paths)) {
4354
+ throw new MongooseError(`Expected path "${paths}" to be populated`);
4355
+ }
4356
+
4357
+ return this;
4358
+ };
4359
+
4323
4360
  /**
4324
4361
  * Takes a populated field and returns it to its unpopulated state.
4325
4362
  *
@@ -16,10 +16,9 @@ class DisconnectedError extends MongooseError {
16
16
  /**
17
17
  * @param {String} connectionString
18
18
  */
19
- constructor(connectionString) {
20
- super('Ran out of retries trying to reconnect to "' +
21
- connectionString + '". Try setting `server.reconnectTries` and ' +
22
- '`server.reconnectInterval` to something higher.');
19
+ constructor(id, fnName) {
20
+ super('Connection ' + id +
21
+ ' was disconnected when calling `' + fnName + '()`');
23
22
  }
24
23
  }
25
24
 
@@ -40,8 +40,7 @@ function createImmutableSetter(path, immutable) {
40
40
  const _value = this.$__.priorDoc != null ?
41
41
  this.$__.priorDoc.$__getValue(path) :
42
42
  this.$__getValue(path);
43
- const strict = this.$__.strictMode == null ? this.$__schema.options.strict : this.$__.strictMode;
44
- if (strict === 'throw' && v !== _value) {
43
+ if (this.$__.strictMode === 'throw' && v !== _value) {
45
44
  throw new StrictModeError(path, 'Path `' + path + '` is immutable ' +
46
45
  'and strict mode is set to throw.', true);
47
46
  }
@@ -31,8 +31,11 @@ module.exports = function setupTimestamps(schema, timestamps) {
31
31
  }
32
32
 
33
33
  if (createdAt && !schema.paths[createdAt]) {
34
- schemaAdditions[createdAt] = { [schema.options.typeKey || 'type']: Date, immutable: true };
34
+ const baseImmutableCreatedAt = schema.base.get('timestamps.createdAt.immutable');
35
+ const immutable = baseImmutableCreatedAt != null ? baseImmutableCreatedAt : true;
36
+ schemaAdditions[createdAt] = { [schema.options.typeKey || 'type']: Date, immutable };
35
37
  }
38
+
36
39
  schema.add(schemaAdditions);
37
40
 
38
41
  schema.pre('save', function(next) {
@@ -125,26 +125,19 @@ module.exports = function(query, schema, castedDoc, options, callback) {
125
125
  validatorsToExecute.push(function(callback) {
126
126
  schemaPath.doValidate(v, function(err) {
127
127
  if (err) {
128
- err.path = updates[i];
129
- validationErrors.push(err);
130
- return callback(null);
131
- }
132
-
133
- v.validate(function(err) {
134
- if (err) {
135
- if (err.errors) {
136
- for (const key of Object.keys(err.errors)) {
137
- const _err = err.errors[key];
138
- _err.path = updates[i] + '.' + key;
139
- validationErrors.push(_err);
140
- }
141
- } else {
142
- err.path = updates[i];
143
- validationErrors.push(err);
128
+ if (err.errors) {
129
+ for (const key of Object.keys(err.errors)) {
130
+ const _err = err.errors[key];
131
+ _err.path = updates[i] + '.' + key;
132
+ validationErrors.push(_err);
144
133
  }
134
+ } else {
135
+ err.path = updates[i];
136
+ validationErrors.push(err);
145
137
  }
146
- callback(null);
147
- });
138
+ }
139
+
140
+ return callback(null);
148
141
  }, context, { updateValidator: true });
149
142
  });
150
143
  } else {