mongoose 7.3.4 → 7.4.1

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.
Files changed (41) hide show
  1. package/dist/browser.umd.js +1 -1
  2. package/lib/cast.js +2 -2
  3. package/lib/connection.js +32 -200
  4. package/lib/document.js +34 -42
  5. package/lib/drivers/node-mongodb-native/connection.js +247 -0
  6. package/lib/error/cast.js +10 -9
  7. package/lib/error/messages.js +1 -1
  8. package/lib/helpers/schema/applyWriteConcern.js +3 -0
  9. package/lib/helpers/schema/getPath.js +0 -1
  10. package/lib/helpers/schema/idGetter.js +12 -1
  11. package/lib/model.js +49 -18
  12. package/lib/query.js +36 -22
  13. package/lib/schema/SubdocumentPath.js +10 -4
  14. package/lib/schema/array.js +2 -0
  15. package/lib/schema/bigint.js +2 -0
  16. package/lib/schema/boolean.js +2 -0
  17. package/lib/schema/buffer.js +2 -0
  18. package/lib/schema/date.js +2 -0
  19. package/lib/schema/decimal128.js +2 -0
  20. package/lib/schema/documentarray.js +2 -0
  21. package/lib/schema/mixed.js +2 -0
  22. package/lib/schema/number.js +2 -0
  23. package/lib/schema/objectid.js +2 -0
  24. package/lib/schema/string.js +2 -0
  25. package/lib/schema/uuid.js +2 -0
  26. package/lib/schema.js +9 -7
  27. package/lib/schematype.js +20 -4
  28. package/lib/types/ArraySubdocument.js +9 -1
  29. package/lib/types/DocumentArray/methods/index.js +2 -2
  30. package/lib/types/array/methods/index.js +1 -1
  31. package/lib/types/subdocument.js +4 -0
  32. package/package.json +3 -3
  33. package/types/augmentations.d.ts +9 -0
  34. package/types/index.d.ts +12 -0
  35. package/types/inferschematype.d.ts +8 -7
  36. package/types/models.d.ts +7 -2
  37. package/types/query.d.ts +5 -1
  38. package/types/schemaoptions.d.ts +3 -0
  39. package/types/schematypes.d.ts +5 -1
  40. package/types/types.d.ts +0 -1
  41. package/lib/helpers/path/flattenObjectWithDottedPaths.js +0 -39
@@ -5,8 +5,13 @@
5
5
  'use strict';
6
6
 
7
7
  const MongooseConnection = require('../../connection');
8
+ const MongooseError = require('../../error/index');
8
9
  const STATES = require('../../connectionstate');
10
+ const mongodb = require('mongodb');
11
+ const pkg = require('../../../package.json');
12
+ const processConnectionOptions = require('../../helpers/processConnectionOptions');
9
13
  const setTimeout = require('../../helpers/timers').setTimeout;
14
+ const utils = require('../../utils');
10
15
 
