mongoose 6.4.7 → 6.5.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.
@@ -13,7 +13,7 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) {
13
13
  return deleted;
14
14
  }
15
15
 
16
- for (const modifiedPath of Object.keys(doc.$__.activePaths.states.modify)) {
16
+ for (const modifiedPath of Object.keys(doc.$__.activePaths.getStatePaths('modify'))) {
17
17
  if (skipDocArrays) {
18
18
  const schemaType = doc.$__schema.path(modifiedPath);
19
19
  if (schemaType && schemaType.$isMongooseDocumentArray) {
@@ -21,13 +21,13 @@ module.exports = function cleanModifiedSubpaths(doc, path, options) {
21
21
  }
22
22
  }
23
23
  if (modifiedPath.startsWith(path + '.')) {
24
- delete doc.$__.activePaths.states.modify[modifiedPath];
24
+ doc.$__.activePaths.clearPath(modifiedPath);
25
25
  ++deleted;
26
26
 
27
27
  if (doc.$isSubdocument) {
28
28
  const owner = doc.ownerDocument();
29
29
  const fullPath = doc.$__fullPath(modifiedPath);
30
- delete owner.$__.activePaths.states.modify[fullPath];
30
+ owner.$__.activePaths.clearPath(fullPath);
31
31
  }
32
32
  }
33
33
  }
@@ -17,6 +17,13 @@ const isPOJO = utils.isPOJO;
17
17
  exports.compile = compile;
18
18
  exports.defineKey = defineKey;
19
19
 
20
+ const _isEmptyOptions = Object.freeze({
21
+ minimize: true,
22
+ virtuals: false,
23
+ getters: false,
24
+ transform: false
25
+ });
26
+
20
27
  /*!
21
28
  * Compiles schemas.
22
29
  */
@@ -130,12 +137,6 @@ function defineKey({ prop, subprops, prototype, prefix, options }) {
130
137
  value: true
131
138
  });
132
139
 
133
- const _isEmptyOptions = Object.freeze({
134
- minimize: true,
135
- virtuals: false,
136
- getters: false,
137
- transform: false
138
- });
139
140
  Object.defineProperty(nested, '$isEmpty', {
140
141
  enumerable: false,
141
142
  configurable: true,
@@ -0,0 +1,8 @@
1
+ 'use strict';
2
+
3
+ module.exports = function firstKey(obj) {
4
+ if (obj == null) {
5
+ return null;
6
+ }
7
+ return Object.keys(obj)[0];
8
+ };
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ module.exports = function applyDefaultsToPOJO(doc, schema) {
4
+ const paths = Object.keys(schema.paths);
5
+ const plen = paths.length;
6
+
7
+ for (let i = 0; i < plen; ++i) {
8
+ let curPath = '';
9
+ const p = paths[i];
10
+
11
+ const type = schema.paths[p];
12
+ const path = type.splitPath();
13
+ const len = path.length;
14
+ let doc_ = doc;
15
+ for (let j = 0; j < len; ++j) {
16
+ if (doc_ == null) {
17
+ break;
18
+ }
19
+
20
+ const piece = path[j];
21
+ curPath += (!curPath.length ? '' : '.') + piece;
22
+
23
+ if (j === len - 1) {
24
+ if (typeof doc_[piece] !== 'undefined') {
25
+ if (type.$isSingleNested) {
26
+ applyDefaultsToPOJO(doc_[piece], type.caster.schema);
27
+ } else if (type.$isMongooseDocumentArray && Array.isArray(doc_[piece])) {
28
+ doc_[piece].forEach(el => applyDefaultsToPOJO(el, type.schema));
29
+ }
30
+
31
+ break;
32
+ }
33
+
34
+ const def = type.getDefault(doc, false, { skipCast: true });
35
+ if (typeof def !== 'undefined') {
36
+ doc_[piece] = def;
37
+
38
+ if (type.$isSingleNested) {
39
+ applyDefaultsToPOJO(def, type.caster.schema);
40
+ } else if (type.$isMongooseDocumentArray && Array.isArray(def)) {
41
+ def.forEach(el => applyDefaultsToPOJO(el, type.schema));
42
+ }
43
+ }
44
+ } else {
45
+ if (doc_[piece] == null) {
46
+ doc_[piece] = {};
47
+ }
48
+ doc_ = doc_[piece];
49
+ }
50
+ }
51
+ }
52
+ };
@@ -20,7 +20,7 @@ module.exports = function castBulkWrite(originalModel, op, options) {
20
20
  const model = decideModelByObject(originalModel, op['insertOne']['document']);
21
21
 
22
22
  const doc = new model(op['insertOne']['document']);
23
- if (model.schema.options.timestamps) {
23
+ if (model.schema.options.timestamps && options.timestamps !== false) {
24
24
  doc.initializeTimestamps();
25
25
  }
26
26
  if (options.session != null) {
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ module.exports = function pushNestedArrayPaths(paths, nestedArray, path) {
4
+ if (nestedArray == null) {
5
+ return;
6
+ }
7
+
8
+ for (let i = 0; i < nestedArray.length; ++i) {
9
+ if (Array.isArray(nestedArray[i])) {
10
+ pushNestedArrayPaths(paths, nestedArray[i], path + '.' + i);
11
+ } else {
12
+ paths.push(path + '.' + i);
13
+ }
14
+ }
15
+ };
package/lib/internal.js CHANGED
@@ -13,8 +13,9 @@ function InternalCache() {
13
13
  this.activePaths = new ActiveRoster();
14
14
  }
15
15
 
16
+ InternalCache.prototype.strictMode = true;
17
+
16
18
  InternalCache.prototype.fullPath = undefined;
17
- InternalCache.prototype.strictMode = undefined;
18
19
  InternalCache.prototype.selected = undefined;
19
20
  InternalCache.prototype.shardval = undefined;
20
21
  InternalCache.prototype.saveError = undefined;
@@ -28,6 +29,7 @@ InternalCache.prototype._id = undefined;
28
29
  InternalCache.prototype.ownerDocument = undefined;
29
30
  InternalCache.prototype.populate = undefined; // what we want to populate in this doc
30
31
  InternalCache.prototype.populated = undefined;// the _ids that have been populated
32
+ InternalCache.prototype.primitiveAtomics = undefined;
31
33
 
32
34
  /**
33
35
  * If `false`, this document was not the result of population.
package/lib/model.js CHANGED
@@ -22,6 +22,8 @@ const ServerSelectionError = require('./error/serverSelection');
22
22
  const ValidationError = require('./error/validation');
23
23
  const VersionError = require('./error/version');
24
24
  const ParallelSaveError = require('./error/parallelSave');
25
+ const applyDefaultsHelper = require('./helpers/document/applyDefaults');
26
+ const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO');
25
27
  const applyQueryMiddleware = require('./helpers/query/applyQueryMiddleware');
26
28
  const applyHooks = require('./helpers/model/applyHooks');
27
29
  const applyMethods = require('./helpers/model/applyMethods');
@@ -35,6 +37,7 @@ const castBulkWrite = require('./helpers/model/castBulkWrite');
35
37
  const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
36
38
  const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
37
39
  const discriminator = require('./helpers/model/discriminator');
40
+ const firstKey = require('./helpers/firstKey');
38
41
  const each = require('./helpers/each');
39
42
  const get = require('./helpers/get');
40
43
  const getConstructorName = require('./helpers/getConstructorName');
@@ -54,6 +57,7 @@ const leanPopulateMap = require('./helpers/populate/leanPopulateMap');
54
57
  const modifiedPaths = require('./helpers/update/modifiedPaths');
55
58
  const parallelLimit = require('./helpers/parallelLimit');
56
59
  const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscriminatorPipeline');
60
+ const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
57
61
  const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
58
62
  const setDottedPath = require('./helpers/path/setDottedPath');
59
63
  const util = require('util');
@@ -589,6 +593,7 @@ function operand(self, where, delta, data, val, op) {
589
593
  case '$pullAll':
590
594
  case '$push':
591
595
  case '$addToSet':
596
+ case '$inc':
592
597
  break;
593
598
  default:
594
599
  // nothing to do
@@ -764,15 +769,21 @@ Model.prototype.$__delta = function() {
764
769
  value = value.toObject();
765
770
  operand(this, where, delta, data, value);
766
771
  } else {
767
- value = utils.clone(value, {
768
- depopulate: true,
769
- transform: false,
770
- virtuals: false,
771
- getters: false,
772
- omitUndefined: true,
773
- _isNested: true
774
- });
775
- operand(this, where, delta, data, value);
772
+ if (this.$__.primitiveAtomics && this.$__.primitiveAtomics[data.path] != null) {
773
+ const val = this.$__.primitiveAtomics[data.path];
774
+ const op = firstKey(val);
775
+ operand(this, where, delta, data, val[op], op);
776
+ } else {
777
+ value = utils.clone(value, {
778
+ depopulate: true,
779
+ transform: false,
780
+ virtuals: false,
781
+ getters: false,
782
+ omitUndefined: true,
783
+ _isNested: true
784
+ });
785
+ operand(this, where, delta, data, value);
786
+ }
776
787
  }
777
788
  }
778
789
 
@@ -3167,6 +3178,7 @@ Model.create = function create(doc, options, callback) {
3167
3178
  *
3168
3179
  * @param {Array} [pipeline]
3169
3180
  * @param {Object} [options] see the [mongodb driver options](https://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#watch)
3181
+ * @param {Boolean} [options.hydrate=false] if true and `fullDocument: 'updateLookup'` is set, Mongoose will automatically hydrate `fullDocument` into a fully fledged Mongoose document
3170
3182
  * @return {ChangeStream} mongoose-specific change stream wrapper, inherits from EventEmitter
3171
3183
  * @api public
3172
3184
  */
@@ -3191,6 +3203,9 @@ Model.watch = function(pipeline, options) {
3191
3203
  }
3192
3204
  };
3193
3205
 
3206
+ options = options || {};
3207
+ options.model = this;
3208
+
3194
3209
  return new ChangeStream(changeStreamThunk, pipeline, options);
3195
3210
  };
3196
3211
 
@@ -3579,41 +3594,50 @@ Model.bulkWrite = function(ops, options, callback) {
3579
3594
  *
3580
3595
  * @param {Array<Document>} documents
3581
3596
  * @param {Object} [options] options passed to the underlying `bulkWrite()`
3597
+ * @param {Boolean} [options.timestamps] defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
3582
3598
  * @param {ClientSession} [options.session=null] The session associated with this bulk write. See [transactions docs](/docs/transactions.html).
3583
3599
  * @param {String|number} [options.w=1] The [write concern](https://docs.mongodb.com/manual/reference/write-concern/). See [`Query#w()`](/docs/api.html#query_Query-w) for more information.
3584
3600
  * @param {number} [options.wtimeout=null] The [write concern timeout](https://docs.mongodb.com/manual/reference/write-concern/#wtimeout).
3585
3601
  * @param {Boolean} [options.j=true] If false, disable [journal acknowledgement](https://docs.mongodb.com/manual/reference/write-concern/#j-option)
3586
3602
  *
3587
3603
  */
3588
- Model.bulkSave = function(documents, options) {
3589
- const preSavePromises = documents.map(buildPreSavePromise);
3590
-
3591
- const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true });
3592
-
3593
- let bulkWriteResultPromise;
3594
- return Promise.all(preSavePromises)
3595
- .then(() => bulkWriteResultPromise = this.bulkWrite(writeOperations, options))
3596
- .then(() => documents.map(buildSuccessfulWriteHandlerPromise))
3597
- .then(() => bulkWriteResultPromise)
3598
- .catch((err) => {
3599
- if (!(err && err.writeErrors && err.writeErrors.length)) {
3600
- throw err;
3601
- }
3602
- return Promise.all(
3603
- documents.map((document) => {
3604
- const documentError = err.writeErrors.find(writeError => {
3605
- const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
3606
- return writeErrorDocumentId.toString() === document._id.toString();
3607
- });
3604
+ Model.bulkSave = async function(documents, options) {
3605
+ options = options || {};
3608
3606
 
3609
- if (documentError == null) {
3610
- return buildSuccessfulWriteHandlerPromise(document);
3611
- }
3612
- })
3613
- ).then(() => {
3614
- throw err;
3607
+ const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true, timestamps: options.timestamps });
3608
+
3609
+ if (options.timestamps != null) {
3610
+ for (const document of documents) {
3611
+ document.$__.saveOptions = document.$__.saveOptions || {};
3612
+ document.$__.saveOptions.timestamps = options.timestamps;
3613
+ }
3614
+ }
3615
+
3616
+ await Promise.all(documents.map(buildPreSavePromise));
3617
+
3618
+ const { bulkWriteResult, bulkWriteError } = await this.bulkWrite(writeOperations, options).then(
3619
+ (res) => ({ bulkWriteResult: res, bulkWriteError: null }),
3620
+ (err) => ({ bulkWriteResult: null, bulkWriteError: err })
3621
+ );
3622
+
3623
+ await Promise.all(
3624
+ documents.map(async(document) => {
3625
+ const documentError = bulkWriteError && bulkWriteError.writeErrors.find(writeError => {
3626
+ const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
3627
+ return writeErrorDocumentId.toString() === document._id.toString();
3615
3628
  });
3616
- });
3629
+
3630
+ if (documentError == null) {
3631
+ await handleSuccessfulWrite(document);
3632
+ }
3633
+ })
3634
+ );
3635
+
3636
+ if (bulkWriteError && bulkWriteError.writeErrors && bulkWriteError.writeErrors.length) {
3637
+ throw bulkWriteError;
3638
+ }
3639
+
3640
+ return bulkWriteResult;
3617
3641
  };
3618
3642
 
3619
3643
  function buildPreSavePromise(document) {
@@ -3628,26 +3652,137 @@ function buildPreSavePromise(document) {
3628
3652
  });
3629
3653
  }
3630
3654
 
3631
- function buildSuccessfulWriteHandlerPromise(document) {
3655
+ function handleSuccessfulWrite(document) {
3632
3656
  return new Promise((resolve, reject) => {
3633
- handleSuccessfulWrite(document, resolve, reject);
3657
+ if (document.$isNew) {
3658
+ _setIsNew(document, false);
3659
+ }
3660
+
3661
+ document.$__reset();
3662
+ document.schema.s.hooks.execPost('save', document, {}, (err) => {
3663
+ if (err) {
3664
+ reject(err);
3665
+ return;
3666
+ }
3667
+ resolve();
3668
+ });
3669
+
3634
3670
  });
3635
3671
  }
3636
3672
 
3637
- function handleSuccessfulWrite(document, resolve, reject) {
3638
- if (document.$isNew) {
3639
- _setIsNew(document, false);
3673
+ /**
3674
+ * Apply defaults to the given document or POJO.
3675
+ *
3676
+ * @param {Object|Document} obj object or document to apply defaults on
3677
+ * @returns {Object|Document}
3678
+ * @api public
3679
+ */
3680
+
3681
+ Model.applyDefaults = function applyDefaults(doc) {
3682
+ if (doc.$__ != null) {
3683
+ applyDefaultsHelper(doc, doc.$__.fields, doc.$__.exclude);
3684
+
3685
+ for (const subdoc of doc.$getAllSubdocs()) {
3686
+ applyDefaults(subdoc, subdoc.$__.fields, subdoc.$__.exclude);
3687
+ }
3688
+
3689
+ return doc;
3640
3690
  }
3641
3691
 
3642
- document.$__reset();
3643
- document.schema.s.hooks.execPost('save', document, {}, (err) => {
3644
- if (err) {
3645
- reject(err);
3646
- return;
3692
+ applyDefaultsToPOJO(doc, this.schema);
3693
+
3694
+ return doc;
3695
+ };
3696
+
3697
+ /**
3698
+ * Cast the given POJO to the model's schema
3699
+ *
3700
+ * #### Example:
3701
+ * const Test = mongoose.model('Test', Schema({ num: Number }));
3702
+ *
3703
+ * const obj = Test.castObject({ num: '42' });
3704
+ * obj.num; // 42 as a number
3705
+ *
3706
+ * Test.castObject({ num: 'not a number' }); // Throws a ValidationError
3707
+ *
3708
+ * @param {Object} obj object or document to cast
3709
+ * @returns {Object} POJO casted to the model's schema
3710
+ * @throws {ValidationError} if casting failed for at least one path
3711
+ * @api public
3712
+ */
3713
+
3714
+ Model.castObject = function castObject(obj) {
3715
+ const ret = {};
3716
+
3717
+ const schema = this.schema;
3718
+ const paths = Object.keys(schema.paths);
3719
+
3720
+ for (const path of paths) {
3721
+ const schemaType = schema.path(path);
3722
+ if (!schemaType || !schemaType.$isMongooseArray) {
3723
+ continue;
3647
3724
  }
3648
- resolve();
3649
- });
3650
- }
3725
+
3726
+ const val = get(obj, path);
3727
+ pushNestedArrayPaths(paths, val, path);
3728
+ }
3729
+
3730
+ let error = null;
3731
+
3732
+ for (const path of paths) {
3733
+ const schemaType = schema.path(path);
3734
+ if (schemaType == null) {
3735
+ continue;
3736
+ }
3737
+
3738
+ let val = get(obj, path, void 0);
3739
+
3740
+ if (val == null) {
3741
+ continue;
3742
+ }
3743
+
3744
+ const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
3745
+ let cur = ret;
3746
+ for (let i = 0; i < pieces.length - 1; ++i) {
3747
+ if (cur[pieces[i]] == null) {
3748
+ cur[pieces[i]] = isNaN(pieces[i + 1]) ? {} : [];
3749
+ }
3750
+ cur = cur[pieces[i]];
3751
+ }
3752
+
3753
+ if (schemaType.$isMongooseDocumentArray) {
3754
+ continue;
3755
+ }
3756
+ if (schemaType.$isSingleNested || schemaType.$isMongooseDocumentArrayElement) {
3757
+ try {
3758
+ val = Model.castObject.call(schemaType.caster, val);
3759
+ } catch (err) {
3760
+ error = error || new ValidationError();
3761
+ error.addError(path, err);
3762
+ continue;
3763
+ }
3764
+
3765
+ cur[pieces[pieces.length - 1]] = val;
3766
+ continue;
3767
+ }
3768
+
3769
+ try {
3770
+ val = schemaType.cast(val);
3771
+ cur[pieces[pieces.length - 1]] = val;
3772
+ } catch (err) {
3773
+ error = error || new ValidationError();
3774
+ error.addError(path, err);
3775
+
3776
+ continue;
3777
+ }
3778
+ }
3779
+
3780
+ if (error != null) {
3781
+ throw error;
3782
+ }
3783
+
3784
+ return ret;
3785
+ };
3651
3786
 
3652
3787
  /**
3653
3788
  * Build bulk write operations for `bulkSave()`.
@@ -3655,9 +3790,11 @@ function handleSuccessfulWrite(document, resolve, reject) {
3655
3790
  * @param {Array<Document>} documents The array of documents to build write operations of
3656
3791
  * @param {Object} options
3657
3792
  * @param {Boolean} options.skipValidation defaults to `false`, when set to true, building the write operations will bypass validating the documents.
3793
+ * @param {Boolean} options.timestamps defaults to `null`, when set to false, mongoose will not add/update timestamps to the documents.
3658
3794
  * @return {Array<Promise>} Returns a array of all Promises the function executes to be awaited.
3659
3795
  * @api private
3660
3796
  */
3797
+
3661
3798
  Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, options) {
3662
3799
  if (!Array.isArray(documents)) {
3663
3800
  throw new Error(`bulkSave expects an array of documents to be passed, received \`${documents}\` instead`);
@@ -3678,9 +3815,9 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3678
3815
 
3679
3816
  const isANewDocument = document.isNew;
3680
3817
  if (isANewDocument) {
3681
- accumulator.push({
3682
- insertOne: { document }
3683
- });
3818
+ const writeOperation = { insertOne: { document } };
3819
+ utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
3820
+ accumulator.push(writeOperation);
3684
3821
 
3685
3822
  return accumulator;
3686
3823
  }
@@ -3695,13 +3832,9 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3695
3832
  _applyCustomWhere(document, where);
3696
3833
 
3697
3834
  document.$__version(where, delta);
3698
-
3699
- accumulator.push({
3700
- updateOne: {
3701
- filter: where,
3702
- update: changes
3703
- }
3704
- });
3835
+ const writeOperation = { updateOne: { filter: where, update: changes } };
3836
+ utils.injectTimestampsOption(writeOperation.updateOne, options.timestamps);
3837
+ accumulator.push(writeOperation);
3705
3838
 
3706
3839
  return accumulator;
3707
3840
  }
@@ -3720,6 +3853,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3720
3853
  }
3721
3854
  };
3722
3855
 
3856
+
3723
3857
  /**
3724
3858
  * Shortcut for creating a new Document from existing raw data, pre-saved in the DB.
3725
3859
  * The document returned has no paths marked as modified initially.
@@ -3731,11 +3865,13 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3731
3865
  *
3732
3866
  * @param {Object} obj
3733
3867
  * @param {Object|String|String[]} [projection] optional projection containing which fields should be selected for this document
3868
+ * @param {Object} [options] optional options
3869
+ * @param {Boolean} [options.setters=false] if true, apply schema setters when hydrating
3734
3870
  * @return {Document} document instance
3735
3871
  * @api public
3736
3872
  */
3737
3873
 
3738
- Model.hydrate = function(obj, projection) {
3874
+ Model.hydrate = function(obj, projection, options) {
3739
3875
  _checkContext(this, 'hydrate');
3740
3876
 
3741
3877
  if (projection != null) {
@@ -3746,7 +3882,7 @@ Model.hydrate = function(obj, projection) {
3746
3882
  }
3747
3883
 
3748
3884
  const document = require('./queryhelpers').createModel(this, obj, projection);
3749
- document.$init(obj);
3885
+ document.$init(obj, options);
3750
3886
  return document;
3751
3887
  };
3752
3888
 
@@ -4245,7 +4381,7 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
4245
4381
  }
4246
4382
 
4247
4383
  const val = get(obj, path);
4248
- pushNestedArrayPaths(val, path);
4384
+ pushNestedArrayPaths(paths, val, path);
4249
4385
  }
4250
4386
 
4251
4387
  let remaining = paths.length;
@@ -4258,7 +4394,7 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
4258
4394
  continue;
4259
4395
  }
4260
4396
 
4261
- const pieces = path.split('.');
4397
+ const pieces = path.indexOf('.') === -1 ? [path] : path.split('.');
4262
4398
  let cur = obj;
4263
4399
  for (let i = 0; i < pieces.length - 1; ++i) {
4264
4400
  cur = cur[pieces[i]];
@@ -4282,32 +4418,12 @@ Model.validate = function validate(obj, pathsToValidate, context, callback) {
4282
4418
  schemaType.doValidate(val, err => {
4283
4419
  if (err) {
4284
4420
  error = error || new ValidationError();
4285
- if (err instanceof ValidationError) {
4286
- for (const _err of Object.keys(err.errors)) {
4287
- error.addError(`${path}.${err.errors[_err].path}`, _err);
4288
- }
4289
- } else {
4290
- error.addError(err.path, err);
4291
- }
4421
+ error.addError(path, err);
4292
4422
  }
4293
4423
  _checkDone();
4294
4424
  }, context, { path: path });
4295
4425
  }
4296
4426
 
4297
- function pushNestedArrayPaths(nestedArray, path) {
4298
- if (nestedArray == null) {
4299
- return;
4300
- }
4301
-
4302
- for (let i = 0; i < nestedArray.length; ++i) {
4303
- if (Array.isArray(nestedArray[i])) {
4304
- pushNestedArrayPaths(nestedArray[i], path + '.' + i);
4305
- } else {
4306
- paths.push(path + '.' + i);
4307
- }
4308
- }
4309
- }
4310
-
4311
4427
  function _checkDone() {
4312
4428
  if (--remaining <= 0) {
4313
4429
  return cb(error);
@@ -52,6 +52,25 @@ Object.defineProperty(SchemaArrayOptions.prototype, 'enum', opts);
52
52
 
53
53
  Object.defineProperty(SchemaArrayOptions.prototype, 'of', opts);
54
54
 
55
+ /**
56
+ * If set to `false`, will always deactivate casting non-array values to arrays.
57
+ * If set to `true`, will cast non-array values to arrays if `init` and `SchemaArray.options.castNonArrays` are also `true`
58
+ *
59
+ * #### Example:
60
+ *
61
+ * const Model = db.model('Test', new Schema({ x1: { castNonArrays: false, type: [String] } }));
62
+ * const doc = new Model({ x1: "some non-array value" });
63
+ * await doc.validate(); // Errors with "CastError"
64
+ *
65
+ * @api public
66
+ * @property castNonArrays
67
+ * @memberOf SchemaArrayOptions
68
+ * @type {Boolean}
69
+ * @instance
70
+ */
71
+
72
+ Object.defineProperty(SchemaArrayOptions.prototype, 'castNonArrays', opts);
73
+
55
74
  /*!
56
75
  * ignore
57
76
  */
@@ -23,14 +23,14 @@ module.exports = function trackTransaction(schema) {
23
23
  initialState.versionKey = this.get(this.$__schema.options.versionKey);
24
24
  }
25
25
 
26
- initialState.modifiedPaths = new Set(Object.keys(this.$__.activePaths.states.modify));
26
+ initialState.modifiedPaths = new Set(Object.keys(this.$__.activePaths.getStatePaths('modify')));
27
27
  initialState.atomics = _getAtomics(this);
28
28
 
29
29
  session[sessionNewDocuments].set(this, initialState);
30
30
  } else {
31
31
  const state = session[sessionNewDocuments].get(this);
32
32
 
33
- for (const path of Object.keys(this.$__.activePaths.states.modify)) {
33
+ for (const path of Object.keys(this.$__.activePaths.getStatePaths('modify'))) {
34
34
  state.modifiedPaths.add(path);
35
35
  }
36
36
  state.atomics = _getAtomics(this, state.atomics);
package/lib/query.js CHANGED
@@ -1632,6 +1632,9 @@ Query.prototype.setOptions = function(options, overwrite) {
1632
1632
  this._mongooseOptions.defaults = options.defaults;
1633
1633
  // deleting options.defaults will cause 7287 to fail
1634
1634
  }
1635
+ if (options.lean == null && this.schema && 'lean' in this.schema.options) {
1636
+ this._mongooseOptions.lean = this.schema.options.lean;
1637
+ }
1635
1638
 
1636
1639
  if (typeof options.limit === 'string') {
1637
1640
  try {
@@ -393,7 +393,8 @@ SchemaArray.prototype.cast = function(value, doc, init, prev, options) {
393
393
  return value;
394
394
  }
395
395
 
396
- if (init || SchemaArray.options.castNonArrays) {
396
+ const castNonArraysOption = this.options.castNonArrays != null ? this.options.castNonArrays : SchemaArray.options.castNonArrays;
397
+ if (init || castNonArraysOption) {
397
398
  // gh-2442: if we're loading this from the db and its not an array, mark
398
399
  // the whole array as modified.
399
400
  if (!!doc && !!init) {