mongoose 6.1.8 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.eslintrc.json +150 -0
  2. package/CHANGELOG.md +50 -0
  3. package/dist/browser.umd.js +191 -187
  4. package/lib/aggregate.js +1 -1
  5. package/lib/cast/objectid.js +1 -2
  6. package/lib/cast.js +21 -8
  7. package/lib/connection.js +33 -3
  8. package/lib/document.js +84 -65
  9. package/lib/error/index.js +11 -0
  10. package/lib/error/syncIndexes.js +30 -0
  11. package/lib/helpers/clone.js +40 -27
  12. package/lib/helpers/common.js +2 -2
  13. package/lib/helpers/getFunctionName.js +6 -4
  14. package/lib/helpers/isMongooseObject.js +9 -8
  15. package/lib/helpers/isObject.js +4 -4
  16. package/lib/helpers/model/discriminator.js +2 -1
  17. package/lib/helpers/path/parentPaths.js +10 -5
  18. package/lib/helpers/populate/assignRawDocsToIdStructure.js +4 -2
  19. package/lib/helpers/populate/assignVals.js +8 -4
  20. package/lib/helpers/populate/getModelsMapForPopulate.js +6 -6
  21. package/lib/helpers/populate/markArraySubdocsPopulated.js +3 -1
  22. package/lib/helpers/populate/modelNamesFromRefPath.js +6 -5
  23. package/lib/helpers/query/cast$expr.js +284 -0
  24. package/lib/helpers/query/castUpdate.js +6 -2
  25. package/lib/helpers/schema/applyPlugins.js +11 -0
  26. package/lib/helpers/schema/getPath.js +4 -2
  27. package/lib/helpers/setDefaultsOnInsert.js +16 -7
  28. package/lib/helpers/timestamps/setupTimestamps.js +3 -8
  29. package/lib/helpers/update/applyTimestampsToChildren.js +2 -2
  30. package/lib/helpers/update/castArrayFilters.js +1 -1
  31. package/lib/helpers/update/updatedPathsByArrayFilter.js +1 -1
  32. package/lib/index.js +16 -40
  33. package/lib/internal.js +1 -1
  34. package/lib/model.js +37 -18
  35. package/lib/plugins/trackTransaction.js +4 -3
  36. package/lib/query.js +15 -13
  37. package/lib/queryhelpers.js +1 -1
  38. package/lib/schema/SubdocumentPath.js +1 -1
  39. package/lib/schema/array.js +18 -16
  40. package/lib/schema/documentarray.js +6 -9
  41. package/lib/schema/objectid.js +1 -1
  42. package/lib/schema.js +5 -4
  43. package/lib/schematype.js +74 -26
  44. package/lib/types/ArraySubdocument.js +2 -1
  45. package/lib/types/DocumentArray/index.js +9 -26
  46. package/lib/types/DocumentArray/isMongooseDocumentArray.js +5 -0
  47. package/lib/types/DocumentArray/methods/index.js +15 -3
  48. package/lib/types/array/index.js +21 -20
  49. package/lib/types/array/isMongooseArray.js +5 -0
  50. package/lib/types/array/methods/index.js +12 -12
  51. package/lib/utils.js +15 -8
  52. package/package.json +24 -156
  53. package/tools/repl.js +1 -1
  54. package/tsconfig.json +10 -0
  55. package/{index.d.ts → types/index.d.ts} +146 -86
@@ -88,7 +88,7 @@ function modifiedPaths(update, path, result) {
88
88
  cur += '.' + sp[i];
89
89
  }
90
90
  }