11
16
  /**
12
17
  * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation.
@@ -121,6 +126,44 @@ NativeConnection.prototype.useDb = function(name, options) {
121
126
  return newConn;
122
127
  };
123
128
 
129
+ /**
130
+ * Removes the database connection with the given name created with `useDb()`.
131
+ *
132
+ * Throws an error if the database connection was not found.
133
+ *
134
+ * #### Example:
135
+ *
136
+ * // Connect to `initialdb` first
137
+ * const conn = await mongoose.createConnection('mongodb://127.0.0.1:27017/initialdb').asPromise();
138
+ *
139
+ * // Creates an un-cached connection to `mydb`
140
+ * const db = conn.useDb('mydb');
141
+ *
142
+ * // Closes `db`, and removes `db` from `conn.relatedDbs` and `conn.otherDbs`
143
+ * await conn.removeDb('mydb');
144
+ *
145
+ * @method removeDb
146
+ * @memberOf Connection
147
+ * @param {String} name The database name
148
+ * @return {Connection} this
149
+ */
150
+
151
+ NativeConnection.prototype.removeDb = function removeDb(name) {
152
+ const dbs = this.otherDbs.filter(db => db.name === name);
153
+ if (!dbs.length) {
154
+ throw new MongooseError(`No connections to database "${name}" found`);
155
+ }
156
+
157
+ for (const db of dbs) {
158
+ db._closeCalled = true;
159
+ db._destroyCalled = true;
160
+ db._readyState = STATES.disconnected;
161
+ db.$wasForceClosed = true;
162
+ }
163
+ delete this.relatedDbs[name];
164
+ this.otherDbs = this.otherDbs.filter(db => db.name !== name);
165
+ };
166
+
124
167
  /**
125
168
  * Closes the connection
126
169
  *
@@ -154,6 +197,210 @@ NativeConnection.prototype.doClose = async function doClose(force) {
154
197
  return this;
155
198
  };
156
199
 
200
+ /*!
201
+ * ignore
202
+ */
203
+
204
+ NativeConnection.prototype.createClient = async function createClient(uri, options) {
205
+ if (typeof uri !== 'string') {
206
+ throw new MongooseError('The `uri` parameter to `openUri()` must be a ' +
207
+ `string, got "${typeof uri}". Make sure the first parameter to ` +
208
+ '`mongoose.connect()` or `mongoose.createConnection()` is a string.');
209
+ }
210
+
211
+ if (this._destroyCalled) {
212
+ throw new MongooseError(
213
+ 'Connection has been closed and destroyed, and cannot be used for re-opening the connection. ' +
214
+ 'Please create a new connection with `mongoose.createConnection()` or `mongoose.connect()`.'
215
+ );
216
+ }
217
+
218
+ if (this.readyState === STATES.connecting || this.readyState === STATES.connected) {
219
+ if (this._connectionString !== uri) {
220
+ throw new MongooseError('Can\'t call `openUri()` on an active connection with ' +
221
+ 'different connection strings. Make sure you aren\'t calling `mongoose.connect()` ' +
222
+ 'multiple times. See: https://mongoosejs.com/docs/connections.html#multiple_connections');
223
+ }
224
+ }
225
+
226
+ options = processConnectionOptions(uri, options);
227
+
228
+ if (options) {
229
+
230
+ const autoIndex = options.config && options.config.autoIndex != null ?
231
+ options.config.autoIndex :
232
+ options.autoIndex;
233
+ if (autoIndex != null) {
234
+ this.config.autoIndex = autoIndex !== false;
235
+ delete options.config;
236
+ delete options.autoIndex;
237
+ }
238
+
239
+ if ('autoCreate' in options) {
240
+ this.config.autoCreate = !!options.autoCreate;
241
+ delete options.autoCreate;
242
+ }
243
+
244
+ if ('sanitizeFilter' in options) {
245
+ this.config.sanitizeFilter = options.sanitizeFilter;
246
+ delete options.sanitizeFilter;
247
+ }
248
+
249
+ // Backwards compat
250
+ if (options.user || options.pass) {
251
+ options.auth = options.auth || {};
252
+ options.auth.username = options.user;
253
+ options.auth.password = options.pass;
254
+
255
+ this.user = options.user;
256
+ this.pass = options.pass;
257
+ }
258
+ delete options.user;
259
+ delete options.pass;
260
+
261
+ if (options.bufferCommands != null) {
262
+ this.config.bufferCommands = options.bufferCommands;
263
+ delete options.bufferCommands;
264
+ }
265
+ } else {
266
+ options = {};
267
+ }
268
+
269
+ this._connectionOptions = options;
270
+ const dbName = options.dbName;
271
+ if (dbName != null) {
272
+ this.$dbName = dbName;
273
+ }
274
+ delete options.dbName;
275
+
276
+ if (!utils.hasUserDefinedProperty(options, 'driverInfo')) {
277
+ options.driverInfo = {
278
+ name: 'Mongoose',
279
+ version: pkg.version
280
+ };
281
+ }
282
+
283
+ this.readyState = STATES.connecting;
284
+ this._connectionString = uri;
285
+
286
+ let client;
287
+ try {
288
+ client = new mongodb.MongoClient(uri, options);
289
+ } catch (error) {
290
+ this.readyState = STATES.disconnected;
291
+ throw error;
292
+ }
293
+ this.client = client;
294
+
295
+ client.setMaxListeners(0);
296
+ await client.connect();
297
+
298
+ _setClient(this, client, options, dbName);
299
+
300
+ for (const db of this.otherDbs) {
301
+ _setClient(db, client, {}, db.name);
302
+ }
303
+ return this;
304
+ };
305
+
306
+ /*!
307
+ * ignore
308
+ */
309
+
310
+ NativeConnection.prototype.setClient = function setClient(client) {
311
+ if (!(client instanceof mongodb.MongoClient)) {
312
+ throw new MongooseError('Must call `setClient()` with an instance of MongoClient');
313
+ }
314
+ if (this.readyState !== STATES.disconnected) {
315
+ throw new MongooseError('Cannot call `setClient()` on a connection that is already connected.');
316
+ }
317
+ if (client.topology == null) {
318
+ throw new MongooseError('Cannot call `setClient()` with a MongoClient that you have not called `connect()` on yet.');
319
+ }
320
+
321
+ this._connectionString = client.s.url;
322
+ _setClient(this, client, {}, client.s.options.dbName);
323
+
324
+ for (const model of Object.values(this.models)) {
325
+ // Errors handled internally, so safe to ignore error
326
+ model.init().catch(function $modelInitNoop() {});
327
+ }
328
+
329
+ return this;
330
+ };
331
+
332
+ /*!
333
+ * ignore
334
+ */
335
+
336
+ function _setClient(conn, client, options, dbName) {
337
+ const db = dbName != null ? client.db(dbName) : client.db();
338
+ conn.db = db;
339
+ conn.client = client;
340
+ conn.host = client &&
341
+ client.s &&
342
+ client.s.options &&
343
+ client.s.options.hosts &&
344
+ client.s.options.hosts[0] &&
345
+ client.s.options.hosts[0].host || void 0;
346
+ conn.port = client &&
347
+ client.s &&
348
+ client.s.options &&
349
+ client.s.options.hosts &&
350
+ client.s.options.hosts[0] &&
351
+ client.s.options.hosts[0].port || void 0;
352
+ conn.name = dbName != null ? dbName : client && client.s && client.s.options && client.s.options.dbName || void 0;
353
+ conn._closeCalled = client._closeCalled;
354
+
355
+ const _handleReconnect = () => {
356
+ // If we aren't disconnected, we assume this reconnect is due to a
357
+ // socket timeout. If there's no activity on a socket for
358
+ // `socketTimeoutMS`, the driver will attempt to reconnect and emit
359
+ // this event.
360
+ if (conn.readyState !== STATES.connected) {
361
+ conn.readyState = STATES.connected;
362
+ conn.emit('reconnect');
363
+ conn.emit('reconnected');
364
+ conn.onOpen();
365
+ }
366
+ };
367
+
368
+ const type = client &&
369
+ client.topology &&
370
+ client.topology.description &&
371
+ client.topology.description.type || '';
372
+
373
+ if (type === 'Single') {
374
+ client.on('serverDescriptionChanged', ev => {
375
+ const newDescription = ev.newDescription;
376
+ if (newDescription.type === 'Unknown') {
377
+ conn.readyState = STATES.disconnected;
378
+ } else {
379
+ _handleReconnect();
380
+ }
381
+ });
382
+ } else if (type.startsWith('ReplicaSet')) {
383
+ client.on('topologyDescriptionChanged', ev => {
384
+ // Emit disconnected if we've lost connectivity to the primary
385
+ const description = ev.newDescription;
386
+ if (conn.readyState === STATES.connected && description.type !== 'ReplicaSetWithPrimary') {
387
+ // Implicitly emits 'disconnected'
388
+ conn.readyState = STATES.disconnected;
389
+ } else if (conn.readyState === STATES.disconnected && description.type === 'ReplicaSetWithPrimary') {
390
+ _handleReconnect();
391
+ }
392
+ });
393
+ }
394
+
395
+ conn.onOpen();
396
+
397
+ for (const i in conn.collections) {
398
+ if (utils.object.hasOwnProperty(conn.collections, i)) {
399
+ conn.collections[i].onOpen();
400
+ }
401
+ }
402
+ }
403
+
157
404
 
