mongoose 6.1.10 → 6.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.eslintrc.json +154 -0
  2. package/CHANGELOG.md +73 -0
  3. package/dist/browser.umd.js +233 -222
  4. package/index.js +5 -1
  5. package/lib/aggregate.js +23 -28
  6. package/lib/browserDocument.js +1 -1
  7. package/lib/cast/number.js +2 -3
  8. package/lib/cast.js +9 -7
  9. package/lib/connection.js +76 -24
  10. package/lib/cursor/AggregationCursor.js +12 -7
  11. package/lib/cursor/QueryCursor.js +11 -6
  12. package/lib/document.js +131 -122
  13. package/lib/drivers/node-mongodb-native/collection.js +12 -4
  14. package/lib/drivers/node-mongodb-native/connection.js +11 -0
  15. package/lib/error/cast.js +3 -2
  16. package/lib/error/index.js +11 -0
  17. package/lib/error/syncIndexes.js +30 -0
  18. package/lib/helpers/clone.js +51 -29
  19. package/lib/helpers/common.js +2 -2
  20. package/lib/helpers/cursor/eachAsync.js +18 -15
  21. package/lib/helpers/document/compile.js +7 -4
  22. package/lib/helpers/getFunctionName.js +6 -4
  23. package/lib/helpers/indexes/decorateDiscriminatorIndexOptions.js +14 -0
  24. package/lib/helpers/indexes/getRelatedIndexes.js +59 -0
  25. package/lib/helpers/isMongooseObject.js +9 -8
  26. package/lib/helpers/isObject.js +4 -4
  27. package/lib/helpers/model/discriminator.js +2 -1
  28. package/lib/helpers/path/parentPaths.js +10 -5
  29. package/lib/helpers/populate/assignRawDocsToIdStructure.js +4 -2
  30. package/lib/helpers/populate/assignVals.js +12 -4
  31. package/lib/helpers/populate/getModelsMapForPopulate.js +4 -4
  32. package/lib/helpers/populate/markArraySubdocsPopulated.js +3 -1
  33. package/lib/helpers/populate/modelNamesFromRefPath.js +4 -3
  34. package/lib/helpers/printJestWarning.js +2 -2
  35. package/lib/helpers/projection/applyProjection.js +77 -0
  36. package/lib/helpers/projection/hasIncludedChildren.js +36 -0
  37. package/lib/helpers/projection/isExclusive.js +5 -2
  38. package/lib/helpers/projection/isInclusive.js +5 -1
  39. package/lib/helpers/query/cast$expr.js +279 -0
  40. package/lib/helpers/query/castUpdate.js +6 -2
  41. package/lib/helpers/query/hasDollarKeys.js +7 -3
  42. package/lib/helpers/query/isOperator.js +5 -2
  43. package/lib/helpers/schema/applyPlugins.js +11 -0
  44. package/lib/helpers/schema/getIndexes.js +6 -2
  45. package/lib/helpers/schema/getPath.js +4 -2
  46. package/lib/helpers/timestamps/setupTimestamps.js +3 -8
  47. package/lib/index.js +26 -19
  48. package/lib/internal.js +10 -2
  49. package/lib/model.js +196 -171
  50. package/lib/options/SchemaTypeOptions.js +1 -1
  51. package/lib/plugins/trackTransaction.js +5 -4
  52. package/lib/query.js +159 -146
  53. package/lib/queryhelpers.js +10 -10
  54. package/lib/schema/SubdocumentPath.js +4 -3
  55. package/lib/schema/array.js +30 -21
  56. package/lib/schema/buffer.js +1 -1
  57. package/lib/schema/date.js +1 -1
  58. package/lib/schema/decimal128.js +1 -1
  59. package/lib/schema/documentarray.js +9 -11
  60. package/lib/schema/number.js +1 -1
  61. package/lib/schema/objectid.js +2 -2
  62. package/lib/schema/string.js +4 -4
  63. package/lib/schema.js +13 -8
  64. package/lib/schematype.js +86 -40
  65. package/lib/types/ArraySubdocument.js +2 -1
  66. package/lib/types/DocumentArray/index.js +10 -27
  67. package/lib/types/DocumentArray/isMongooseDocumentArray.js +5 -0
  68. package/lib/types/DocumentArray/methods/index.js +15 -3
  69. package/lib/types/array/index.js +22 -21
  70. package/lib/types/array/isMongooseArray.js +5 -0
  71. package/lib/types/array/methods/index.js +22 -23
  72. package/lib/types/buffer.js +3 -3
  73. package/lib/types/map.js +3 -4
  74. package/lib/utils.js +19 -10
  75. package/package.json +34 -168
  76. package/tools/repl.js +1 -1
  77. package/tsconfig.json +8 -0
  78. package/types/Error.d.ts +129 -0
  79. package/types/PipelineStage.d.ts +272 -0
  80. package/{index.d.ts → types/index.d.ts} +169 -481
  81. package/lib/types/array/ArrayWrapper.js +0 -981
