mongoose 5.0.13 → 5.0.17
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 +43 -0
- package/lib/connection.js +1 -1
- package/lib/document.js +54 -33
- package/lib/model.js +65 -51
- package/lib/plugins/idGetter.js +17 -3
- package/lib/query.js +32 -24
- package/lib/queryhelpers.js +1 -1
- package/lib/schema/array.js +4 -14
- package/lib/schema/decimal128.js +4 -0
- package/lib/schema/documentarray.js +6 -5
- package/lib/schema/number.js +3 -0
- package/lib/schema.js +31 -51
- package/lib/services/model/applyHooks.js +16 -0
- package/lib/services/model/discriminator.js +3 -1
- package/lib/services/populate/getSchemaTypes.js +11 -11
- package/lib/services/populate/getVirtual.js +51 -0
- package/lib/services/update/modifiedPaths.js +27 -0
- package/lib/types/array.js +6 -5
- package/lib/types/documentarray.js +7 -0
- package/lib/utils.js +18 -3
- package/migrating_to_5.md +37 -1
- package/package.json +4 -4
package/History.md
CHANGED
|
@@ -1,3 +1,46 @@
|
|
|
1
|
+
5.0.17 / 2018-04-30
|
|
2
|
+
===================
|
|
3
|
+
* docs(migration): certain chars in passwords may cause connection failures #6401 [markstos](https://github.com/markstos)
|
|
4
|
+
* fix(document): don't throw when `push()` on a nested doc array #6398
|
|
5
|
+
* fix(model): apply hooks to custom methods if specified #6385
|
|
6
|
+
* fix(schema): support opting out of one timestamp field but not the other for `insertMany()` #6381
|
|
7
|
+
* fix(documentarray): handle `required: true` within documentarray definition #6364
|
|
8
|
+
* fix(document): ensure `isNew` is set before default functions run on init #3793
|
|
9
|
+
|
|
10
|
+
5.0.16 / 2018-04-23
|
|
11
|
+
===================
|
|
12
|
+
* docs(api): sort api methods based on their string property #6374 [lineus](https://github.com/lineus)
|
|
13
|
+
* docs(connection): fix typo in `createCollection()` #6370 [mattc41190](https://github.com/mattc41190)
|
|
14
|
+
* docs(document): remove vestigial reference to `numAffected` #6367 [ekulabuhov](https://github.com/ekulabuhov)
|
|
15
|
+
* docs(schema): fix typo #6366 [dhritzkiv](https://github.com/dhritzkiv)
|
|
16
|
+
* docs(schematypes): add missing `minlength` and `maxlength` docs #6365 [treble-snake](https://github.com/treble-snake)
|
|
17
|
+
* docs(queries): fix formatting #6360 [treble-snake](https://github.com/treble-snake)
|
|
18
|
+
* docs(api): add cursors to API docs #6353 #6344 [lineus](https://github.com/lineus)
|
|
19
|
+
* docs(aggregate): remove reference to non-existent `.select()` method #6346
|
|
20
|
+
* fix(update): handle `required` array with update validators and $pull #6341
|
|
21
|
+
* fix(update): avoid setting __v in findOneAndUpdate if it is `$set` #5973
|
|
22
|
+
|
|
23
|
+
5.0.15 / 2018-04-16
|
|
24
|
+
===================
|
|
25
|
+
* fix: add ability for casting from number to decimal128 #6336 #6331 [lineus](https://github.com/lineus)
|
|
26
|
+
* docs(middleware): enumerate the ways to error out in a hook #6315
|
|
27
|
+
* fix(document): respect schema-level depopulate option for toObject() #6313
|
|
28
|
+
* fix: bump mongodb driver -> 3.0.6 #6310
|
|
29
|
+
* fix(number): check for `valueOf()` function to support Decimal.js #6306 #6299 [lineus](https://github.com/lineus)
|
|
30
|
+
* fix(query): run array setters on query if input value is an array #6277
|
|
31
|
+
* fix(versioning): don't require matching version when using array.pull() #6190
|
|
32
|
+
* fix(document): add `toHexString()` function so you don't need to check whether a path is populated to get an id #6115
|
|
33
|
+
|
|
34
|
+
5.0.14 / 2018-04-09
|
|
35
|
+
===================
|
|
36
|
+
* fix(schema): clone aliases and alternative option syntax correctly
|
|
37
|
+
* fix(query): call utils.toObject in query.count like in query.find #6325 [lineus](https://github.com/lineus)
|
|
38
|
+
* docs(populate): add middleware examples #6320 [BorntraegerMarc](https://github.com/BorntraegerMarc)
|
|
39
|
+
* docs(compatibility): fix dead link #6319 [lacivert](https://github.com/lacivert)
|
|
40
|
+
* docs(api): fix markdown parsing for parameters #6318 #6314 [lineus](https://github.com/lineus)
|
|
41
|
+
* fix(populate): handle space-delimited paths in array populate #6296 #6284 [lineus](https://github.com/lineus)
|
|
42
|
+
* fix(populate): support basic virtual populate underneath embedded discriminators #6273
|
|
43
|
+
|
|
1
44
|
5.0.13 / 2018-04-05
|
|
2
45
|
===================
|
|
3
46
|
* docs(faq): add middleware to faq arrow function warning #6309 [lineus](https://github.com/lineus)
|
package/lib/connection.js
CHANGED
|
@@ -243,7 +243,7 @@ Connection.prototype.config;
|
|
|
243
243
|
* Options are passed down without modification to the [MongoDB driver's `createCollection()` function](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
|
|
244
244
|
*
|
|
245
245
|
* @method createCollection
|
|
246
|
-
* @param {string} collection The collection to
|
|
246
|
+
* @param {string} collection The collection to create
|
|
247
247
|
* @param {Object} [options] see [MongoDB driver docs](http://mongodb.github.io/node-mongodb-native/2.2/api/Db.html#createCollection)
|
|
248
248
|
* @param {Function} [callback]
|
|
249
249
|
* @return {Promise}
|
package/lib/document.js
CHANGED
|
@@ -4,34 +4,37 @@
|
|
|
4
4
|
* Module dependencies.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
7
|
+
const EventEmitter = require('events').EventEmitter;
|
|
8
|
+
const InternalCache = require('./internal');
|
|
9
|
+
const MongooseError = require('./error');
|
|
10
|
+
const MixedSchema = require('./schema/mixed');
|
|
11
|
+
const Schema = require('./schema');
|
|
12
|
+
const ObjectExpectedError = require('./error/objectExpected');
|
|
13
|
+
const ObjectParameterError = require('./error/objectParameter');
|
|
14
|
+
const StrictModeError = require('./error/strict');
|
|
15
|
+
const ValidatorError = require('./schematype').ValidatorError;
|
|
16
|
+
const VirtualType = require('./virtualtype');
|
|
17
|
+
const cleanModifiedSubpaths = require('./services/document/cleanModifiedSubpaths');
|
|
18
|
+
const compile = require('./services/document/compile').compile;
|
|
19
|
+
const defineKey = require('./services/document/compile').defineKey;
|
|
20
|
+
const flatten = require('./services/common').flatten;
|
|
21
|
+
const get = require('lodash.get');
|
|
22
|
+
const idGetter = require('./plugins/idGetter');
|
|
23
|
+
const isDefiningProjection = require('./services/projection/isDefiningProjection');
|
|
24
|
+
const isExclusive = require('./services/projection/isExclusive');
|
|
25
|
+
const inspect = require('util').inspect;
|
|
26
|
+
const internalToObjectOptions = require('./options').internalToObjectOptions;
|
|
27
|
+
const mpath = require('mpath');
|
|
28
|
+
const utils = require('./utils');
|
|
29
|
+
|
|
30
|
+
const ValidationError = MongooseError.ValidationError;
|
|
31
|
+
const clone = utils.clone;
|
|
32
|
+
const deepEqual = utils.deepEqual;
|
|
33
|
+
const isMongooseObject = utils.isMongooseObject;
|
|
34
|
+
|
|
35
|
+
let DocumentArray;
|
|
36
|
+
let MongooseArray;
|
|
37
|
+
let Embedded;
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* Document constructor.
|
|
@@ -46,9 +49,15 @@ var idGetter = require('./plugins/idGetter');
|
|
|
46
49
|
*/
|
|
47
50
|
|
|
48
51
|
function Document(obj, fields, skipId, options) {
|
|
52
|
+
if (typeof skipId === 'object' && skipId != null) {
|
|
53
|
+
options = skipId;
|
|
54
|
+
skipId = options.skipId;
|
|
55
|
+
}
|
|
56
|
+
options = options || {};
|
|
57
|
+
|
|
49
58
|
this.$__ = new InternalCache;
|
|
50
59
|
this.$__.emitter = new EventEmitter();
|
|
51
|
-
this.isNew = true;
|
|
60
|
+
this.isNew = 'isNew' in options ? options.isNew : true;
|
|
52
61
|
this.errors = undefined;
|
|
53
62
|
this.$__.$options = options || {};
|
|
54
63
|
|
|
@@ -1513,7 +1522,12 @@ function _getPathsToValidate(doc) {
|
|
|
1513
1522
|
var path = paths[i];
|
|
1514
1523
|
|
|
1515
1524
|
var _pathType = doc.schema.path(path);
|
|
1516
|
-
if (!_pathType ||
|
|
1525
|
+
if (!_pathType ||
|
|
1526
|
+
!_pathType.$isMongooseArray ||
|
|
1527
|
+
// To avoid potential performance issues, skip doc arrays whose children
|
|
1528
|
+
// are not required. `getPositionalPathType()` may be slow, so avoid
|
|
1529
|
+
// it unless we have a case of #6364
|
|
1530
|
+
(_pathType.$isMongooseDocumentArray && !get(_pathType, 'schemaOptions.required'))) {
|
|
1517
1531
|
continue;
|
|
1518
1532
|
}
|
|
1519
1533
|
|
|
@@ -1802,7 +1816,7 @@ Document.prototype.$markValid = function(path) {
|
|
|
1802
1816
|
* ####Example:
|
|
1803
1817
|
*
|
|
1804
1818
|
* product.sold = Date.now();
|
|
1805
|
-
* product.save(function (err, product
|
|
1819
|
+
* product.save(function (err, product) {
|
|
1806
1820
|
* if (err) ..
|
|
1807
1821
|
* })
|
|
1808
1822
|
*
|
|
@@ -1810,7 +1824,6 @@ Document.prototype.$markValid = function(path) {
|
|
|
1810
1824
|
*
|
|
1811
1825
|
* 1. `err` if an error occurred
|
|
1812
1826
|
* 2. `product` which is the saved `product`
|
|
1813
|
-
* 3. `numAffected` will be 1 when the document was successfully persisted to MongoDB, otherwise 0. Unless you tweak mongoose's internals, you don't need to worry about checking this parameter for errors - checking `err` is sufficient to make sure your document was properly saved.
|
|
1814
1827
|
*
|
|
1815
1828
|
* As an extra measure of flow control, save will return a Promise.
|
|
1816
1829
|
* ####Example:
|
|
@@ -2138,15 +2151,21 @@ Document.prototype.$toObject = function(options, json) {
|
|
|
2138
2151
|
_minimize = this.schema.options.minimize;
|
|
2139
2152
|
}
|
|
2140
2153
|
|
|
2154
|
+
// The original options that will be passed to `clone()`. Important because
|
|
2155
|
+
// `clone()` will recursively call `$toObject()` on embedded docs, so we
|
|
2156
|
+
// need the original options the user passed in, plus `_isNested` and
|
|
2157
|
+
// `_parentOptions` for checking whether we need to depopulate.
|
|
2141
2158
|
const cloneOptions = Object.assign(utils.clone(options), {
|
|
2142
2159
|
_isNested: true,
|
|
2143
2160
|
json: json,
|
|
2144
2161
|
minimize: _minimize
|
|
2145
2162
|
});
|
|
2146
2163
|
|
|
2164
|
+
const depopulate = options.depopulate ||
|
|
2165
|
+
get(options, '_parentOptions.depopulate', false);
|
|
2147
2166
|
// _isNested will only be true if this is not the top level document, we
|
|
2148
2167
|
// should never depopulate
|
|
2149
|
-
if (
|
|
2168
|
+
if (depopulate && options._isNested && this.$__.wasPopulated) {
|
|
2150
2169
|
// populated paths that we set to a document
|
|
2151
2170
|
return clone(this._id, cloneOptions);
|
|
2152
2171
|
}
|
|
@@ -2157,6 +2176,8 @@ Document.prototype.$toObject = function(options, json) {
|
|
|
2157
2176
|
options.json = json;
|
|
2158
2177
|
options.minimize = _minimize;
|
|
2159
2178
|
|
|
2179
|
+
cloneOptions._parentOptions = options;
|
|
2180
|
+
|
|
2160
2181
|
// remember the root transform function
|
|
2161
2182
|
// to save it from being overwritten by sub-transform functions
|
|
2162
2183
|
var originalTransform = options.transform;
|
package/lib/model.js
CHANGED
|
@@ -27,6 +27,8 @@ var internalToObjectOptions = require('./options').internalToObjectOptions;
|
|
|
27
27
|
var isPathSelectedInclusive = require('./services/projection/isPathSelectedInclusive');
|
|
28
28
|
var get = require('lodash.get');
|
|
29
29
|
var getSchemaTypes = require('./services/populate/getSchemaTypes');
|
|
30
|
+
var getVirtual = require('./services/populate/getVirtual');
|
|
31
|
+
var modifiedPaths = require('./services/update/modifiedPaths');
|
|
30
32
|
var mpath = require('mpath');
|
|
31
33
|
var parallel = require('async/parallel');
|
|
32
34
|
var parallelLimit = require('async/parallelLimit');
|
|
@@ -57,7 +59,7 @@ function Model(doc, fields, skipId) {
|
|
|
57
59
|
'**not** a schema. Make sure you\'re calling `mongoose.model()`, not ' +
|
|
58
60
|
'`mongoose.Model()`.');
|
|
59
61
|
}
|
|
60
|
-
Document.call(this, doc, fields, skipId
|
|
62
|
+
Document.call(this, doc, fields, skipId);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
/*!
|
|
@@ -408,8 +410,7 @@ function operand(self, where, delta, data, val, op) {
|
|
|
408
410
|
// editing the correct array element.
|
|
409
411
|
// only increment the version if an array position changes.
|
|
410
412
|
// modifying elements of an array is ok if position does not change.
|
|
411
|
-
|
|
412
|
-
if (op === '$push' || op === '$addToSet') {
|
|
413
|
+
if (op === '$push' || op === '$addToSet' || op === '$pullAll' || op === '$pull') {
|
|
413
414
|
self.$__.version = VERSION_INC;
|
|
414
415
|
} else if (/^\$p/.test(op)) {
|
|
415
416
|
// potentially changing array positions
|
|
@@ -732,7 +733,7 @@ Model.prototype.$__where = function _where(where) {
|
|
|
732
733
|
* })
|
|
733
734
|
*
|
|
734
735
|
*
|
|
735
|
-
* As an extra measure of flow control, remove will return a Promise (bound to `fn` if passed) so it could be chained, or hooked to
|
|
736
|
+
* As an extra measure of flow control, remove will return a Promise (bound to `fn` if passed) so it could be chained, or hooked to recieve errors
|
|
736
737
|
*
|
|
737
738
|
* ####Example:
|
|
738
739
|
* product.remove().then(function (product) {
|
|
@@ -1724,13 +1725,16 @@ Model.findOneAndUpdate = function(conditions, update, options, callback) {
|
|
|
1724
1725
|
|
|
1725
1726
|
update = utils.clone(update, {depopulate: 1, _isNested: true});
|
|
1726
1727
|
if (this.schema.options.versionKey && options && options.upsert) {
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1728
|
+
var updatedPaths = modifiedPaths(update);
|
|
1729
|
+
if (!updatedPaths[this.schema.options.versionKey]) {
|
|
1730
|
+
if (options.overwrite) {
|
|
1731
|
+
update[this.schema.options.versionKey] = 0;
|
|
1732
|
+
} else {
|
|
1733
|
+
if (!update.$setOnInsert) {
|
|
1734
|
+
update.$setOnInsert = {};
|
|
1735
|
+
}
|
|
1736
|
+
update.$setOnInsert[this.schema.options.versionKey] = 0;
|
|
1732
1737
|
}
|
|
1733
|
-
update.$setOnInsert[this.schema.options.versionKey] = 0;
|
|
1734
1738
|
}
|
|
1735
1739
|
}
|
|
1736
1740
|
|
|
@@ -2617,14 +2621,15 @@ function _update(model, op, conditions, doc, options, callback) {
|
|
|
2617
2621
|
}
|
|
2618
2622
|
options = typeof options === 'function' ? options : utils.clone(options);
|
|
2619
2623
|
|
|
2620
|
-
|
|
2624
|
+
var versionKey = get(model, 'schema.options.versionKey', null);
|
|
2625
|
+
if (versionKey && options && options.upsert) {
|
|
2621
2626
|
if (options.overwrite) {
|
|
2622
|
-
doc[
|
|
2627
|
+
doc[versionKey] = 0;
|
|
2623
2628
|
} else {
|
|
2624
2629
|
if (!doc.$setOnInsert) {
|
|
2625
2630
|
doc.$setOnInsert = {};
|
|
2626
2631
|
}
|
|
2627
|
-
doc.$setOnInsert[
|
|
2632
|
+
doc.$setOnInsert[versionKey] = 0;
|
|
2628
2633
|
}
|
|
2629
2634
|
}
|
|
2630
2635
|
|
|
@@ -2767,10 +2772,10 @@ Model.mapReduce = function mapReduce(o, callback) {
|
|
|
2767
2772
|
* });
|
|
2768
2773
|
*
|
|
2769
2774
|
* // Or use the aggregation pipeline builder.
|
|
2770
|
-
* Users.aggregate()
|
|
2771
|
-
*
|
|
2772
|
-
*
|
|
2773
|
-
*
|
|
2775
|
+
* Users.aggregate().
|
|
2776
|
+
* group({ _id: null, maxBalance: { $max: '$balance' } }).
|
|
2777
|
+
* project('-id maxBalance').
|
|
2778
|
+
* exec(function (err, res) {
|
|
2774
2779
|
* if (err) return handleError(err);
|
|
2775
2780
|
* console.log(res); // [ { maxBalance: 98 } ]
|
|
2776
2781
|
* });
|
|
@@ -3026,8 +3031,8 @@ function _populate(model, docs, paths, cache, callback) {
|
|
|
3026
3031
|
/*!
|
|
3027
3032
|
* Populates `docs`
|
|
3028
3033
|
*/
|
|
3029
|
-
|
|
3030
|
-
|
|
3034
|
+
const excludeIdReg = /\s?-_id\s?/;
|
|
3035
|
+
const excludeIdRegGlobal = /\s?-_id\s?/g;
|
|
3031
3036
|
|
|
3032
3037
|
function populate(model, docs, options, callback) {
|
|
3033
3038
|
var modelsMap;
|
|
@@ -3157,12 +3162,7 @@ function populate(model, docs, options, callback) {
|
|
|
3157
3162
|
var val;
|
|
3158
3163
|
|
|
3159
3164
|
// Clone because `assignRawDocsToIdStructure` will mutate the array
|
|
3160
|
-
var allIds =
|
|
3161
|
-
if (Array.isArray(v)) {
|
|
3162
|
-
return [].concat(v);
|
|
3163
|
-
}
|
|
3164
|
-
return v;
|
|
3165
|
-
}));
|
|
3165
|
+
var allIds = utils.clone(mod.allIds);
|
|
3166
3166
|
|
|
3167
3167
|
// optimization:
|
|
3168
3168
|
// record the document positions as returned by
|
|
@@ -3224,7 +3224,8 @@ function populate(model, docs, options, callback) {
|
|
|
3224
3224
|
|
|
3225
3225
|
assignVals({
|
|
3226
3226
|
originalModel: model,
|
|
3227
|
-
|
|
3227
|
+
// If virtual, make sure to not mutate original field
|
|
3228
|
+
rawIds: mod.isVirtual ? allIds : mod.allIds,
|
|
3228
3229
|
allIds: allIds,
|
|
3229
3230
|
localField: mod.localField,
|
|
3230
3231
|
foreignField: mod.foreignField,
|
|
@@ -3253,7 +3254,6 @@ function assignVals(o) {
|
|
|
3253
3254
|
|
|
3254
3255
|
// now update the original documents being populated using the
|
|
3255
3256
|
// result structure that contains real documents.
|
|
3256
|
-
|
|
3257
3257
|
var docs = o.docs;
|
|
3258
3258
|
var rawIds = o.rawIds;
|
|
3259
3259
|
var options = o.options;
|
|
@@ -3264,7 +3264,7 @@ function assignVals(o) {
|
|
|
3264
3264
|
|
|
3265
3265
|
for (var i = 0; i < docs.length; ++i) {
|
|
3266
3266
|
if (utils.getValue(o.path, docs[i]) == null &&
|
|
3267
|
-
!o.originalModel.schema
|
|
3267
|
+
!getVirtual(o.originalModel.schema, o.path)) {
|
|
3268
3268
|
continue;
|
|
3269
3269
|
}
|
|
3270
3270
|
|
|
@@ -3294,7 +3294,7 @@ function assignVals(o) {
|
|
|
3294
3294
|
if (docs[i].$__) {
|
|
3295
3295
|
docs[i].populated(o.path, o.allIds[i], o.allOptions);
|
|
3296
3296
|
}
|
|
3297
|
-
utils.setValue(o.path, rawIds[i], docs[i], setValue);
|
|
3297
|
+
utils.setValue(o.path, rawIds[i], docs[i], setValue, false);
|
|
3298
3298
|
}
|
|
3299
3299
|
}
|
|
3300
3300
|
}
|
|
@@ -3383,11 +3383,20 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, lo
|
|
|
3383
3383
|
}
|
|
3384
3384
|
|
|
3385
3385
|
function getModelsMapForPopulate(model, docs, options) {
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3386
|
+
let i;
|
|
3387
|
+
let doc;
|
|
3388
|
+
let len = docs.length;
|
|
3389
|
+
let available = {};
|
|
3390
|
+
let map = [];
|
|
3391
|
+
let modelNameFromQuery = options.model && options.model.modelName || options.model;
|
|
3392
|
+
let schema;
|
|
3393
|
+
let refPath;
|
|
3394
|
+
let Model;
|
|
3395
|
+
let currentOptions;
|
|
3396
|
+
let modelNames;
|
|
3397
|
+
let modelName;
|
|
3398
|
+
let discriminatorKey;
|
|
3399
|
+
let modelForFindSchema;
|
|
3391
3400
|
|
|
3392
3401
|
var originalModel = options.model;
|
|
3393
3402
|
var isVirtual = false;
|
|
@@ -3398,23 +3407,21 @@ function getModelsMapForPopulate(model, docs, options) {
|
|
|
3398
3407
|
doc = docs[i];
|
|
3399
3408
|
|
|
3400
3409
|
schema = getSchemaTypes(modelSchema, doc, options.path);
|
|
3401
|
-
|
|
3402
|
-
if (isUnderneathDocArray &&
|
|
3403
|
-
options &&
|
|
3404
|
-
options.options &&
|
|
3405
|
-
options.options.sort) {
|
|
3410
|
+
const isUnderneathDocArray = schema && schema.$isUnderneathDocArray;
|
|
3411
|
+
if (isUnderneathDocArray && get(options, 'options.sort') != null) {
|
|
3406
3412
|
return new Error('Cannot populate with `sort` on path ' + options.path +
|
|
3407
3413
|
' because it is a subproperty of a document array');
|
|
3408
3414
|
}
|
|
3409
3415
|
|
|
3416
|
+
modelNames = null;
|
|
3410
3417
|
if (Array.isArray(schema)) {
|
|
3411
|
-
for (
|
|
3418
|
+
for (let j = 0; j < schema.length; ++j) {
|
|
3412
3419
|
var _modelNames = _getModelNames(doc, schema[j]);
|
|
3413
3420
|
if (!_modelNames) {
|
|
3414
3421
|
continue;
|
|
3415
3422
|
}
|
|
3416
|
-
modelNames =
|
|
3417
|
-
for (
|
|
3423
|
+
modelNames = modelNames || [];
|
|
3424
|
+
for (let x = 0; x < _modelNames.length; ++x) {
|
|
3418
3425
|
if (modelNames.indexOf(_modelNames[x]) === -1) {
|
|
3419
3426
|
modelNames.push(_modelNames[x]);
|
|
3420
3427
|
}
|
|
@@ -3427,10 +3434,10 @@ function getModelsMapForPopulate(model, docs, options) {
|
|
|
3427
3434
|
}
|
|
3428
3435
|
}
|
|
3429
3436
|
|
|
3430
|
-
|
|
3431
|
-
|
|
3437
|
+
let virtual = getVirtual(model.schema, options.path);
|
|
3438
|
+
let localField;
|
|
3432
3439
|
if (virtual && virtual.options) {
|
|
3433
|
-
|
|
3440
|
+
let virtualPrefix = virtual.$nestedSchemaPath ?
|
|
3434
3441
|
virtual.$nestedSchemaPath + '.' : '';
|
|
3435
3442
|
if (typeof virtual.options.localField === 'function') {
|
|
3436
3443
|
localField = virtualPrefix + virtual.options.localField.call(doc, doc);
|
|
@@ -3440,17 +3447,24 @@ function getModelsMapForPopulate(model, docs, options) {
|
|
|
3440
3447
|
} else {
|
|
3441
3448
|
localField = options.path;
|
|
3442
3449
|
}
|
|
3443
|
-
|
|
3450
|
+
let foreignField = virtual && virtual.options ?
|
|
3444
3451
|
virtual.options.foreignField :
|
|
3445
3452
|
'_id';
|
|
3446
|
-
|
|
3453
|
+
let justOne = true;
|
|
3447
3454
|
if (virtual && virtual.options && virtual.options.ref) {
|
|
3448
3455
|
justOne = virtual.options.justOne;
|
|
3449
3456
|
isVirtual = true;
|
|
3457
|
+
if (!modelNames) {
|
|
3458
|
+
modelNames = [virtual.options.ref];
|
|
3459
|
+
}
|
|
3450
3460
|
} else if (schema) {
|
|
3451
3461
|
justOne = !schema.$isMongooseArray;
|
|
3452
3462
|
}
|
|
3453
3463
|
|
|
3464
|
+
if (!modelNames) {
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3454
3468
|
if (virtual && (!localField || !foreignField)) {
|
|
3455
3469
|
throw new Error('If you are populating a virtual, you must set the ' +
|
|
3456
3470
|
'localField and foreignField options');
|
|
@@ -3463,11 +3477,11 @@ function getModelsMapForPopulate(model, docs, options) {
|
|
|
3463
3477
|
if (typeof foreignField === 'function') {
|
|
3464
3478
|
foreignField = foreignField.call(doc);
|
|
3465
3479
|
}
|
|
3466
|
-
|
|
3467
|
-
|
|
3480
|
+
const ret = convertTo_id(utils.getValue(localField, doc));
|
|
3481
|
+
const id = String(utils.getValue(foreignField, doc));
|
|
3468
3482
|
options._docs[id] = Array.isArray(ret) ? ret.slice() : ret;
|
|
3469
3483
|
|
|
3470
|
-
|
|
3484
|
+
let k = modelNames.length;
|
|
3471
3485
|
while (k--) {
|
|
3472
3486
|
modelName = modelNames[k];
|
|
3473
3487
|
if (modelName == null) {
|
|
@@ -3559,7 +3573,7 @@ function getModelsMapForPopulate(model, docs, options) {
|
|
|
3559
3573
|
} else {
|
|
3560
3574
|
schemaForCurrentDoc = schema;
|
|
3561
3575
|
}
|
|
3562
|
-
var virtual = modelForCurrentDoc.schema
|
|
3576
|
+
var virtual = getVirtual(modelForCurrentDoc.schema, options.path);
|
|
3563
3577
|
|
|
3564
3578
|
var ref;
|
|
3565
3579
|
if ((ref = get(schemaForCurrentDoc, 'options.ref')) != null) {
|
package/lib/plugins/idGetter.js
CHANGED
|
@@ -7,10 +7,24 @@
|
|
|
7
7
|
module.exports = function(schema) {
|
|
8
8
|
// ensure the documents receive an id getter unless disabled
|
|
9
9
|
var autoIdGetter = !schema.paths['id'] &&
|
|
10
|
-
|
|
11
|
-
if (autoIdGetter) {
|
|
12
|
-
|
|
10
|
+
(!schema.options.noVirtualId && schema.options.id);
|
|
11
|
+
if (!autoIdGetter) {
|
|
12
|
+
return;
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
schema.virtual('id').get(idGetter);
|
|
16
|
+
|
|
17
|
+
if ('toHexString' in schema.methods) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Re: gh-6115, make it easy to get something usable as an ObjectID regardless
|
|
22
|
+
// of whether a property is populated or not. With this, you can do
|
|
23
|
+
// `blogPost.author.toHexString()` and get a hex string regardless of whether
|
|
24
|
+
// `author` is populated
|
|
25
|
+
schema.methods.toHexString = function() {
|
|
26
|
+
return this.id;
|
|
27
|
+
};
|
|
14
28
|
};
|
|
15
29
|
|
|
16
30
|
/*!
|
package/lib/query.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
1
3
|
/*!
|
|
2
4
|
* Module dependencies.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
const CastError = require('./error/cast');
|
|
8
|
+
const ObjectParameterError = require('./error/objectParameter');
|
|
9
|
+
const QueryCursor = require('./cursor/QueryCursor');
|
|
10
|
+
const ReadPreference = require('./drivers').ReadPreference;
|
|
11
|
+
const cast = require('./cast');
|
|
12
|
+
const castUpdate = require('./services/query/castUpdate');
|
|
13
|
+
const get = require('lodash.get');
|
|
14
|
+
const hasDollarKeys = require('./services/query/hasDollarKeys');
|
|
15
|
+
const helpers = require('./queryhelpers');
|
|
16
|
+
const isInclusive = require('./services/projection/isInclusive');
|
|
17
|
+
const mquery = require('mquery');
|
|
18
|
+
const selectPopulatedFields = require('./services/query/selectPopulatedFields');
|
|
19
|
+
const setDefaultsOnInsert = require('./services/setDefaultsOnInsert');
|
|
20
|
+
const slice = require('sliced');
|
|
21
|
+
const updateValidators = require('./services/updateValidators');
|
|
22
|
+
const util = require('util');
|
|
23
|
+
const utils = require('./utils');
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Query constructor used for building queries. You do not need
|
|
@@ -80,8 +83,9 @@ function Query(conditions, options, model, collection) {
|
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
this.options = this.options || {};
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
const collation = get(this, 'schema.options.collation', null);
|
|
87
|
+
if (collation != null) {
|
|
88
|
+
this.options.collation = collation;
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
|
|
@@ -1163,12 +1167,14 @@ Query.prototype._optionsForExec = function(model) {
|
|
|
1163
1167
|
return options;
|
|
1164
1168
|
}
|
|
1165
1169
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1170
|
+
const safe = get(model, 'schema.options.safe', null);
|
|
1171
|
+
if (!('safe' in options) && safe != null) {
|
|
1172
|
+
options.safe = safe;
|
|
1168
1173
|
}
|
|
1169
1174
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1175
|
+
const readPreference = get(model, 'schema.options.read');
|
|
1176
|
+
if (!('readPreference' in options) && readPreference) {
|
|
1177
|
+
options.readPreference = readPreference;
|
|
1172
1178
|
}
|
|
1173
1179
|
|
|
1174
1180
|
if (options.upsert !== void 0) {
|
|
@@ -1675,6 +1681,8 @@ Query.prototype.count = function(conditions, callback) {
|
|
|
1675
1681
|
conditions = undefined;
|
|
1676
1682
|
}
|
|
1677
1683
|
|
|
1684
|
+
conditions = utils.toObject(conditions);
|
|
1685
|
+
|
|
1678
1686
|
if (mquery.canMerge(conditions)) {
|
|
1679
1687
|
this.merge(conditions);
|
|
1680
1688
|
}
|
|
@@ -3209,9 +3217,9 @@ Query.prototype.cast = function(model, obj) {
|
|
|
3209
3217
|
upsert: this.options && this.options.upsert,
|
|
3210
3218
|
strict: (this.options && 'strict' in this.options) ?
|
|
3211
3219
|
this.options.strict :
|
|
3212
|
-
(model
|
|
3220
|
+
get(model, 'schema.options.strict', null),
|
|
3213
3221
|
strictQuery: (this.options && this.options.strictQuery) ||
|
|
3214
|
-
(model
|
|
3222
|
+
get(model, 'schema.options.strictQuery', null)
|
|
3215
3223
|
}, this);
|
|
3216
3224
|
} catch (err) {
|
|
3217
3225
|
// CastError, assign model
|
package/lib/queryhelpers.js
CHANGED
package/lib/schema/array.js
CHANGED
|
@@ -70,6 +70,7 @@ function SchemaArray(key, cast, options, schemaOptions) {
|
|
|
70
70
|
: cast;
|
|
71
71
|
|
|
72
72
|
this.casterConstructor = caster;
|
|
73
|
+
|
|
73
74
|
if (typeof caster === 'function' && !caster.$isArraySubdocument) {
|
|
74
75
|
this.caster = new caster(null, castOptions);
|
|
75
76
|
} else {
|
|
@@ -140,20 +141,6 @@ SchemaArray.prototype.enum = function() {
|
|
|
140
141
|
return this;
|
|
141
142
|
};
|
|
142
143
|
|
|
143
|
-
/**
|
|
144
|
-
* Check if the given value satisfies a `required` validator. The given value
|
|
145
|
-
* must be an array (that is, not `null` or `undefined`).
|
|
146
|
-
* Empty arrays will **not** make `required` validator fail.
|
|
147
|
-
*
|
|
148
|
-
* @param {Any} value
|
|
149
|
-
* @return {Boolean}
|
|
150
|
-
* @api public
|
|
151
|
-
*/
|
|
152
|
-
|
|
153
|
-
SchemaArray.prototype.checkRequired = function(value) {
|
|
154
|
-
return Array.isArray(value);
|
|
155
|
-
};
|
|
156
|
-
|
|
157
144
|
/**
|
|
158
145
|
* Overrides the getters application for the population special-case
|
|
159
146
|
*
|
|
@@ -273,6 +260,9 @@ SchemaArray.prototype.castForQuery = function($conditional, value) {
|
|
|
273
260
|
var caster = this.caster;
|
|
274
261
|
|
|
275
262
|
if (Array.isArray(val)) {
|
|
263
|
+
this.setters.reverse().forEach(setter => {
|
|
264
|
+
val = setter.call(this, val, this);
|
|
265
|
+
});
|
|
276
266
|
val = val.map(function(v) {
|
|
277
267
|
if (utils.isObject(v) && v.$elemMatch) {
|
|
278
268
|
return v;
|
package/lib/schema/decimal128.js
CHANGED
|
@@ -124,6 +124,10 @@ Decimal128.prototype.cast = function(value, doc, init) {
|
|
|
124
124
|
return new Decimal128Type(value);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
if (typeof value === 'number') {
|
|
128
|
+
return Decimal128Type.fromString(String(value));
|
|
129
|
+
}
|
|
130
|
+
|
|
127
131
|
throw new CastError('Decimal128', value, this.path);
|
|
128
132
|
};
|
|
129
133
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
3
|
/*!
|
|
4
4
|
* Module dependencies.
|
|
@@ -26,15 +26,16 @@ var Subdocument;
|
|
|
26
26
|
* @api public
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
function DocumentArray(key, schema, options) {
|
|
30
|
-
|
|
29
|
+
function DocumentArray(key, schema, options, schemaOptions) {
|
|
30
|
+
const EmbeddedDocument = _createConstructor(schema, options);
|
|
31
31
|
EmbeddedDocument.prototype.$basePath = key;
|
|
32
32
|
|
|
33
33
|
ArrayType.call(this, key, EmbeddedDocument, options);
|
|
34
34
|
|
|
35
35
|
this.schema = schema;
|
|
36
|
+
this.schemaOptions = schemaOptions || {};
|
|
36
37
|
this.$isMongooseDocumentArray = true;
|
|
37
|
-
|
|
38
|
+
const fn = this.defaultValue;
|
|
38
39
|
|
|
39
40
|
if (!('defaultValue' in this) || fn !== void 0) {
|
|
40
41
|
this.default(function() {
|
|
@@ -169,7 +170,7 @@ DocumentArray.prototype.doValidate = function(array, fn, scope, options) {
|
|
|
169
170
|
for (var i = 0, len = count; i < len; ++i) {
|
|
170
171
|
// sidestep sparse entries
|
|
171
172
|
var doc = array[i];
|
|
172
|
-
if (
|
|
173
|
+
if (doc == null) {
|
|
173
174
|
--count || fn(error);
|
|
174
175
|
continue;
|
|
175
176
|
}
|
package/lib/schema/number.js
CHANGED
|
@@ -223,6 +223,9 @@ SchemaNumber.prototype.cast = function(value, doc, init) {
|
|
|
223
223
|
if (typeof val === 'number') {
|
|
224
224
|
return val;
|
|
225
225
|
}
|
|
226
|
+
if (!Array.isArray(val) && typeof val.valueOf === 'function') {
|
|
227
|
+
return Number(val.valueOf());
|
|
228
|
+
}
|
|
226
229
|
if (val.toString && !Array.isArray(val) && val.toString() == Number(val)) {
|
|
227
230
|
return new Number(val);
|
|
228
231
|
}
|
package/lib/schema.js
CHANGED
|
@@ -9,6 +9,7 @@ var utils = require('./utils');
|
|
|
9
9
|
var MongooseTypes;
|
|
10
10
|
var Kareem = require('kareem');
|
|
11
11
|
var SchemaType = require('./schematype');
|
|
12
|
+
var get = require('lodash.get');
|
|
12
13
|
var mpath = require('mpath');
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -235,6 +236,7 @@ Schema.prototype.tree;
|
|
|
235
236
|
Schema.prototype.clone = function() {
|
|
236
237
|
var s = new Schema({}, this._userProvidedOptions);
|
|
237
238
|
s.obj = this.obj;
|
|
239
|
+
s.options = utils.clone(this.options);
|
|
238
240
|
s.callQueue = this.callQueue.map(function(f) { return f; });
|
|
239
241
|
s.methods = utils.clone(this.methods);
|
|
240
242
|
s.statics = utils.clone(this.statics);
|
|
@@ -248,12 +250,24 @@ Schema.prototype.clone = function() {
|
|
|
248
250
|
s.nested = utils.clone(this.nested);
|
|
249
251
|
s.subpaths = utils.clone(this.subpaths);
|
|
250
252
|
s.childSchemas = this.childSchemas.slice();
|
|
253
|
+
s.singleNestedPaths = utils.clone(this.singleNestedPaths);
|
|
251
254
|
|
|
252
255
|
s.virtuals = utils.clone(this.virtuals);
|
|
253
256
|
s.$globalPluginsApplied = this.$globalPluginsApplied;
|
|
254
257
|
s.$isRootDiscriminator = this.$isRootDiscriminator;
|
|
255
|
-
|
|
256
|
-
|
|
258
|
+
|
|
259
|
+
if (this.discriminatorMapping != null) {
|
|
260
|
+
s.discriminatorMapping = Object.assign({}, this.discriminatorMapping);
|
|
261
|
+
}
|
|
262
|
+
if (s.discriminators != null) {
|
|
263
|
+
s.discriminators = Object.assign({}, this.discriminators);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
s.aliases = Object.assign({}, this.aliases);
|
|
267
|
+
|
|
268
|
+
// Bubble up `init` for backwards compat
|
|
269
|
+
s.on('init', v => this.emit('init', v));
|
|
270
|
+
|
|
257
271
|
return s;
|
|
258
272
|
};
|
|
259
273
|
|
|
@@ -540,7 +554,7 @@ Schema.interpretAsType = function(path, obj, options) {
|
|
|
540
554
|
if (cast &&
|
|
541
555
|
cast[options.typeKey] &&
|
|
542
556
|
cast[options.typeKey].instanceOfSchema) {
|
|
543
|
-
return new MongooseTypes.DocumentArray(path, cast[options.typeKey], cast);
|
|
557
|
+
return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast);
|
|
544
558
|
}
|
|
545
559
|
|
|
546
560
|
if (Array.isArray(cast)) {
|
|
@@ -762,11 +776,11 @@ Schema.prototype.setupTimestamp = function(timestamps) {
|
|
|
762
776
|
var defaultTimestamp = new Date();
|
|
763
777
|
var auto_id = this._id && this._id.auto;
|
|
764
778
|
|
|
765
|
-
if (createdAt
|
|
779
|
+
if (createdAt && !this.get(createdAt) && this.isSelected(createdAt)) {
|
|
766
780
|
this.set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp);
|
|
767
781
|
}
|
|
768
782
|
|
|
769
|
-
if (updatedAt
|
|
783
|
+
if (updatedAt && (this.isNew || this.isModified())) {
|
|
770
784
|
var ts = defaultTimestamp;
|
|
771
785
|
if (this.isNew) {
|
|
772
786
|
if (createdAt != null) {
|
|
@@ -791,10 +805,10 @@ Schema.prototype.setupTimestamp = function(timestamps) {
|
|
|
791
805
|
updates.$set = {};
|
|
792
806
|
_updates = updates.$set;
|
|
793
807
|
}
|
|
794
|
-
if (updatedAt
|
|
808
|
+
if (updatedAt && !currentUpdate[updatedAt]) {
|
|
795
809
|
_updates[updatedAt] = now;
|
|
796
810
|
}
|
|
797
|
-
if (createdAt
|
|
811
|
+
if (createdAt && !currentUpdate[createdAt]) {
|
|
798
812
|
_updates[createdAt] = now;
|
|
799
813
|
}
|
|
800
814
|
return updates;
|
|
@@ -802,12 +816,12 @@ Schema.prototype.setupTimestamp = function(timestamps) {
|
|
|
802
816
|
updates = { $set: {} };
|
|
803
817
|
currentUpdate = currentUpdate || {};
|
|
804
818
|
|
|
805
|
-
if (updatedAt
|
|
819
|
+
if (updatedAt &&
|
|
806
820
|
(!currentUpdate.$currentDate || !currentUpdate.$currentDate[updatedAt])) {
|
|
807
821
|
updates.$set[updatedAt] = now;
|
|
808
822
|
}
|
|
809
823
|
|
|
810
|
-
if (createdAt
|
|
824
|
+
if (createdAt) {
|
|
811
825
|
if (currentUpdate[createdAt]) {
|
|
812
826
|
delete currentUpdate[createdAt];
|
|
813
827
|
}
|
|
@@ -823,10 +837,10 @@ Schema.prototype.setupTimestamp = function(timestamps) {
|
|
|
823
837
|
};
|
|
824
838
|
|
|
825
839
|
this.methods.initializeTimestamps = function() {
|
|
826
|
-
if (!this.get(createdAt)) {
|
|
840
|
+
if (createdAt && !this.get(createdAt)) {
|
|
827
841
|
this.set(createdAt, new Date());
|
|
828
842
|
}
|
|
829
|
-
if (!this.get(updatedAt)) {
|
|
843
|
+
if (updatedAt && !this.get(updatedAt)) {
|
|
830
844
|
this.set(updatedAt, new Date());
|
|
831
845
|
}
|
|
832
846
|
return this;
|
|
@@ -980,7 +994,9 @@ function getPositionalPathType(self, path) {
|
|
|
980
994
|
if (i === last && val && !/\D/.test(subpath)) {
|
|
981
995
|
if (val.$isMongooseDocumentArray) {
|
|
982
996
|
var oldVal = val;
|
|
983
|
-
val = new SchemaType(subpath
|
|
997
|
+
val = new SchemaType(subpath, {
|
|
998
|
+
required: get(val, 'schemaOptions.required', false)
|
|
999
|
+
});
|
|
984
1000
|
val.cast = function(value, doc, init) {
|
|
985
1001
|
return oldVal.cast(value, doc, init)[0];
|
|
986
1002
|
};
|
|
@@ -1341,7 +1357,8 @@ Schema.prototype.indexes = function() {
|
|
|
1341
1357
|
path = schema.paths[key];
|
|
1342
1358
|
|
|
1343
1359
|
if ((path instanceof MongooseTypes.DocumentArray) || path.$isSingleNested) {
|
|
1344
|
-
if (path
|
|
1360
|
+
if (get(path, 'options.excludeIndexes') !== true &&
|
|
1361
|
+
get(path, 'schemaOptions.excludeIndexes') !== true) {
|
|
1345
1362
|
collectIndexes(path.schema, prefix + key + '.');
|
|
1346
1363
|
}
|
|
1347
1364
|
|
|
@@ -1526,43 +1543,6 @@ Schema.prototype.virtual = function(name, options) {
|
|
|
1526
1543
|
return virtuals[name];
|
|
1527
1544
|
};
|
|
1528
1545
|
|
|
1529
|
-
/*!
|
|
1530
|
-
* ignore
|
|
1531
|
-
*/
|
|
1532
|
-
|
|
1533
|
-
Schema.prototype._getVirtual = function(name) {
|
|
1534
|
-
return _getVirtual(this, name);
|
|
1535
|
-
};
|
|
1536
|
-
|
|
1537
|
-
/*!
|
|
1538
|
-
* ignore
|
|
1539
|
-
*/
|
|
1540
|
-
|
|
1541
|
-
function _getVirtual(schema, name) {
|
|
1542
|
-
if (schema.virtuals[name]) {
|
|
1543
|
-
return schema.virtuals[name];
|
|
1544
|
-
}
|
|
1545
|
-
var parts = name.split('.');
|
|
1546
|
-
var cur = '';
|
|
1547
|
-
var nestedSchemaPath = '';
|
|
1548
|
-
for (var i = 0; i < parts.length; ++i) {
|
|
1549
|
-
cur += (cur.length > 0 ? '.' : '') + parts[i];
|
|
1550
|
-
if (schema.virtuals[cur]) {
|
|
1551
|
-
if (i === parts.length - 1) {
|
|
1552
|
-
schema.virtuals[cur].$nestedSchemaPath = nestedSchemaPath;
|
|
1553
|
-
return schema.virtuals[cur];
|
|
1554
|
-
}
|
|
1555
|
-
continue;
|
|
1556
|
-
} else if (schema.paths[cur] && schema.paths[cur].schema) {
|
|
1557
|
-
schema = schema.paths[cur].schema;
|
|
1558
|
-
nestedSchemaPath += (nestedSchemaPath.length > 0 ? '.' : '') + cur;
|
|
1559
|
-
cur = '';
|
|
1560
|
-
} else {
|
|
1561
|
-
return null;
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
1546
|
/**
|
|
1567
1547
|
* Returns the virtual type with the given `name`.
|
|
1568
1548
|
*
|
|
@@ -1638,7 +1618,7 @@ Schema.prototype.remove = function(path) {
|
|
|
1638
1618
|
* ```
|
|
1639
1619
|
*
|
|
1640
1620
|
* @param {Function} model
|
|
1641
|
-
* @param {Boolean} [
|
|
1621
|
+
* @param {Boolean} [virtualsOnly] if truthy, only pulls virtuals from the class, not methods or statics
|
|
1642
1622
|
*/
|
|
1643
1623
|
Schema.prototype.loadClass = function(model, virtualsOnly) {
|
|
1644
1624
|
if (model === Object.prototype ||
|
|
@@ -40,6 +40,9 @@ function applyHooks(model, schema, options) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// Built-in hooks rely on hooking internal functions in order to support
|
|
44
|
+
// promises and make it so that `doc.save.toString()` provides meaningful
|
|
45
|
+
// information.
|
|
43
46
|
objToDecorate.$__save = schema.s.hooks.
|
|
44
47
|
createWrapper('save', objToDecorate.$__save, null, kareemOptions);
|
|
45
48
|
objToDecorate.$__validate = schema.s.hooks.
|
|
@@ -48,4 +51,17 @@ function applyHooks(model, schema, options) {
|
|
|
48
51
|
createWrapper('remove', objToDecorate.$__remove, null, kareemOptions);
|
|
49
52
|
objToDecorate.$__init = schema.s.hooks.
|
|
50
53
|
createWrapperSync('init', objToDecorate.$__init, null, kareemOptions);
|
|
54
|
+
|
|
55
|
+
// Support hooks for custom methods
|
|
56
|
+
const customMethods = Object.keys(schema.methods);
|
|
57
|
+
for (const method of customMethods) {
|
|
58
|
+
if (!schema.s.hooks.hasHooks(method)) {
|
|
59
|
+
// Don't wrap if there are no hooks for the custom method to avoid
|
|
60
|
+
// surprises. Also, `createWrapper()` enforces consistent async,
|
|
61
|
+
// so wrapping a sync method would break it.
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
objToDecorate[method] = schema.s.hooks.
|
|
65
|
+
createWrapper(method, objToDecorate[method], null, kareemOptions);
|
|
66
|
+
}
|
|
51
67
|
}
|
|
@@ -136,7 +136,7 @@ module.exports = function discriminator(model, name, schema, tiedValue) {
|
|
|
136
136
|
|
|
137
137
|
schema.plugins = Array.prototype.slice(baseSchema.plugins);
|
|
138
138
|
schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);
|
|
139
|
-
schema._requiredpaths
|
|
139
|
+
delete schema._requiredpaths; // reset just in case Schema#requiredPaths() was called on either schema
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// merges base schema into new discriminator schema and sets new type field.
|
|
@@ -148,6 +148,8 @@ module.exports = function discriminator(model, name, schema, tiedValue) {
|
|
|
148
148
|
|
|
149
149
|
if (!model.schema.discriminatorMapping) {
|
|
150
150
|
model.schema.discriminatorMapping = {key: key, value: null, isRoot: true};
|
|
151
|
+
}
|
|
152
|
+
if (!model.schema.discriminators) {
|
|
151
153
|
model.schema.discriminators = {};
|
|
152
154
|
}
|
|
153
155
|
|
|
@@ -14,16 +14,16 @@ var mpath = require('mpath');
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
module.exports = function getSchemaTypes(schema, doc, path) {
|
|
17
|
-
|
|
17
|
+
const pathschema = schema.path(path);
|
|
18
18
|
|
|
19
19
|
if (pathschema) {
|
|
20
20
|
return pathschema;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function search(parts, schema) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
let p = parts.length + 1;
|
|
25
|
+
let foundschema;
|
|
26
|
+
let trypath;
|
|
27
27
|
|
|
28
28
|
while (p--) {
|
|
29
29
|
trypath = parts.slice(0, p).join('.');
|
|
@@ -35,10 +35,10 @@ module.exports = function getSchemaTypes(schema, doc, path) {
|
|
|
35
35
|
return foundschema.caster;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
let schemas = null;
|
|
39
39
|
if (doc != null && foundschema.schema != null && foundschema.schema.discriminators != null) {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const discriminators = foundschema.schema.discriminators;
|
|
41
|
+
const keys = mpath.get(trypath + '.' + foundschema.schema.options.discriminatorKey,
|
|
42
42
|
doc);
|
|
43
43
|
schemas = Object.keys(discriminators).
|
|
44
44
|
reduce(function(cur, discriminator) {
|
|
@@ -56,7 +56,7 @@ module.exports = function getSchemaTypes(schema, doc, path) {
|
|
|
56
56
|
// If there is no foundschema.schema we are dealing with
|
|
57
57
|
// a path like array.$
|
|
58
58
|
if (p !== parts.length && foundschema.schema) {
|
|
59
|
-
|
|
59
|
+
let ret;
|
|
60
60
|
if (parts[p] === '$') {
|
|
61
61
|
if (p + 1 === parts.length) {
|
|
62
62
|
// comments.$
|
|
@@ -74,7 +74,7 @@ module.exports = function getSchemaTypes(schema, doc, path) {
|
|
|
74
74
|
if (schemas != null && schemas.length > 0) {
|
|
75
75
|
ret = [];
|
|
76
76
|
for (var i = 0; i < schemas.length; ++i) {
|
|
77
|
-
|
|
77
|
+
let _ret = search(parts.slice(p), schemas[i]);
|
|
78
78
|
if (_ret != null) {
|
|
79
79
|
_ret.$isUnderneathDocArray = _ret.$isUnderneathDocArray ||
|
|
80
80
|
!foundschema.schema.$isSingleNested;
|
|
@@ -104,8 +104,8 @@ module.exports = function getSchemaTypes(schema, doc, path) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// look for arrays
|
|
107
|
-
|
|
108
|
-
for (
|
|
107
|
+
const parts = path.split('.');
|
|
108
|
+
for (let i = 0; i < parts.length; ++i) {
|
|
109
109
|
if (parts[i] === '$') {
|
|
110
110
|
// Re: gh-5628, because `schema.path()` doesn't take $ into account.
|
|
111
111
|
parts[i] = '0';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = getVirtual;
|
|
4
|
+
|
|
5
|
+
/*!
|
|
6
|
+
* ignore
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function getVirtual(schema, name) {
|
|
10
|
+
if (schema.virtuals[name]) {
|
|
11
|
+
return schema.virtuals[name];
|
|
12
|
+
}
|
|
13
|
+
const parts = name.split('.');
|
|
14
|
+
let cur = '';
|
|
15
|
+
let nestedSchemaPath = '';
|
|
16
|
+
for (let i = 0; i < parts.length; ++i) {
|
|
17
|
+
cur += (cur.length > 0 ? '.' : '') + parts[i];
|
|
18
|
+
if (schema.virtuals[cur]) {
|
|
19
|
+
if (i === parts.length - 1) {
|
|
20
|
+
schema.virtuals[cur].$nestedSchemaPath = nestedSchemaPath;
|
|
21
|
+
return schema.virtuals[cur];
|
|
22
|
+
}
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (schema.paths[cur] && schema.paths[cur].schema) {
|
|
27
|
+
schema = schema.paths[cur].schema;
|
|
28
|
+
|
|
29
|
+
if (i === parts.length - 2 && schema.discriminators) {
|
|
30
|
+
// Check for embedded discriminators, don't currently support populating
|
|
31
|
+
// nested virtuals underneath embedded discriminators because that will
|
|
32
|
+
// require substantial refactoring.
|
|
33
|
+
for (let key of Object.keys(schema.discriminators)) {
|
|
34
|
+
const discriminatorSchema = schema.discriminators[key];
|
|
35
|
+
let _cur = parts[i + 1];
|
|
36
|
+
if (discriminatorSchema.virtuals[_cur]) {
|
|
37
|
+
discriminatorSchema.virtuals[_cur].$nestedSchemaPath =
|
|
38
|
+
(nestedSchemaPath.length > 0 ? '.' : '') + cur;
|
|
39
|
+
return discriminatorSchema.virtuals[_cur];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
nestedSchemaPath += (nestedSchemaPath.length > 0 ? '.' : '') + cur;
|
|
45
|
+
cur = '';
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _modifiedPaths = require('../common').modifiedPaths;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Given an update document with potential update operators (`$set`, etc.)
|
|
7
|
+
* returns an object whose keys are the directly modified paths
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} update
|
|
10
|
+
* @return {Object} modified
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
module.exports = function modifiedPaths(update) {
|
|
14
|
+
const keys = Object.keys(update);
|
|
15
|
+
const hasDollarKey = keys.filter(key => key.startsWith('$')).length > 0;
|
|
16
|
+
|
|
17
|
+
const res = {};
|
|
18
|
+
if (hasDollarKey) {
|
|
19
|
+
for (const key of keys) {
|
|
20
|
+
_modifiedPaths(update[key], '', res);
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
_modifiedPaths(update, '', res);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return res;
|
|
27
|
+
};
|
package/lib/types/array.js
CHANGED
|
@@ -6,6 +6,7 @@ const EmbeddedDocument = require('./embedded');
|
|
|
6
6
|
const Document = require('../document');
|
|
7
7
|
const ObjectId = require('./objectid');
|
|
8
8
|
const cleanModifiedSubpaths = require('../services/document/cleanModifiedSubpaths');
|
|
9
|
+
const get = require('lodash.get');
|
|
9
10
|
const internalToObjectOptions = require('../options').internalToObjectOptions;
|
|
10
11
|
const utils = require('../utils');
|
|
11
12
|
|
|
@@ -514,10 +515,10 @@ MongooseArray.mixin = {
|
|
|
514
515
|
*/
|
|
515
516
|
|
|
516
517
|
pull: function() {
|
|
517
|
-
var values = [].map.call(arguments, this._cast, this)
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
518
|
+
var values = [].map.call(arguments, this._cast, this);
|
|
519
|
+
var cur = this._parent.get(this._path);
|
|
520
|
+
var i = cur.length;
|
|
521
|
+
var mem;
|
|
521
522
|
|
|
522
523
|
while (i--) {
|
|
523
524
|
mem = cur[i];
|
|
@@ -822,7 +823,7 @@ function _isAllSubdocs(docs, ref) {
|
|
|
822
823
|
*/
|
|
823
824
|
|
|
824
825
|
function _checkManualPopulation(arr, docs) {
|
|
825
|
-
var ref = arr
|
|
826
|
+
var ref = get(arr, '_schema.caster.options.ref', null);
|
|
826
827
|
if (arr.length === 0 &&
|
|
827
828
|
docs.length > 0) {
|
|
828
829
|
if (_isAllSubdocs(docs, ref)) {
|
|
@@ -64,6 +64,13 @@ function MongooseDocumentArray(values, path, doc) {
|
|
|
64
64
|
if (doc && doc instanceof Document) {
|
|
65
65
|
arr._parent = doc;
|
|
66
66
|
arr._schema = doc.schema.path(path);
|
|
67
|
+
|
|
68
|
+
// Tricky but this may be a document array embedded in a normal array,
|
|
69
|
+
// in which case `path` would point to the embedded array. See #6405, #6398
|
|
70
|
+
if (arr._schema && !arr._schema.$isMongooseDocumentArray) {
|
|
71
|
+
arr._schema = arr._schema.casterConstructor;
|
|
72
|
+
}
|
|
73
|
+
|
|
67
74
|
arr._handlers = {
|
|
68
75
|
isNew: arr.notify('isNew'),
|
|
69
76
|
save: arr.notify('save')
|
package/lib/utils.js
CHANGED
|
@@ -538,7 +538,22 @@ exports.populate = function populate(path, select, model, match, options, subPop
|
|
|
538
538
|
throw new TypeError('utils.populate: invalid path. Expected string. Got typeof `' + typeof path + '`');
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
-
if (
|
|
541
|
+
if (Array.isArray(subPopulate)) {
|
|
542
|
+
let ret = [];
|
|
543
|
+
subPopulate.forEach(function(obj) {
|
|
544
|
+
if (/[\s]/.test(obj.path)) {
|
|
545
|
+
let copy = Object.assign({}, obj);
|
|
546
|
+
let paths = copy.path.split(' ');
|
|
547
|
+
paths.forEach(function(p) {
|
|
548
|
+
copy.path = p;
|
|
549
|
+
ret.push(exports.populate(copy)[0]);
|
|
550
|
+
});
|
|
551
|
+
} else {
|
|
552
|
+
ret.push(exports.populate(obj)[0]);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
subPopulate = exports.populate(ret);
|
|
556
|
+
} else if (typeof subPopulate === 'object') {
|
|
542
557
|
subPopulate = exports.populate(subPopulate);
|
|
543
558
|
}
|
|
544
559
|
|
|
@@ -571,8 +586,8 @@ exports.getValue = function(path, obj, map) {
|
|
|
571
586
|
* @param {Object} obj
|
|
572
587
|
*/
|
|
573
588
|
|
|
574
|
-
exports.setValue = function(path, val, obj, map) {
|
|
575
|
-
mpath.set(path, val, obj, '_doc', map);
|
|
589
|
+
exports.setValue = function(path, val, obj, map, _copying) {
|
|
590
|
+
mpath.set(path, val, obj, '_doc', map, _copying);
|
|
576
591
|
};
|
|
577
592
|
|
|
578
593
|
/*!
|
package/migrating_to_5.md
CHANGED
|
@@ -102,7 +102,43 @@ const cursorWithOptions = MyModel.
|
|
|
102
102
|
|
|
103
103
|
`Model.geoNear()` has been removed because the [MongoDB driver no longer supports it](https://github.com/mongodb/node-mongodb-native/blob/3.0.0/CHANGES_3.0.0.md#geonear-command-helper)
|
|
104
104
|
|
|
105
|
-
###
|
|
105
|
+
### Required URI encoding of connection strings
|
|
106
|
+
|
|
107
|
+
Due to changes in the MongoDB driver, connection strings must be URI encoded.
|
|
108
|
+
|
|
109
|
+
If they are not, connections may fail with an illegeal character message.
|
|
110
|
+
|
|
111
|
+
#### Passwords which contain certain characters
|
|
112
|
+
|
|
113
|
+
See a [full list of affected characters](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding).
|
|
114
|
+
|
|
115
|
+
If your app is used by a lot of different connection strings, it's possible
|
|
116
|
+
that your test cases will pass, but production passwords will fail. Encode all your connection
|
|
117
|
+
strings to be safe.
|
|
118
|
+
|
|
119
|
+
If you want to continue to use unencoded connection strings, the easiest fix is to use
|
|
120
|
+
the `mongodb-uri` module to parse the connection strings, and then produce the properly encoded
|
|
121
|
+
versions. You can use a function like this:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
const uriFormat = require('mongodb-uri')
|
|
125
|
+
function encodeMongoURI (urlString) {
|
|
126
|
+
if (urlString) {
|
|
127
|
+
let parsed = uriFormat.parse(urlString)
|
|
128
|
+
urlString = uriFormat.format(parsed);
|
|
129
|
+
}
|
|
130
|
+
return urlString;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Your un-encoded string.
|
|
135
|
+
const mongodbConnectString = "mongodb://...";
|
|
136
|
+
mongoose.connect(encodeMongoURI(mongodbConnectString)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The function above is safe to use whether the existing string is already encoded or not.
|
|
140
|
+
|
|
141
|
+
#### Domain sockets
|
|
106
142
|
|
|
107
143
|
Domain sockets must be URI encoded. For example:
|
|
108
144
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongoose",
|
|
3
3
|
"description": "Mongoose MongoDB ODM",
|
|
4
|
-
"version": "5.0.
|
|
4
|
+
"version": "5.0.17",
|
|
5
5
|
"author": "Guillermo Rauch <guillermo@learnboost.com>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongodb",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"async": "2.1.4",
|
|
23
23
|
"bson": "~1.0.4",
|
|
24
|
-
"kareem": "2.0.
|
|
24
|
+
"kareem": "2.0.7",
|
|
25
25
|
"lodash.get": "4.4.2",
|
|
26
|
-
"mongodb": "3.0.
|
|
26
|
+
"mongodb": "3.0.7",
|
|
27
27
|
"mongoose-legacy-pluralize": "1.0.2",
|
|
28
|
-
"mpath": "0.
|
|
28
|
+
"mpath": "0.4.1",
|
|
29
29
|
"mquery": "3.0.0",
|
|
30
30
|
"ms": "2.0.0",
|
|
31
31
|
"regexp-clone": "0.0.1",
|