158
405
  /*!
159
406
  * Module exports.
package/lib/error/cast.js CHANGED
@@ -20,10 +20,9 @@ class CastError extends MongooseError {
20
20
  constructor(type, value, path, reason, schemaType) {
21
21
  // If no args, assume we'll `init()` later.
22
22
  if (arguments.length > 0) {
23
- const stringValue = getStringValue(value);
24
23
  const valueType = getValueType(value);
25
24
  const messageFormat = getMessageFormat(schemaType);
26
- const msg = formatMessage(null, type, stringValue, path, messageFormat, valueType, reason);
25
+ const msg = formatMessage(null, type, value, path, messageFormat, valueType, reason);
27
26
  super(msg);
28
27
  this.init(type, value, path, reason, schemaType);
29
28
  } else {
@@ -77,7 +76,7 @@ class CastError extends MongooseError {
77
76
  */
78
77
  setModel(model) {
79
78
  this.model = model;
80
- this.message = formatMessage(model, this.kind, this.stringValue, this.path,
79
+ this.message = formatMessage(model, this.kind, this.value, this.path,
81
80
  this.messageFormat, this.valueType);
82
81
  }
83
82
  }
@@ -111,10 +110,8 @@ function getValueType(value) {
111
110
  }
112
111
 
113
112
  function getMessageFormat(schemaType) {
114
- const messageFormat = schemaType &&
115
- schemaType.options &&
116
- schemaType.options.cast || null;
117
- if (typeof messageFormat === 'string') {
113
+ const messageFormat = schemaType && schemaType._castErrorMessage || null;
114
+ if (typeof messageFormat === 'string' || typeof messageFormat === 'function') {
118
115
  return messageFormat;
119
116
  }
120
117
  }
@@ -123,8 +120,9 @@ function getMessageFormat(schemaType) {
123
120
  * ignore
124
121
  */
125
122
 
126
- function formatMessage(model, kind, stringValue, path, messageFormat, valueType, reason) {
127
- if (messageFormat != null) {
123
+ function formatMessage(model, kind, value, path, messageFormat, valueType, reason) {
124
+ if (typeof messageFormat === 'string') {
125
+ const stringValue = getStringValue(value);
128
126
  let ret = messageFormat.
129
127
  replace('{KIND}', kind).
130
128
  replace('{VALUE}', stringValue).
@@ -134,7 +132,10 @@ function formatMessage(model, kind, stringValue, path, messageFormat, valueType,
134
132
  }
135
133
 
136
134
  return ret;
135
+ } else if (typeof messageFormat === 'function') {
136
+ return messageFormat(value, path, model, kind);
137
137
  } else {
138
+ const stringValue = getStringValue(value);
138
139
  const valueTypeMsg = valueType ? ' (type ' + valueType + ')' : '';
139
140
  let ret = 'Cast to ' + kind + ' failed for value ' +
140
141
  stringValue + valueTypeMsg + ' at path "' + path + '"';
@@ -6,7 +6,7 @@
6
6
  * const mongoose = require('mongoose');
7
7
  * mongoose.Error.messages.String.enum = "Your custom message for {PATH}.";
8
8
  *
9
- * As you might have noticed, error messages support basic templating
9
+ * Error messages support basic templating. Mongoose will replace the following strings with the corresponding value.
10
10
  *
11
11
  * - `{PATH}` is replaced with the invalid document path
12
12
  * - `{VALUE}` is replaced with the invalid value
@@ -3,6 +3,9 @@
3
3
  const get = require('../get');
4
4
 
5
5
  module.exports = function applyWriteConcern(schema, options) {
6
+ if (options.writeConcern != null) {
7
+ return;
8
+ }
6
9
  const writeConcern = get(schema, 'options.writeConcern', {});
7
10
  if (Object.keys(writeConcern).length != 0) {
8
11
  options.writeConcern = {};
@@ -13,7 +13,6 @@ module.exports = function getPath(schema, path) {
13
13
  if (schematype != null) {
14
14
  return schematype;
15
15
  }
16
-
17
16
  const pieces = path.split('.');
18
17
  let cur = '';
19
18
  let isArray = false;
@@ -12,8 +12,8 @@ module.exports = function addIdGetter(schema) {
12
12
  if (!autoIdGetter) {
13
13
  return schema;
14
14
  }
15
-
16
15
  schema.virtual('id').get(idGetter);
16
+ schema.virtual('id').set(idSetter);
17
17
 
18
18
  return schema;
19
19
  };
@@ -30,3 +30,14 @@ function idGetter() {
30
30
 
31
31
  return null;
32
32
  }
33
+
34
+ /**
35
+ *
36
+ * @param {String} v the id to set
37
+ * @api private
38
+ */
39
+
40
+ function idSetter(v) {
41
+ this._id = v;
42
+ return v;
43
+ }
package/lib/model.js CHANGED
@@ -320,7 +320,6 @@ Model.prototype.$__handleSave = function(options, callback) {
320
320
  _setIsNew(this, false);
321
321
  // Make it possible to retry the insert
322
322
  this.$__.inserting = true;
323
-
324
323
  return;
325
324
  }
326
325
 
@@ -479,7 +478,6 @@ function generateVersionError(doc, modifiedPaths) {
479
478
  * newProduct === product; // true
480
479
  *
481
480
  * @param {Object} [options] options optional options
482
- * @param {Boolean} [options.ordered] saves the docs in series rather than parallel.
483
481
  * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.session()).
484
482
  * @param {Object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead.
485
483
  * @param {Boolean} [options.validateBeforeSave] set to false to save without validating.
@@ -1372,6 +1370,14 @@ Model.createCollection = async function createCollection(options) {
1372
1370
  throw new MongooseError('Model.createCollection() no longer accepts a callback');
1373
1371
  }
1374
1372
 
1373
+ const collectionOptions = this &&
1374
+ this.schema &&
1375
+ this.schema.options &&
1376
+ this.schema.options.collectionOptions;
1377
+ if (collectionOptions != null) {
1378
+ options = Object.assign({}, collectionOptions, options);
1379
+ }
1380
+
1375
1381
  const schemaCollation = this &&
1376
1382
  this.schema &&
1377
1383
  this.schema.options &&
@@ -2802,6 +2808,8 @@ Model.findByIdAndRemove = function(id, options) {
2802
2808
  *
2803
2809
  * @param {Array|Object} docs Documents to insert, as a spread or array
2804
2810
  * @param {Object} [options] Options passed down to `save()`. To specify `options`, `docs` **must** be an array, not a spread. See [Model.save](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) for available options.
2811
+ * @param {Boolean} [options.ordered] saves the docs in series rather than parallel.
2812
+ * @param {Boolean} [options.aggregateErrors] Aggregate Errors instead of throwing the first one that occurs. Default: false
2805
2813
  * @return {Promise}
2806
2814
  * @api public
2807
2815
  */
@@ -2858,27 +2866,41 @@ Model.create = async function create(doc, options) {
2858
2866
  return Array.isArray(doc) ? [] : null;
2859
2867
  }
2860
2868
  let res = [];
2869
+ const immediateError = typeof options.aggregateErrors === 'boolean' ? !options.aggregateErrors : true;
2870
+
2871
+ delete options.aggregateErrors; // dont pass on the option to "$save"
2872
+
2861
2873
  if (options.ordered) {
2862
2874
  for (let i = 0; i < args.length; i++) {
2863
- const doc = args[i];
2864
- const Model = this.discriminators && doc[discriminatorKey] != null ?
2865
- this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2866
- this;
2867
- if (Model == null) {
2868
- throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
2875
+ try {
2876
+ const doc = args[i];
2877
+ const Model = this.discriminators && doc[discriminatorKey] != null ?
2878
+ this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2879
+ this;
2880
+ if (Model == null) {
2881
+ throw new MongooseError(`Discriminator "${doc[discriminatorKey]}" not ` +
2869
2882
  `found for model "${this.modelName}"`);
2870
- }
2871
- let toSave = doc;
2872
- if (!(toSave instanceof Model)) {
2873
- toSave = new Model(toSave);
2874
- }
2883
+ }
2884
+ let toSave = doc;
2885
+ if (!(toSave instanceof Model)) {
2886
+ toSave = new Model(toSave);
2887
+ }
2875
2888
 
2876
- await toSave.$save(options);
2877
- res.push(toSave);
2889
+ await toSave.$save(options);
2890
+ res.push(toSave);
2891
+ } catch (err) {
2892
+ if (!immediateError) {
2893
+ res.push(err);
2894
+ } else {
2895
+ throw err;
2896
+ }
2897
+ }
2878
2898
  }
2879
2899
  return res;
2880
2900
  } else {
2881
- res = await Promise.all(args.map(async doc => {
2901
+ // ".bind(Promise)" is required, otherwise results in "TypeError: Promise.allSettled called on non-object"
2902
+ const promiseType = !immediateError ? Promise.allSettled.bind(Promise) : Promise.all.bind(Promise);
2903
+ let p = promiseType(args.map(async doc => {
2882
2904
  const Model = this.discriminators && doc[discriminatorKey] != null ?
2883
2905
  this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
2884
2906
  this;
@@ -2896,6 +2918,13 @@ Model.create = async function create(doc, options) {
2896
2918
 
2897
2919
  return toSave;
2898
2920
  }));
2921
+
2922
+ // chain the mapper, only if "allSettled" is used
2923
+ if (!immediateError) {
2924
+ p = p.then(presult => presult.map(v => v.status === 'fulfilled' ? v.value : v.reason));
2925
+ }
2926
+
2927
+ res = await p;
2899
2928
  }
2900
2929
 
2901
2930
 
@@ -3016,8 +3045,10 @@ Model.startSession = function() {
3016
3045
  *
3017
3046
  * #### Example:
3018
3047
  *
3019
- * const arr = [{ name: 'Star Wars' }, { name: 'The Empire Strikes Back' }];
3020
- * Movies.insertMany(arr, function(error, docs) {});
3048
+ * await Movies.insertMany([
3049
+ * { name: 'Star Wars' },
3050
+ * { name: 'The Empire Strikes Back' }
3051
+ * ]);
3021
3052
  *
3022
3053
  * @param {Array|Object|*} doc(s)
3023
3054
  * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/4.9/classes/Collection.html#insertMany)