@@ -43,7 +43,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
43
43
  let allSchemaTypes = getSchemaTypes(model, modelSchema, null, options.path);
44
44
  allSchemaTypes = Array.isArray(allSchemaTypes) ? allSchemaTypes : [allSchemaTypes].filter(v => v != null);
45
45
 
46
- if (allSchemaTypes.length <= 0 && options.strictPopulate !== false && options._localModel != null) {
46
+ if (allSchemaTypes.length === 0 && options.strictPopulate !== false && options._localModel != null) {
47
47
  return new MongooseError('Cannot populate path `' + (options._fullPath || options.path) +
48
48
  '` because it is not in your schema. Set the `strictPopulate` option ' +
49
49
  'to false to override.');
@@ -607,7 +607,7 @@ function _getLocalFieldValues(doc, localField, model, options, virtual, schema)
607
607
  const getters = 'getters' in _populateOptions ?
608
608
  _populateOptions.getters :
609
609
  get(virtual, 'options.getters', false);
610
- if (localFieldGetters.length > 0 && getters) {
610
+ if (localFieldGetters.length !== 0 && getters) {
611
611
  const hydratedDoc = (doc.$__ != null) ? doc : model.hydrate(doc);
612
612
  const localFieldValue = utils.getValue(localField, doc);
613
613
  if (Array.isArray(localFieldValue)) {
@@ -644,7 +644,7 @@ function convertTo_id(val, schema) {
644
644
  rawVal[i] = rawVal[i]._id;
645
645
  }
646
646
  }
647
- if (val.isMongooseArray && val.$schema()) {
647
+ if (utils.isMongooseArray(val) && val.$schema()) {
648
648
  return val.$schema()._castForPopulate(val, val.$parent());
649
649
  }
650
650
 
@@ -694,7 +694,7 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
694
694
  if (schematype != null &&
695
695
  schematype.$isMongooseArray &&
696
696
  schematype.caster.discriminators != null &&
697
- Object.keys(schematype.caster.discriminators).length > 0) {
697
+ Object.keys(schematype.caster.discriminators).length !== 0) {
698
698
  const subdocs = utils.getValue(cur, doc);
699
699
  const remnant = options.path.substring(cur.length + 1);
700
700
  const discriminatorKey = schematype.caster.schema.options.discriminatorKey;
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const utils = require('../../utils');
4
+
3
5
  /*!
4
6
  * If populating a path within a document array, make sure each
5
7
  * subdoc within the array knows its subpaths are populated.
@@ -29,7 +31,7 @@ module.exports = function markArraySubdocsPopulated(doc, populated) {
29
31
  continue;
30
32
  }
31
33
 
32
- if (val.isMongooseDocumentArray) {
34
+ if (utils.isMongooseDocumentArray(val)) {
33
35
  for (let j = 0; j < val.length; ++j) {
34
36
  val[j].populated(rest, item._docs[id] == null ? void 0 : item._docs[id][j], item);
35
37
  }
@@ -7,6 +7,8 @@ const mpath = require('mpath');
7
7
  const util = require('util');
8
8
  const utils = require('../../utils');
9
9
 
10
+ const hasNumericPropRE = /(\.\d+$|\.\d+\.)/g;
11
+
10
12
  module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, modelSchema, queryProjection) {
11
13
  if (refPath == null) {
12
14
  return [];
@@ -20,10 +22,9 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
20
22
  // If populated path has numerics, the end `refPath` should too. For example,
21
23
  // if populating `a.0.b` instead of `a.b` and `b` has `refPath = a.c`, we
22
24
  // should return `a.0.c` for the refPath.
23
- const hasNumericProp = /(\.\d+$|\.\d+\.)/g;
24
25
 
25
- if (hasNumericProp.test(populatedPath)) {
26
- const chunks = populatedPath.split(hasNumericProp);
26
+ if (hasNumericPropRE.test(populatedPath)) {
27
+ const chunks = populatedPath.split(hasNumericPropRE);
27
28
 
28
29
  if (chunks[chunks.length - 1] === '') {
29
30
  throw new Error('Can\'t populate individual element in an array');
@@ -6,12 +6,12 @@ if (typeof jest !== 'undefined' && typeof window !== 'undefined') {
6
6
  utils.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' +
7
7
  'with Jest\'s default jsdom test environment. Please make sure you read ' +
8
8
  'Mongoose\'s docs on configuring Jest to test Node.js apps: ' +
9
- 'http://mongoosejs.com/docs/jest.html');
9
+ 'https://mongoosejs.com/docs/jest.html');
10
10
  }
11
11
 
12
12
  if (typeof jest !== 'undefined' && process.nextTick.toString().indexOf('nextTick') === -1) {
13
13
  utils.warn('Mongoose: looks like you\'re trying to test a Mongoose app ' +
14
14
  'with Jest\'s mock timers enabled. Please make sure you read ' +
15
15
  'Mongoose\'s docs on configuring Jest to test Node.js apps: ' +
16
- 'http://mongoosejs.com/docs/jest.html');
16
+ 'https://mongoosejs.com/docs/jest.html');
17
17
  }
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const hasIncludedChildren = require('./hasIncludedChildren');
4
+ const isExclusive = require('./isExclusive');
5
+ const isInclusive = require('./isInclusive');
6
+ const isPOJO = require('../../utils').isPOJO;
7
+
8
+ module.exports = function applyProjection(doc, projection, _hasIncludedChildren) {
9
+ if (projection == null) {
10
+ return doc;
11
+ }
12
+ if (doc == null) {
13
+ return doc;
14
+ }
15
+
16
+ let exclude = null;
17
+ if (isInclusive(projection)) {
18
+ exclude = false;
19
+ } else if (isExclusive(projection)) {
20
+ exclude = true;
21
+ }
22
+
23
+ if (exclude == null) {
24
+ return doc;
25
+ } else if (exclude) {
26
+ _hasIncludedChildren = _hasIncludedChildren || hasIncludedChildren(projection);
27
+ return applyExclusiveProjection(doc, projection, _hasIncludedChildren);
28
+ } else {
29
+ _hasIncludedChildren = _hasIncludedChildren || hasIncludedChildren(projection);
30
+ return applyInclusiveProjection(doc, projection, _hasIncludedChildren);
31
+ }
32
+ };
33
+
34
+ function applyExclusiveProjection(doc, projection, hasIncludedChildren, projectionLimb, prefix) {
35
+ if (doc == null || typeof doc !== 'object') {
36
+ return doc;
37
+ }
38
+ const ret = { ...doc };
39
+ projectionLimb = prefix ? (projectionLimb || {}) : projection;
40
+
41
+ for (const key of Object.keys(ret)) {
42
+ const fullPath = prefix ? prefix + '.' + key : key;
43
+ if (projection.hasOwnProperty(fullPath) || projectionLimb.hasOwnProperty(key)) {
44
+ if (isPOJO(projection[fullPath]) || isPOJO(projectionLimb[key])) {
45
+ ret[key] = applyExclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
46
+ } else {
47
+ delete ret[key];
48
+ }
49
+ } else if (hasIncludedChildren[fullPath]) {
50
+ ret[key] = applyExclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
51
+ }
52
+ }
53
+ return ret;
54
+ }
55
+
56
+ function applyInclusiveProjection(doc, projection, hasIncludedChildren, projectionLimb, prefix) {
57
+ if (doc == null || typeof doc !== 'object') {
58
+ return doc;
59
+ }
60
+ const ret = { ...doc };
61
+ projectionLimb = prefix ? (projectionLimb || {}) : projection;
62
+
63
+ for (const key of Object.keys(ret)) {
64
+ const fullPath = prefix ? prefix + '.' + key : key;
65
+ if (projection.hasOwnProperty(fullPath) || projectionLimb.hasOwnProperty(key)) {
66
+ if (isPOJO(projection[fullPath]) || isPOJO(projectionLimb[key])) {
67
+ ret[key] = applyInclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
68
+ }
69
+ continue;
70
+ } else if (hasIncludedChildren[fullPath]) {
71
+ ret[key] = applyInclusiveProjection(ret[key], projection, hasIncludedChildren, projectionLimb[key], fullPath);
72
+ } else {
73
+ delete ret[key];
74
+ }
75
+ }
76
+ return ret;
77
+ }
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ /*!
4
+ * Creates an object that precomputes whether a given path has child fields in
5
+ * the projection.
6
+ *
7
+ * ####Example:
8
+ * const res = hasIncludedChildren({ 'a.b.c': 0 });
9
+ * res.a; // 1
10
+ * res['a.b']; // 1
11
+ * res['a.b.c']; // 1
12
+ * res['a.c']; // undefined
13
+ */
14
+
15
+ module.exports = function hasIncludedChildren(fields) {
16
+ const hasIncludedChildren = {};
17
+ const keys = Object.keys(fields);
18
+
19
+ for (const key of keys) {
20
+ if (key.indexOf('.') === -1) {
21
+ hasIncludedChildren[key] = 1;
22
+ continue;
23
+ }
24
+ const parts = key.split('.');
25
+ let c = parts[0];
26
+
27
+ for (let i = 0; i < parts.length; ++i) {
28
+ hasIncludedChildren[c] = 1;
29
+ if (i + 1 < parts.length) {
30
+ c = c + '.' + parts[i + 1];
31
+ }
32
+ }
33
+ }
34
+
35
+ return hasIncludedChildren;
36
+ };
@@ -21,8 +21,11 @@ module.exports = function isExclusive(projection) {
21
21
  while (ki--) {
22
22
  // Does this projection explicitly define inclusion/exclusion?
23
23
  // Explicitly avoid `$meta` and `$slice`
24
- if (keys[ki] !== '_id' && isDefiningProjection(projection[keys[ki]])) {
25
- exclude = !projection[keys[ki]];
24
+ const key = keys[ki];
25
+ if (key !== '_id' && isDefiningProjection(projection[key])) {
26
+ exclude = (projection[key] != null && typeof projection[key] === 'object') ?
27
+ isExclusive(projection[key]) :
28
+ !projection[key];
26
29
  break;
27
30
  }
28
31
  }
@@ -26,7 +26,11 @@ module.exports = function isInclusive(projection) {
26
26
  // If field is truthy (1, true, etc.) and not an object, then this
27
27
  // projection must be inclusive. If object, assume its $meta, $slice, etc.
28
28
  if (isDefiningProjection(projection[prop]) && !!projection[prop]) {
29
- return true;
29
+ if (projection[prop] != null && typeof projection[prop] === 'object') {
30
+ return isInclusive(projection[prop]);
31
+ } else {
32
+ return !!projection[prop];
33
+ }
30
34
  }
31
35
  }
32
36
 
@@ -0,0 +1,279 @@
1
+ 'use strict';
2
+
3
+ const CastError = require('../../error/cast');
4
+ const StrictModeError = require('../../error/strict');
5
+ const castNumber = require('../../cast/number');
6
+
7
+ const booleanComparison = new Set(['$and', '$or', '$not']);
8
+ const comparisonOperator = new Set(['$cmp', '$eq', '$lt', '$lte', '$gt', '$gte']);
9
+ const arithmeticOperatorArray = new Set([
10
+ // avoid casting '$add' or '$subtract', because expressions can be either number or date,
11
+ // and we don't have a good way of inferring which arguments should be numbers and which should
12
+ // be dates.
13
+ '$multiply',
14
+ '$divide',
15
+ '$log',
16
+ '$mod',
17
+ '$trunc',
18
+ '$avg',
19
+ '$max',
20
+ '$min',
21
+ '$stdDevPop',
22
+ '$stdDevSamp',
23
+ '$sum'
24
+ ]);
25
+ const arithmeticOperatorNumber = new Set([
26
+ '$abs',
27
+ '$exp',
28
+ '$ceil',
29
+ '$floor',
30
+ '$ln',
31
+ '$log10',
32
+ '$round',
33
+ '$sqrt',
34
+ '$sin',
35
+ '$cos',
36
+ '$tan',
37
+ '$asin',
38
+ '$acos',
39
+ '$atan',
40
+ '$atan2',
41
+ '$asinh',
42
+ '$acosh',
43
+ '$atanh',
44
+ '$sinh',
45
+ '$cosh',
46
+ '$tanh',
47
+ '$degreesToRadians',
48
+ '$radiansToDegrees'
49
+ ]);
50
+ const arrayElementOperators = new Set([
51
+ '$arrayElemAt',
52
+ '$first',
53
+ '$last'
54
+ ]);
55
+ const dateOperators = new Set([
56
+ '$year',
57
+ '$month',
58
+ '$week',
59
+ '$dayOfMonth',
60
+ '$dayOfYear',
61
+ '$hour',
62
+ '$minute',
63
+ '$second',
64
+ '$isoDayOfWeek',
65
+ '$isoWeekYear',
66
+ '$isoWeek',
67
+ '$millisecond'
68
+ ]);
69
+
70
+ module.exports = function cast$expr(val, schema, strictQuery) {
71
+ if (typeof val !== 'object' || val === null) {
72
+ throw new Error('`$expr` must be an object');
73
+ }
74
+
75
+ return _castExpression(val, schema, strictQuery);
76
+ };
77
+
78
+ function _castExpression(val, schema, strictQuery) {
79
+ if (isPath(val)) {
80
+ // Assume path
81
+ return val;
82
+ }
83
+
84
+ if (val.$cond != null) {
85
+ if (Array.isArray(val.$cond)) {
86
+ val.$cond = val.$cond.map(expr => _castExpression(expr, schema, strictQuery));
87
+ } else {
88
+ val.$cond.if = _castExpression(val.$cond.if, schema, strictQuery);
89
+ val.$cond.then = _castExpression(val.$cond.then, schema, strictQuery);
90
+ val.$cond.else = _castExpression(val.$cond.else, schema, strictQuery);
91
+ }
92
+ } else if (val.$ifNull != null) {
93
+ val.$ifNull.map(v => _castExpression(v, schema, strictQuery));
94
+ } else if (val.$switch != null) {
95
+ val.branches.map(v => _castExpression(v, schema, strictQuery));
96
+ val.default = _castExpression(val.default, schema, strictQuery);
97
+ }
98
+
99
+ const keys = Object.keys(val);
100
+ for (const key of keys) {
101
+ if (booleanComparison.has(key)) {
102
+ val[key] = val[key].map(v => _castExpression(v, schema, strictQuery));
103
+ } else if (comparisonOperator.has(key)) {
104
+ val[key] = castComparison(val[key], schema, strictQuery);
105
+ } else if (arithmeticOperatorArray.has(key)) {
106
+ val[key] = castArithmetic(val[key], schema, strictQuery);
107
+ } else if (arithmeticOperatorNumber.has(key)) {
108
+ val[key] = castNumberOperator(val[key], schema, strictQuery);
109
+ }
110
+ }
111
+
112
+ if (val.$in) {
113
+ val.$in = castIn(val.$in, schema, strictQuery);
114
+ }
115
+ if (val.$size) {
116
+ val.$size = castNumberOperator(val.$size, schema, strictQuery);
117
+ }
118
+
119
+ _omitUndefined(val);
120
+
121
+ return val;
122
+ }
123
+
124
+ function _omitUndefined(val) {
125
+ const keys = Object.keys(val);
126
+ for (let i = 0, len = keys.length; i < len; ++i) {
127
+ (val[keys[i]] === void 0) && delete val[keys[i]];
128
+ }
129
+ }
130
+
131
+ // { $op: <number> }
132
+ function castNumberOperator(val) {
133
+ if (!isLiteral(val)) {
134
+ return val;
135
+ }
136
+
137
+ try {
138
+ return castNumber(val);
139
+ } catch (err) {
140
+ throw new CastError('Number', val);
141
+ }
142
+ }
143
+
144
+ function castIn(val, schema, strictQuery) {
145
+ const path = val[1];
146
+ if (!isPath(path)) {
147
+ return val;
148
+ }
149
+ const search = val[0];
150
+
151
+ const schematype = schema.path(path.slice(1));
152
+ if (schematype === null) {
153
+ if (strictQuery === false) {
154
+ return val;
155
+ } else if (strictQuery === 'throw') {
156
+ throw new StrictModeError('$in');
157
+ }
158
+
159
+ return void 0;
160
+ }
161
+
162
+ if (!schematype.$isMongooseArray) {
163
+ throw new Error('Path must be an array for $in');
164
+ }
165
+
166
+ return [
167
+ schematype.$isMongooseDocumentArray ? schematype.$embeddedSchemaType.cast(search) : schematype.caster.cast(search),
168
+ path
169
+ ];
170
+ }
171
+
172
+ // { $op: [<number>, <number>] }
173
+ function castArithmetic(val) {
174
+ if (!Array.isArray(val)) {
175
+ if (!isLiteral(val)) {
176
+ return val;
177
+ }
178
+ try {
179
+ return castNumber(val);
180
+ } catch (err) {
181
+ throw new CastError('Number', val);
182
+ }
183
+ }
184
+
185
+ return val.map(v => {
186
+ if (!isLiteral(v)) {
187
+ return v;
188
+ }
189
+ try {
190
+ return castNumber(v);
191
+ } catch (err) {
192
+ throw new CastError('Number', v);
193
+ }
194
+ });
195
+ }
196
+
197
+ // { $op: [expression, expression] }
198
+ function castComparison(val, schema, strictQuery) {
199
+ if (!Array.isArray(val) || val.length !== 2) {
200
+ throw new Error('Comparison operator must be an array of length 2');
201
+ }
202
+
203
+ val[0] = _castExpression(val[0], schema, strictQuery);
204
+ const lhs = val[0];
205
+
206
+ if (isLiteral(val[1])) {
207
+ let path = null;
208
+ let schematype = null;
209
+ let caster = null;
210
+ if (isPath(lhs)) {
211
+ path = lhs.slice(1);
212
+ schematype = schema.path(path);
213
+ } else if (typeof lhs === 'object' && lhs != null) {
214
+ for (const key of Object.keys(lhs)) {
215
+ if (dateOperators.has(key) && isPath(lhs[key])) {
216
+ path = lhs[key].slice(1) + '.' + key;
217
+ caster = castNumber;
218
+ } else if (arrayElementOperators.has(key) && isPath(lhs[key])) {
219
+ path = lhs[key].slice(1) + '.' + key;
220
+ schematype = schema.path(lhs[key].slice(1));
221
+ if (schematype != null) {
222
+ if (schematype.$isMongooseDocumentArray) {
223
+ schematype = schematype.$embeddedSchemaType;
224
+ } else if (schematype.$isMongooseArray) {
225
+ schematype = schematype.caster;
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+
232
+ const is$literal = typeof val[1] === 'object' && val[1] != null && val[1].$literal != null;
233
+ if (schematype != null) {
234
+ if (is$literal) {
235
+ val[1] = { $literal: schematype.cast(val[1].$literal) };
236
+ } else {
237
+ val[1] = schematype.cast(val[1]);
238
+ }
239
+ } else if (caster != null) {
240
+ if (is$literal) {
241
+ try {
242
+ val[1] = { $literal: caster(val[1].$literal) };
243
+ } catch (err) {
244
+ throw new CastError(caster.name.replace(/^cast/, ''), val[1], path + '.$literal');
245
+ }
246
+ } else {
247
+ try {
248
+ val[1] = caster(val[1]);
249
+ } catch (err) {
250
+ throw new CastError(caster.name.replace(/^cast/, ''), val[1], path);
251
+ }
252
+ }
253
+ } else if (path != null && strictQuery === true) {
254
+ return void 0;
255
+ } else if (path != null && strictQuery === 'throw') {
256
+ throw new StrictModeError(path);
257
+ }
258
+ } else {
259
+ val[1] = _castExpression(val[1]);
260
+ }
261
+
262
+ return val;
263
+ }
264
+
265
+ function isPath(val) {
266
+ return typeof val === 'string' && val[0] === '$';
267
+ }
268
+
269
+ function isLiteral(val) {
270
+ if (typeof val === 'string' && val[0] === '$') {
271
+ return false;
272
+ }
273
+ if (typeof val === 'object' && val !== null && Object.keys(val).find(key => key[0] === '$')) {
274
+ // The `$literal` expression can make an object a literal
275
+ // https://docs.mongodb.com/manual/reference/operator/aggregation/literal/#mongodb-expression-exp.-literal
276
+ return val.$literal != null;
277
+ }
278
+ return true;
279
+ }
@@ -41,9 +41,10 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
41
41
  }
42
42
  return obj;
43
43
  }
44
+
44
45
  if (schema.options.strict === 'throw' && obj.hasOwnProperty(schema.options.discriminatorKey)) {
45
46
  throw new StrictModeError(schema.options.discriminatorKey);
46
- } else if (context._mongooseOptions != null && !context._mongooseOptions.overwriteDiscriminatorKey) {
47
+ } else if (!options.overwriteDiscriminatorKey) {
47
48
  delete obj[schema.options.discriminatorKey];
48
49
  }
49
50
  if (options.upsert) {
@@ -353,7 +354,10 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
353
354
  }
354
355
 
355
356
  if (Array.isArray(obj[key]) && (op === '$addToSet' || op === '$push') && key !== '$each') {
356
- if (schematype && schematype.caster && !schematype.caster.$isMongooseArray) {
357
+ if (schematype &&
358
+ schematype.caster &&
359
+ !schematype.caster.$isMongooseArray &&
360
+ !schematype.caster[schemaMixedSymbol]) {
357
361
  obj[key] = { $each: obj[key] };
358
362
  }
359
363
  }
@@ -4,16 +4,20 @@
4
4
  * ignore
5
5
  */
6
6
 
7
- module.exports = function(obj) {
8
- if (obj == null || typeof obj !== 'object') {
7
+ module.exports = function hasDollarKeys(obj) {
8
+
9
+ if (typeof obj !== 'object' || obj === null) {
9
10
  return false;
10
11
  }
12
+
11
13
  const keys = Object.keys(obj);
12
14
  const len = keys.length;
15
+
13
16
  for (let i = 0; i < len; ++i) {
14
- if (keys[i].startsWith('$')) {
17
+ if (keys[i][0] === '$') {
15
18
  return true;
16
19
  }
17
20
  }
21
+
18
22
  return false;
19
23
  };
@@ -7,5 +7,8 @@ const specialKeys = new Set([
7
7
  ]);
8
8
 
9
9
  module.exports = function isOperator(path) {
10
- return path.startsWith('$') && !specialKeys.has(path);
11
- };
10
+ return (
11
+ path[0] === '$' &&
12
+ !specialKeys.has(path)
13
+ );
14
+ };
@@ -7,7 +7,18 @@ module.exports = function applyPlugins(schema, plugins, options, cacheKey) {
7
7
  schema[cacheKey] = true;
8
8
 
9
9
  if (!options || !options.skipTopLevel) {
10
+ let pluginTags = null;
10
11
  for (const plugin of plugins) {
12
+ const tags = plugin[1] == null ? null : plugin[1].tags;
13
+ if (!Array.isArray(tags)) {
14
+ schema.plugin(plugin[0], plugin[1]);
15
+ continue;
16
+ }
17
+
18
+ pluginTags = pluginTags || new Set(schema.options.pluginTags || []);
19
+ if (!tags.find(tag => pluginTags.has(tag))) {
20
+ continue;
21
+ }
11
22
  schema.plugin(plugin[0], plugin[1]);
12
23
  }
13
24
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const get = require('../get');
4
4
  const helperIsObject = require('../isObject');
5
+ const decorateDiscriminatorIndexOptions = require('../indexes/decorateDiscriminatorIndexOptions');
5
6
 
6
7
  /*!
7
8
  * Gather all indexes defined in the schema, including single nested,
@@ -88,6 +89,7 @@ module.exports = function getIndexes(schema) {
88
89
  }
89
90
 
90
91
  const indexName = options && options.name;
92
+
91
93
  if (typeof indexName === 'string') {
92
94
  if (indexByName.has(indexName)) {
93
95
  Object.assign(indexByName.get(indexName), field);
@@ -108,9 +110,11 @@ module.exports = function getIndexes(schema) {
108
110
  fixSubIndexPaths(schema, prefix);
109
111
  } else {
110
112
  schema._indexes.forEach(function(index) {
111
- if (!('background' in index[1])) {
112
- index[1].background = true;
113
+ const options = index[1];
114
+ if (!('background' in options)) {
115
+ options.background = true;
113
116
  }
117
+ decorateDiscriminatorIndexOptions(schema, options);
114
118
  });
115
119
  indexes = indexes.concat(schema._indexes);
116
120
  }
@@ -5,6 +5,8 @@
5
5
  * needing to put `.0.`, so `getPath(schema, 'docArr.elProp')` works.
6
6
  */
7
7
 
8
+ const numberRE = /^\d+$/;
9
+
8
10
  module.exports = function getPath(schema, path) {
9
11
  let schematype = schema.path(path);
10
12
  if (schematype != null) {
@@ -16,7 +18,7 @@ module.exports = function getPath(schema, path) {
16
18
  let isArray = false;
17
19
 
18
20
  for (const piece of pieces) {
19
- if (/^\d+$/.test(piece) && isArray) {
21
+ if (isArray && numberRE.test(piece)) {
20
22
  continue;
21
23
  }
22
24
  cur = cur.length === 0 ? piece : cur + '.' + piece;
@@ -25,7 +27,7 @@ module.exports = function getPath(schema, path) {
25
27
  if (schematype != null && schematype.schema) {
26
28
  schema = schematype.schema;
27
29
  cur = '';
28
- if (schematype.$isMongooseDocumentArray) {
30
+ if (!isArray && schematype.$isMongooseDocumentArray) {
29
31
  isArray = true;
30
32
  }
31
33
  }
@@ -47,20 +47,15 @@ module.exports = function setupTimestamps(schema, timestamps) {
47
47
  const defaultTimestamp = currentTime != null ?
48
48
  currentTime() :
49
49
  this.ownerDocument().constructor.base.now();
50
- const auto_id = this._id && this._id.auto;
51
50
 
52
51
  if (!skipCreatedAt && this.isNew && createdAt && !this.$__getValue(createdAt) && this.$__isSelected(createdAt)) {
53
- this.$set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp);
52
+ this.$set(createdAt, defaultTimestamp);
54
53
  }
55
54
 
56
55
  if (!skipUpdatedAt && updatedAt && (this.isNew || this.$isModified())) {
57
56
  let ts = defaultTimestamp;
58
- if (this.isNew) {
59
- if (createdAt != null) {
60
- ts = this.$__getValue(createdAt);
61
- } else if (auto_id) {
62
- ts = this._id.getTimestamp();
63
- }
57
+ if (this.isNew && createdAt != null) {
58
+ ts = this.$__getValue(createdAt);
64
59
  }
65
60
  this.$set(updatedAt, ts);
66
61
  }