91
- if (isMongooseObject(val) && !Buffer.isBuffer(val)) {
91
+ if (!Buffer.isBuffer(val) && isMongooseObject(val)) {
92
92
  val = val.toObject({ transform: false, virtuals: false });
93
93
  }
94
94
  if (shouldFlatten(val)) {
@@ -108,7 +108,7 @@ function shouldFlatten(val) {
108
108
  typeof val === 'object' &&
109
109
  !(val instanceof Date) &&
110
110
  !(val instanceof ObjectId) &&
111
- (!Array.isArray(val) || val.length > 0) &&
111
+ (!Array.isArray(val) || val.length !== 0) &&
112
112
  !(val instanceof Buffer) &&
113
113
  !(val instanceof Decimal128) &&
114
114
  !(val instanceof Binary);
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ const functionNameRE = /^function\s*([^\s(]+)/;
4
+
3
5
  module.exports = function(fn) {
4
- if (fn.name) {
5
- return fn.name;
6
- }
7
- return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
6
+ return (
7
+ fn.name ||
8
+ (fn.toString().trim().match(functionNameRE) || [])[1]
9
+ );
8
10
  };
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const isMongooseArray = require('../types/array/isMongooseArray').isMongooseArray;
3
4
  /*!
4
5
  * Returns if `v` is a mongoose object that has a `toObject()` method we can use.
5
6
  *
@@ -10,12 +11,12 @@
10
11
  */
11
12
 
12
13
  module.exports = function(v) {
13
- if (v == null) {
14
- return false;
15
- }
16
-
17
- return v.$__ != null || // Document
18
- v.isMongooseArray || // Array or Document Array
19
- v.isMongooseBuffer || // Buffer
20
- v.$isMongooseMap; // Map
14
+ return (
15
+ v != null && (
16
+ isMongooseArray(v) || // Array or Document Array
17
+ v.$__ != null || // Document
18
+ v.isMongooseBuffer || // Buffer
19
+ v.$isMongooseMap // Map
20
+ )
21
+ );
21
22
  };
@@ -9,8 +9,8 @@
9
9
  */
10
10
 
11
11
  module.exports = function(arg) {
12
- if (Buffer.isBuffer(arg)) {
13
- return true;
14
- }
15
- return Object.prototype.toString.call(arg) === '[object Object]';
12
+ return (
13
+ Buffer.isBuffer(arg) ||
14
+ Object.prototype.toString.call(arg) === '[object Object]'
15
+ );
16
16
  };
@@ -17,6 +17,7 @@ const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
17
17
  */
18
18
 
19
19
  module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) {
20
+
20
21
  if (!(schema && schema.instanceOfSchema)) {
21
22
  throw new Error('You must pass a valid discriminator Schema');
22
23
  }
@@ -201,7 +202,7 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
201
202
 
202
203
  model.schema.discriminators[name] = schema;
203
204
 
204
- if (model.discriminators[name]) {
205
+ if (model.discriminators[name] && !schema.options.overwriteModels) {
205
206
  throw new Error('Discriminator with name "' + name + '" already exists');
206
207
  }
207
208
 
@@ -1,12 +1,17 @@
1
1
  'use strict';
2
2
 
3
+ const dotRE = /\./g;
3
4
  module.exports = function parentPaths(path) {
4
- const pieces = path.split('.');
5
+ if (path.indexOf('.') === -1) {
6
+ return [path];
7
+ }
8
+ const pieces = path.split(dotRE);
9
+ const len = pieces.length;
10
+ const ret = new Array(len);
5
11
  let cur = '';
6
- const ret = [];
7
- for (let i = 0; i < pieces.length; ++i) {
8
- cur += (cur.length > 0 ? '.' : '') + pieces[i];
9
- ret.push(cur);
12
+ for (let i = 0; i < len; ++i) {
13
+ cur += (cur.length !== 0) ? '.' + pieces[i] : pieces[i];
14
+ ret[i] = cur;
10
15
  }
11
16
 
12
17
  return ret;
@@ -35,11 +35,13 @@ function assignRawDocsToIdStructure(rawIds, resultDocs, resultOrder, options, re
35
35
  let sid;
36
36
  let id;
37
37
 
38
- if (rawIds.isMongooseArrayProxy) {
38
+ if (utils.isMongooseArray(rawIds)) {
39
39
  rawIds = rawIds.__array;
40
40
  }
41
41
 
42
- for (let i = 0; i < rawIds.length; ++i) {
42
+ let i = 0;
43
+ const len = rawIds.length;
44
+ for (i = 0; i < len; ++i) {
43
45
  id = rawIds[i];
44
46
 
45
47
  if (Array.isArray(id)) {
@@ -241,15 +241,19 @@ function valueFilter(val, assignmentOpts, populateOptions, allIds) {
241
241
  }
242
242
  }
243
243
 
244
+ const rLen = ret.length;
244
245
  // Since we don't want to have to create a new mongoosearray, make sure to
245
246
  // modify the array in place
246
- while (val.length > ret.length) {
247
+ while (val.length > rLen) {
247
248
  Array.prototype.pop.apply(val, []);
248
249
  }
249
- for (let i = 0; i < ret.length; ++i) {
250
- if (val.isMongooseArrayProxy) {
250
+ let i = 0;
251
+ if (utils.isMongooseArray(val)) {
252
+ for (i = 0; i < rLen; ++i) {
251
253
  val.set(i, ret[i], true);
252
- } else {
254
+ }
255
+ } else {
256
+ for (i = 0; i < rLen; ++i) {
253
257
  val[i] = ret[i];
254
258
  }
255
259
  }
@@ -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,9 +694,9 @@ 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
- const remnant = options.path.substr(cur.length + 1);
699
+ const remnant = options.path.substring(cur.length + 1);
700
700
  const discriminatorKey = schematype.caster.schema.options.discriminatorKey;
701
701
  modelNames = [];
702
702
  for (const subdoc of subdocs) {
@@ -708,7 +708,7 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
708
708
  }
709
709
  const _path = discriminatorSchema.path(remnant);
710
710
  if (_path == null || _path.options.refPath == null) {
711
- const docValue = utils.getValue(data.localField.substr(cur.length + 1), subdoc);
711
+ const docValue = utils.getValue(data.localField.substring(cur.length + 1), subdoc);
712
712
  ret.forEach((v, i) => {
713
713
  if (v === docValue) {
714
714
  ret[i] = SkipPopulateValue(v);
@@ -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');
@@ -35,8 +36,8 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
35
36
  for (let i = 0; i < chunks.length; i += 2) {
36
37
  const chunk = chunks[i];
37
38
  if (_remaining.startsWith(chunk + '.')) {
38
- _refPath += _remaining.substr(0, chunk.length) + chunks[i + 1];
39
- _remaining = _remaining.substr(chunk.length + 1);
39
+ _refPath += _remaining.substring(0, chunk.length) + chunks[i + 1];
40
+ _remaining = _remaining.substring(chunk.length + 1);
40
41
  } else if (i === chunks.length - 1) {
41
42
  _refPath += _remaining;
42
43
  _remaining = '';
@@ -0,0 +1,284 @@
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 (const key of keys) {
127
+ if (val[key] === void 0) {
128
+ delete val[key];
129
+ }
130
+ }
131
+ }
132
+
133
+ // { $op: <number> }
134
+ function castNumberOperator(val) {
135
+ if (!isLiteral(val)) {
136
+ return val;
137
+ }
138
+
139
+ try {
140
+ return castNumber(val);
141
+ } catch (err) {
142
+ throw new CastError('Number', val);
143
+ }
144
+ }
145
+
146
+ function castIn(val, schema, strictQuery) {
147
+ let search = val[0];
148
+ let path = val[1];
149
+ if (!isPath(path)) {
150
+ return val;
151
+ }
152
+
153
+ path = path.slice(1);
154
+ const schematype = schema.path(path);
155
+ if (schematype == null) {
156
+ if (strictQuery === false) {
157
+ return val;
158
+ } else if (strictQuery === 'throw') {
159
+ throw new StrictModeError('$in');
160
+ }
161
+
162
+ return void 0;
163
+ }
164
+
165
+ if (!schematype.$isMongooseArray) {
166
+ throw new Error('Path must be an array for $in');
167
+ }
168
+
169
+ if (schematype.$isMongooseDocumentArray) {
170
+ search = schematype.$embeddedSchemaType.cast(search);
171
+ } else {
172
+ search = schematype.caster.cast(search);
173
+ }
174
+ return [search, val[1]];
175
+ }
176
+
177
+ // { $op: [<number>, <number>] }
178
+ function castArithmetic(val) {
179
+ if (!Array.isArray(val)) {
180
+ if (!isLiteral(val)) {
181
+ return val;
182
+ }
183
+ try {
184
+ return castNumber(val);
185
+ } catch (err) {
186
+ throw new CastError('Number', val);
187
+ }
188
+ }
189
+
190
+ return val.map(v => {
191
+ if (!isLiteral(v)) {
192
+ return v;
193
+ }
194
+ try {
195
+ return castNumber(v);
196
+ } catch (err) {
197
+ throw new CastError('Number', v);
198
+ }
199
+ });
200
+ }
201
+
202
+ // { $op: [expression, expression] }
203
+ function castComparison(val, schema, strictQuery) {
204
+ if (!Array.isArray(val) || val.length !== 2) {
205
+ throw new Error('Comparison operator must be an array of length 2');
206
+ }
207
+
208
+ val[0] = _castExpression(val[0], schema, strictQuery);
209
+ const lhs = val[0];
210
+
211
+ if (isLiteral(val[1])) {
212
+ let path = null;
213
+ let schematype = null;
214
+ let caster = null;
215
+ if (isPath(lhs)) {
216
+ path = lhs.slice(1);
217
+ schematype = schema.path(path);
218
+ } else if (typeof lhs === 'object' && lhs != null) {
219
+ for (const key of Object.keys(lhs)) {
220
+ if (dateOperators.has(key) && isPath(lhs[key])) {
221
+ path = lhs[key].slice(1) + '.' + key;
222
+ caster = castNumber;
223
+ } else if (arrayElementOperators.has(key) && isPath(lhs[key])) {
224
+ path = lhs[key].slice(1) + '.' + key;
225
+ schematype = schema.path(lhs[key].slice(1));
226
+ if (schematype != null) {
227
+ if (schematype.$isMongooseDocumentArray) {
228
+ schematype = schematype.$embeddedSchemaType;
229
+ } else if (schematype.$isMongooseArray) {
230
+ schematype = schematype.caster;
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+
237
+ const is$literal = typeof val[1] === 'object' && val[1] != null && val[1].$literal != null;
238
+ if (schematype != null) {
239
+ if (is$literal) {
240
+ val[1] = { $literal: schematype.cast(val[1].$literal) };
241
+ } else {
242
+ val[1] = schematype.cast(val[1]);
243
+ }
244
+ } else if (caster != null) {
245
+ if (is$literal) {
246
+ try {
247
+ val[1] = { $literal: caster(val[1].$literal) };
248
+ } catch (err) {
249
+ throw new CastError(caster.name.replace(/^cast/, ''), val[1], path + '.$literal');
250
+ }
251
+ } else {
252
+ try {
253
+ val[1] = caster(val[1]);
254
+ } catch (err) {
255
+ throw new CastError(caster.name.replace(/^cast/, ''), val[1], path);
256
+ }
257
+ }
258
+ } else if (path != null && strictQuery === true) {
259
+ return void 0;
260
+ } else if (path != null && strictQuery === 'throw') {
261
+ throw new StrictModeError(path);
262
+ }
263
+ } else {
264
+ val[1] = _castExpression(val[1]);
265
+ }
266
+
267
+ return val;
268
+ }
269
+
270
+ function isPath(val) {
271
+ return typeof val === 'string' && val.startsWith('$');
272
+ }
273
+
274
+ function isLiteral(val) {
275
+ if (typeof val === 'string' && val.startsWith('$')) {
276
+ return false;
277
+ }
278
+ if (typeof val === 'object' && val != null && Object.keys(val).find(key => key.startsWith('$'))) {
279
+ // The `$literal` expression can make an object a literal
280
+ // https://docs.mongodb.com/manual/reference/operator/aggregation/literal/#mongodb-expression-exp.-literal
281
+ return val.$literal != null;
282
+ }
283
+ return true;
284
+ }
@@ -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
  }
@@ -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
  }
@@ -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
  }
@@ -79,14 +79,23 @@ module.exports = function(filter, schema, castedDoc, options) {
79
79
  return;
80
80
  }
81
81
  const def = schemaType.getDefault(null, true);
82
- if (!isModified(modified, path) && typeof def !== 'undefined') {
83
- castedDoc = castedDoc || {};
84
- castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
85
- if (get(castedDoc, path) == null) {
86
- castedDoc.$setOnInsert[path] = def;
87
- }
88
- updatedValues[path] = def;
82
+ if (isModified(modified, path)) {
83
+ return;
84
+ }
85
+ if (typeof def === 'undefined') {
86
+ return;
87
+ }
88
+ if (schemaType.splitPath().includes('$*')) {
89
+ // Skip defaults underneath maps. We should never do `$setOnInsert` on a path with `$*`
90
+ return;
91
+ }
92
+
93
+ castedDoc = castedDoc || {};
94
+ castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
95
+ if (get(castedDoc, path) == null) {
96
+ castedDoc.$setOnInsert[path] = def;
89
97
  }
98
+ updatedValues[path] = def;
90
99
  });
91
100
 
92
101
  return castedDoc;
@@ -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
  }