mongoose 8.10.2 → 8.11.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.
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const assert = require('assert');
4
3
  const { Long } = require('bson');
5
4
 
6
5
  /**
@@ -13,6 +12,10 @@ const { Long } = require('bson');
13
12
  * @api private
14
13
  */
15
14
 
15
+ const MAX_BIGINT = 9223372036854775807n;
16
+ const MIN_BIGINT = -9223372036854775808n;
17
+ const ERROR_MESSAGE = `Mongoose only supports BigInts between ${MIN_BIGINT} and ${MAX_BIGINT} because MongoDB does not support arbitrary precision integers`;
18
+
16
19
  module.exports = function castBigInt(val) {
17
20
  if (val == null) {
18
21
  return val;
@@ -21,6 +24,9 @@ module.exports = function castBigInt(val) {
21
24
  return null;
22
25
  }
23
26
  if (typeof val === 'bigint') {
27
+ if (val > MAX_BIGINT || val < MIN_BIGINT) {
28
+ throw new Error(ERROR_MESSAGE);
29
+ }
24
30
  return val;
25
31
  }
26
32
 
@@ -29,8 +35,12 @@ module.exports = function castBigInt(val) {
29
35
  }
30
36
 
31
37
  if (typeof val === 'string' || typeof val === 'number') {
32
- return BigInt(val);
38
+ val = BigInt(val);
39
+ if (val > MAX_BIGINT || val < MIN_BIGINT) {
40
+ throw new Error(ERROR_MESSAGE);
41
+ }
42
+ return val;
33
43
  }
34
44
 
35
- assert.ok(false);
45
+ throw new Error(`Cannot convert value to BigInt: "${val}"`);
36
46
  };
package/lib/document.js CHANGED
@@ -3836,15 +3836,39 @@ Document.prototype.$toObject = function(options, json) {
3836
3836
  // Parent options should only bubble down for subdocuments, not populated docs
3837
3837
  options._parentOptions = this.$isSubdocument ? options : null;
3838
3838
 
3839
- // remember the root transform function
3840
- // to save it from being overwritten by sub-transform functions
3841
- // const originalTransform = options.transform;
3839
+ const schemaFieldsOnly = options._calledWithOptions.schemaFieldsOnly
3840
+ ?? options.schemaFieldsOnly
3841
+ ?? defaultOptions.schemaFieldsOnly
3842
+ ?? false;
3842
3843
 
3843
3844
  let ret;
3844
3845
  if (hasOnlyPrimitiveValues && !options.flattenObjectIds) {
3845
3846
  // Fast path: if we don't have any nested objects or arrays, we only need a
3846
3847
  // shallow clone.
3847
- ret = this.$__toObjectShallow();
3848
+ ret = this.$__toObjectShallow(schemaFieldsOnly);
3849
+ } else if (schemaFieldsOnly) {
3850
+ ret = {};
3851
+ for (const path of Object.keys(this.$__schema.paths)) {
3852
+ const value = this.$__getValue(path);
3853
+ if (value === undefined) {
3854
+ continue;
3855
+ }
3856
+ let pathToSet = path;
3857
+ let objToSet = ret;
3858
+ if (path.indexOf('.') !== -1) {
3859
+ const segments = path.split('.');
3860
+ pathToSet = segments[segments.length - 1];
3861
+ for (let i = 0; i < segments.length - 1; ++i) {
3862
+ objToSet[segments[i]] = objToSet[segments[i]] ?? {};
3863
+ objToSet = objToSet[segments[i]];
3864
+ }
3865
+ }
3866
+ if (value === null) {
3867
+ objToSet[pathToSet] = null;
3868
+ continue;
3869
+ }
3870
+ objToSet[pathToSet] = clone(value, options);
3871
+ }
3848
3872
  } else {
3849
3873
  ret = clone(this._doc, options) || {};
3850
3874
  }
@@ -3910,10 +3934,12 @@ Document.prototype.$toObject = function(options, json) {
3910
3934
  * Internal shallow clone alternative to `$toObject()`: much faster, no options processing
3911
3935
  */
3912
3936
 
3913
- Document.prototype.$__toObjectShallow = function $__toObjectShallow() {
3937
+ Document.prototype.$__toObjectShallow = function $__toObjectShallow(schemaFieldsOnly) {
3914
3938
  const ret = {};
3915
3939
  if (this._doc != null) {
3916
- for (const key of Object.keys(this._doc)) {
3940
+ const keys = schemaFieldsOnly ? Object.keys(this.$__schema.paths) : Object.keys(this._doc);
3941
+ for (const key of keys) {
3942
+ // Safe to do this even in the schemaFieldsOnly case because we assume there's no nested paths
3917
3943
  const value = this._doc[key];
3918
3944
  if (value instanceof Date) {
3919
3945
  ret[key] = new Date(value);
@@ -4066,6 +4092,7 @@ Document.prototype.$__toObjectShallow = function $__toObjectShallow() {
4066
4092
  * @param {Boolean} [options.flattenMaps=false] if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`.
4067
4093
  * @param {Boolean} [options.flattenObjectIds=false] if true, convert any ObjectIds in the result to 24 character hex strings.
4068
4094
  * @param {Boolean} [options.useProjection=false] - If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema.
4095
+ * @param {Boolean} [options.schemaFieldsOnly=false] - If true, the resulting object will only have fields that are defined in the document's schema. By default, `toObject()` returns all fields in the underlying document from MongoDB, including ones that are not listed in the schema.
4069
4096
  * @return {Object} document as a plain old JavaScript object (POJO). This object may contain ObjectIds, Maps, Dates, mongodb.Binary, Buffers, and other non-POJO values.
4070
4097
  * @see mongodb.Binary https://mongodb.github.io/node-mongodb-native/4.9/classes/Binary.html
4071
4098
  * @api public
@@ -4336,6 +4363,7 @@ function omitDeselectedFields(self, json) {
4336
4363
  * @param {Object} options
4337
4364
  * @param {Boolean} [options.flattenMaps=true] if true, convert Maps to [POJOs](https://masteringjs.io/tutorials/fundamentals/pojo). Useful if you want to `JSON.stringify()` the result.
4338
4365
  * @param {Boolean} [options.flattenObjectIds=false] if true, convert any ObjectIds in the result to 24 character hex strings.
4366
+ * @param {Boolean} [options.schemaFieldsOnly=false] - If true, the resulting object will only have fields that are defined in the document's schema. By default, `toJSON()` returns all fields in the underlying document from MongoDB, including ones that are not listed in the schema.
4339
4367
  * @return {Object}
4340
4368
  * @see Document#toObject https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject()
4341
4369
  * @see JSON.stringify() in JavaScript https://thecodebarbarian.com/the-80-20-guide-to-json-stringify-in-javascript.html
@@ -4506,6 +4534,8 @@ Document.prototype.equals = function(doc) {
4506
4534
  * @param {Object|Function} [options.match=null] Add an additional filter to the populate query. Can be a filter object containing [MongoDB query syntax](https://www.mongodb.com/docs/manual/tutorial/query-documents/), or a function that returns a filter object.
4507
4535
  * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
4508
4536
  * @param {Object} [options.options=null] Additional options like `limit` and `lean`.
4537
+ * @param {Boolean} [options.forceRepopulate=true] Set to `false` to prevent Mongoose from repopulating paths that are already populated
4538
+ * @param {Boolean} [options.ordered=false] Set to `true` to execute any populate queries one at a time, as opposed to in parallel. We recommend setting this option to `true` if using transactions, especially if also populating multiple paths or paths with multiple models. MongoDB server does **not** support multiple operations in parallel on a single transaction.
4509
4539
  * @param {Function} [callback] Callback
4510
4540
  * @see population https://mongoosejs.com/docs/populate.html
4511
4541
  * @see Query#select https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
@@ -4532,6 +4562,7 @@ Document.prototype.populate = async function populate() {
4532
4562
  }
4533
4563
 
4534
4564
  const paths = utils.object.vals(pop);
4565
+
4535
4566
  let topLevelModel = this.constructor;
4536
4567
  if (this.$__isNested) {
4537
4568
  topLevelModel = this.$__[scopeSymbol].constructor;
package/lib/model.js CHANGED
@@ -3104,11 +3104,9 @@ Model.$__insertMany = function(arr, options, callback) {
3104
3104
  const res = {
3105
3105
  acknowledged: true,
3106
3106
  insertedCount: 0,
3107
- insertedIds: {},
3108
- mongoose: {
3109
- validationErrors: validationErrors
3110
- }
3107
+ insertedIds: {}
3111
3108
  };
3109
+ decorateBulkWriteResult(res, validationErrors, validationErrors);
3112
3110
  return callback(null, res);
3113
3111
  }
3114
3112
  callback(null, []);
@@ -3161,10 +3159,7 @@ Model.$__insertMany = function(arr, options, callback) {
3161
3159
 
3162
3160
  // Decorate with mongoose validation errors in case of unordered,
3163
3161
  // because then still do `insertMany()`
3164
- res.mongoose = {
3165
- validationErrors: validationErrors,
3166
- results: results
3167
- };
3162
+ decorateBulkWriteResult(res, validationErrors, results);
3168
3163
  }
3169
3164
  return callback(null, res);
3170
3165
  }
@@ -3198,10 +3193,7 @@ Model.$__insertMany = function(arr, options, callback) {
3198
3193
  if (error.writeErrors != null) {
3199
3194
  for (let i = 0; i < error.writeErrors.length; ++i) {
3200
3195
  const originalIndex = validDocIndexToOriginalIndex.get(error.writeErrors[i].index);
3201
- error.writeErrors[i] = {
3202
- ...error.writeErrors[i],
3203
- index: originalIndex
3204
- };
3196
+ error.writeErrors[i] = { ...error.writeErrors[i], index: originalIndex };
3205
3197
  if (!ordered) {
3206
3198
  results[originalIndex] = error.writeErrors[i];
3207
3199
  }
@@ -3245,10 +3237,7 @@ Model.$__insertMany = function(arr, options, callback) {
3245
3237
  });
3246
3238
 
3247
3239
  if (rawResult && ordered === false) {
3248
- error.mongoose = {
3249
- validationErrors: validationErrors,
3250
- results: results
3251
- };
3240
+ decorateBulkWriteResult(error, validationErrors, results);
3252
3241
  }
3253
3242
 
3254
3243
  callback(error, null);
@@ -3486,8 +3475,14 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3486
3475
  then(res => ([res, null])).
3487
3476
  catch(error => ([null, error]));
3488
3477
 
3478
+ const writeErrorsByIndex = {};
3479
+ if (error?.writeErrors) {
3480
+ for (const writeError of error.writeErrors) {
3481
+ writeErrorsByIndex[writeError.err.index] = writeError;
3482
+ }
3483
+ }
3489
3484
  for (let i = 0; i < validOpIndexes.length; ++i) {
3490
- results[validOpIndexes[i]] = null;
3485
+ results[validOpIndexes[i]] = writeErrorsByIndex[i] ?? null;
3491
3486
  }
3492
3487
  if (error) {
3493
3488
  if (validationErrors.length > 0) {
@@ -4386,6 +4381,7 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
4386
4381
  * @param {Object} [options.options=null] Additional options like `limit` and `lean`.
4387
4382
  * @param {Function} [options.transform=null] Function that Mongoose will call on every populated document that allows you to transform the populated document.
4388
4383
  * @param {Boolean} [options.forceRepopulate=true] Set to `false` to prevent Mongoose from repopulating paths that are already populated
4384
+ * @param {Boolean} [options.ordered=false] Set to `true` to execute any populate queries one at a time, as opposed to in parallel. Set this option to `true` if populating multiple paths or paths with multiple models in transactions.
4389
4385
  * @return {Promise}
4390
4386
  * @api public
4391
4387
  */
@@ -4403,11 +4399,21 @@ Model.populate = async function populate(docs, paths) {
4403
4399
  }
4404
4400
 
4405
4401
  // each path has its own query options and must be executed separately
4406
- const promises = [];
4407
- for (const path of paths) {
4408
- promises.push(_populatePath(this, docs, path));
4402
+ if (paths.find(p => p.ordered)) {
4403
+ // Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
4404
+ // one transaction in parallel.
4405
+ // Note that if _any_ path has `ordered`, we make the top-level populate `ordered` as well.
4406
+ for (const path of paths) {
4407
+ await _populatePath(this, docs, path);
4408
+ }
4409
+ } else {
4410
+ // By default, populate in parallel
4411
+ const promises = [];
4412
+ for (const path of paths) {
4413
+ promises.push(_populatePath(this, docs, path));
4414
+ }
4415
+ await Promise.all(promises);
4409
4416
  }
4410
- await Promise.all(promises);
4411
4417
 
4412
4418
  return docs;
4413
4419
  };
@@ -4527,12 +4533,22 @@ async function _populatePath(model, docs, populateOptions) {
4527
4533
  return;
4528
4534
  }
4529
4535
 
4530
- const promises = [];
4531
- for (const arr of params) {
4532
- promises.push(_execPopulateQuery.apply(null, arr).then(valsFromDb => { vals = vals.concat(valsFromDb); }));
4536
+ if (populateOptions.ordered) {
4537
+ // Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
4538
+ // one transaction in parallel.
4539
+ for (const arr of params) {
4540
+ await _execPopulateQuery.apply(null, arr).then(valsFromDb => { vals = vals.concat(valsFromDb); });
4541
+ }
4542
+ } else {
4543
+ // By default, populate in parallel
4544
+ const promises = [];
4545
+ for (const arr of params) {
4546
+ promises.push(_execPopulateQuery.apply(null, arr).then(valsFromDb => { vals = vals.concat(valsFromDb); }));
4547
+ }
4548
+
4549
+ await Promise.all(promises);
4533
4550
  }
4534
4551
 
4535
- await Promise.all(promises);
4536
4552
 
4537
4553
  for (const arr of params) {
4538
4554
  const mod = arr[0];
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Double type constructor
3
+ *
4
+ * #### Example:
5
+ *
6
+ * const pi = new mongoose.Types.Double(3.1415);
7
+ *
8
+ * @constructor Double
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ module.exports = require('bson').Double;
@@ -12,6 +12,7 @@ exports.Document = // @deprecate
12
12
  exports.Embedded = require('./arraySubdocument');
13
13
 
14
14
  exports.DocumentArray = require('./documentArray');
15
+ exports.Double = require('./double');
15
16
  exports.Decimal128 = require('./decimal128');
16
17
  exports.ObjectId = require('./objectid');
17
18
 
package/lib/utils.js CHANGED
@@ -551,8 +551,8 @@ exports.populate = function populate(path, select, model, match, options, subPop
551
551
  };
552
552
  }
553
553
 
554
- if (typeof obj.path !== 'string') {
555
- throw new TypeError('utils.populate: invalid path. Expected string. Got typeof `' + typeof path + '`');
554
+ if (typeof obj.path !== 'string' && !(Array.isArray(obj.path) && obj.path.every(el => typeof el === 'string'))) {
555
+ throw new TypeError('utils.populate: invalid path. Expected string or array of strings. Got typeof `' + typeof path + '`');
556
556
  }
557
557
 
558
558
  return _populateObj(obj);
@@ -600,7 +600,11 @@ function _populateObj(obj) {
600
600
  }
601
601
 
602
602
  const ret = [];
603
- const paths = oneSpaceRE.test(obj.path) ? obj.path.split(manySpaceRE) : [obj.path];
603
+ const paths = oneSpaceRE.test(obj.path)
604
+ ? obj.path.split(manySpaceRE)
605
+ : Array.isArray(obj.path)
606
+ ? obj.path
607
+ : [obj.path];
604
608
  if (obj.options != null) {
605
609
  obj.options = clone(obj.options);
606
610
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "8.10.2",
4
+ "version": "8.11.0",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
package/types/index.d.ts CHANGED
@@ -204,30 +204,32 @@ declare module 'mongoose' {
204
204
  }
205
205
 
206
206
  export interface ToObjectOptions<THydratedDocumentType = HydratedDocument<unknown>> {
207
- /** apply all getters (path and virtual getters) */
208
- getters?: boolean;
209
- /** apply virtual getters (can override getters option) */
210
- virtuals?: boolean | string[];
211
207
  /** if `options.virtuals = true`, you can set `options.aliases = false` to skip applying aliases. This option is a no-op if `options.virtuals = false`. */
212
208
  aliases?: boolean;
209
+ /** if true, replace any conventionally populated paths with the original id in the output. Has no affect on virtual populated paths. */
210
+ depopulate?: boolean;
211
+ /** if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`. */
212
+ flattenMaps?: boolean;
213
+ /** if true, convert any ObjectIds in the result to 24 character hex strings. */
214
+ flattenObjectIds?: boolean;
215
+ /** apply all getters (path and virtual getters) */
216
+ getters?: boolean;
213
217
  /** remove empty objects (defaults to true) */
214
218
  minimize?: boolean;
219
+ /** If true, the resulting object will only have fields that are defined in the document's schema. By default, `toJSON()` & `toObject()` returns all fields in the underlying document from MongoDB, including ones that are not listed in the schema. */
220
+ schemaFieldsOnly?: boolean;
215
221
  /** if set, mongoose will call this function to allow you to transform the returned object */
216
222
  transform?: boolean | ((
217
223
  doc: THydratedDocumentType,
218
224
  ret: Record<string, any>,
219
225
  options: ToObjectOptions<THydratedDocumentType>
220
226
  ) => any);
221
- /** if true, replace any conventionally populated paths with the original id in the output. Has no affect on virtual populated paths. */
222
- depopulate?: boolean;
223
- /** if false, exclude the version key (`__v` by default) from the output */
224
- versionKey?: boolean;
225
- /** if true, convert Maps to POJOs. Useful if you want to `JSON.stringify()` the result of `toObject()`. */
226
- flattenMaps?: boolean;
227
- /** if true, convert any ObjectIds in the result to 24 character hex strings. */
228
- flattenObjectIds?: boolean;
229
227
  /** If true, omits fields that are excluded in this document's projection. Unless you specified a projection, this will omit any field that has `select: false` in the schema. */
230
228
  useProjection?: boolean;
229
+ /** if false, exclude the version key (`__v` by default) from the output */
230
+ versionKey?: boolean;
231
+ /** apply virtual getters (can override getters option) */
232
+ virtuals?: boolean | string[];
231
233
  }
232
234
 
233
235
  export type DiscriminatorModel<M, T> = T extends Model<infer T, infer TQueryHelpers, infer TInstanceMethods, infer TVirtuals>
@@ -312,14 +312,15 @@ type ResolvePathType<PathValueType, Options extends SchemaTypeOptions<PathValueT
312
312
  IfEquals<PathValueType, BigInt> extends true ? bigint :
313
313
  PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint :
314
314
  PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer :
315
- IfEquals<PathValueType, Schema.Types.UUID> extends true ? Buffer :
316
- PathValueType extends MapConstructor | 'Map' ? Map<string, ResolvePathType<Options['of']>> :
317
- IfEquals<PathValueType, typeof Schema.Types.Map> extends true ? Map<string, ResolvePathType<Options['of']>> :
318
- PathValueType extends ArrayConstructor ? any[] :
319
- PathValueType extends typeof Schema.Types.Mixed ? any:
320
- IfEquals<PathValueType, ObjectConstructor> extends true ? any:
321
- IfEquals<PathValueType, {}> extends true ? any:
322
- PathValueType extends typeof SchemaType ? PathValueType['prototype'] :
323
- PathValueType extends Record<string, any> ? ObtainDocumentType<PathValueType, any, { typeKey: TypeKey }> :
324
- unknown,
315
+ PathValueType extends 'double' | 'Double' | typeof Schema.Types.Double ? Types.Double :
316
+ IfEquals<PathValueType, Schema.Types.UUID> extends true ? Buffer :
317
+ PathValueType extends MapConstructor | 'Map' ? Map<string, ResolvePathType<Options['of']>> :
318
+ IfEquals<PathValueType, typeof Schema.Types.Map> extends true ? Map<string, ResolvePathType<Options['of']>> :
319
+ PathValueType extends ArrayConstructor ? any[] :
320
+ PathValueType extends typeof Schema.Types.Mixed ? any:
321
+ IfEquals<PathValueType, ObjectConstructor> extends true ? any:
322
+ IfEquals<PathValueType, {}> extends true ? any:
323
+ PathValueType extends typeof SchemaType ? PathValueType['prototype'] :
324
+ PathValueType extends Record<string, any> ? ObtainDocumentType<PathValueType, any, { typeKey: TypeKey }> :
325
+ unknown,
325
326
  TypeHint>;
package/types/models.d.ts CHANGED
@@ -308,7 +308,7 @@ declare module 'mongoose' {
308
308
  bulkWrite<DocContents = TRawDocType>(
309
309
  writes: Array<AnyBulkWriteOperation<DocContents extends Document ? any : (DocContents extends {} ? DocContents : any)>>,
310
310
  options: MongooseBulkWriteOptions & { ordered: false }
311
- ): Promise<mongodb.BulkWriteResult & { mongoose?: { validationErrors: Error[], results: Array<Error | null> } }>;
311
+ ): Promise<mongodb.BulkWriteResult & { mongoose?: { validationErrors: Error[], results: Array<Error | mongodb.WriteError | null> } }>;
312
312
  bulkWrite<DocContents = TRawDocType>(
313
313
  writes: Array<AnyBulkWriteOperation<DocContents extends Document ? any : (DocContents extends {} ? DocContents : any)>>,
314
314
  options?: MongooseBulkWriteOptions
@@ -39,6 +39,12 @@ declare module 'mongoose' {
39
39
  foreignField?: string;
40
40
  /** Set to `false` to prevent Mongoose from repopulating paths that are already populated */
41
41
  forceRepopulate?: boolean;
42
+ /**
43
+ * Set to `true` to execute any populate queries one at a time, as opposed to in parallel.
44
+ * We recommend setting this option to `true` if using transactions, especially if also populating multiple paths or paths with multiple models.
45
+ * MongoDB server does **not** support multiple operations in parallel on a single transaction.
46
+ */
47
+ ordered?: boolean;
42
48
  }
43
49
 
44
50
  interface PopulateOption {
package/types/types.d.ts CHANGED
@@ -104,5 +104,7 @@ declare module 'mongoose' {
104
104
  }
105
105
 
106
106
  class UUID extends bson.UUID {}
107
+
108
+ class Double extends bson.Double {}
107
109
  }
108
110
  }