@depup/mongoose 9.3.3-depup.0 → 9.8.0-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +3 -9
  2. package/changes.json +3 -8
  3. package/eslint.config.mjs +4 -1
  4. package/lib/aggregate.js +54 -24
  5. package/lib/cast.js +1 -1
  6. package/lib/connection.js +20 -8
  7. package/lib/cursor/aggregationCursor.js +47 -16
  8. package/lib/cursor/queryCursor.js +23 -7
  9. package/lib/document.js +240 -105
  10. package/lib/drivers/node-mongodb-native/collection.js +47 -14
  11. package/lib/drivers/node-mongodb-native/connection.js +13 -0
  12. package/lib/error/cast.js +18 -9
  13. package/lib/error/divergentArray.js +1 -1
  14. package/lib/error/index.js +1 -1
  15. package/lib/error/messages.js +1 -0
  16. package/lib/helpers/clone.js +22 -5
  17. package/lib/helpers/document/applyDefaults.js +5 -0
  18. package/lib/helpers/populate/createPopulateQueryFilter.js +9 -1
  19. package/lib/helpers/populate/getModelsMapForPopulate.js +8 -6
  20. package/lib/helpers/populate/splitPopulateQuery.js +81 -0
  21. package/lib/helpers/query/castUpdate.js +4 -0
  22. package/lib/helpers/schema/getKeysInSchemaOrder.js +13 -16
  23. package/lib/helpers/update/applyTimestampsToUpdate.js +4 -2
  24. package/lib/model.js +159 -41
  25. package/lib/mongoose.js +3 -3
  26. package/lib/options/schemaTypeOptions.js +14 -0
  27. package/lib/plugins/saveSubdocs.js +16 -13
  28. package/lib/query.js +208 -102
  29. package/lib/queryHelpers.js +13 -12
  30. package/lib/schema/array.js +4 -6
  31. package/lib/schema/bigint.js +1 -3
  32. package/lib/schema/boolean.js +1 -3
  33. package/lib/schema/buffer.js +1 -3
  34. package/lib/schema/date.js +8 -8
  35. package/lib/schema/decimal128.js +1 -3
  36. package/lib/schema/documentArray.js +3 -5
  37. package/lib/schema/double.js +1 -3
  38. package/lib/schema/int32.js +1 -3
  39. package/lib/schema/map.js +1 -4
  40. package/lib/schema/number.js +2 -4
  41. package/lib/schema/objectId.js +7 -3
  42. package/lib/schema/string.js +20 -5
  43. package/lib/schema/subdocument.js +2 -4
  44. package/lib/schema/union.js +50 -0
  45. package/lib/schema/uuid.js +1 -3
  46. package/lib/schema.js +40 -14
  47. package/lib/schemaType.js +93 -12
  48. package/lib/standardSchema/convertErrorToIssues.js +20 -0
  49. package/lib/stateMachine.js +11 -6
  50. package/lib/tracing.js +38 -0
  51. package/lib/types/array/methods/index.js +4 -4
  52. package/lib/types/documentArray/methods/index.js +117 -3
  53. package/lib/types/subdocument.js +4 -2
  54. package/lib/utils.js +12 -2
  55. package/lib/validOptions.js +1 -0
  56. package/package.json +30 -33
  57. package/{tstyche.config.json → tstyche.json} +2 -1
  58. package/types/augmentations.d.ts +2 -2
  59. package/types/document.d.ts +61 -7
  60. package/types/expressions.d.ts +16 -0
  61. package/types/index.d.ts +25 -7
  62. package/types/inferhydrateddoctype.d.ts +1 -1
  63. package/types/inferrawdoctype.d.ts +6 -3
  64. package/types/inferschematype.d.ts +10 -2
  65. package/types/models.d.ts +30 -13
  66. package/types/mongooseoptions.d.ts +9 -1
  67. package/types/populate.d.ts +52 -0
  68. package/types/query.d.ts +39 -12
  69. package/types/schemaoptions.d.ts +5 -0
  70. package/types/schematypes.d.ts +10 -0
  71. package/types/tracing.d.ts +10 -0
  72. package/types/utility.d.ts +11 -2
  73. package/lib/helpers/createJSONSchemaTypeDefinition.js +0 -24
@@ -84,28 +84,29 @@ exports.createModel = function createModel(model, doc, fields, userProvidedField
84
84
  discriminatorMapping.key :
85
85
  null;
86
86
 
87
+ const _opts = {
88
+ skipId: true,
89
+ isNew: false,
90
+ willInit: true
91
+ };
92
+ if (options?.defaults != null) {
93
+ _opts.defaults = options.defaults;
94
+ }
95
+ if (options?.strict != null) {
96
+ _opts.strict = options.strict;
97
+ }
98
+
87
99
  const value = doc[key];
88
100
  if (key && value && model.discriminators) {
89
101
  const discriminator = model.discriminators[value] || getDiscriminatorByValue(model.discriminators, value);
90
102
  if (discriminator) {
91
103
  const _fields = clone(userProvidedFields);
92
104
  exports.applyPaths(_fields, discriminator.schema);
93
- const _opts = { strict: options?.strict };
105
+
94
106
  return new discriminator(undefined, _fields, _opts);
95
107
  }
96
108
  }
97
109
 
98
- const _opts = {
99
- skipId: true,
100
- isNew: false,
101
- willInit: true
102
- };
103
- if (options != null && 'defaults' in options) {
104
- _opts.defaults = options.defaults;
105
- }
106
- if (options != null && 'strict' in options) {
107
- _opts.strict = options.strict;
108
- }
109
110
  return new model(undefined, fields, _opts);
110
111
  };
111
112
 
@@ -21,7 +21,6 @@ const isOperator = require('../helpers/query/isOperator');
21
21
  const util = require('util');
22
22
  const utils = require('../utils');
23
23
  const castToNumber = require('./operators/helpers').castToNumber;
24
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
25
24
  const geospatial = require('./operators/geospatial');
26
25
  const getDiscriminatorByValue = require('../helpers/discriminator/getDiscriminatorByValue');
27
26
 
@@ -511,9 +510,9 @@ SchemaArray.prototype._castForQuery = function(val, context) {
511
510
  }
512
511
 
513
512
  if (Array.isArray(val)) {
514
- this.setters.reverse().forEach(setter => {
515
- val = setter.call(this, val, this);
516
- });
513
+ for (let i = this.setters.length - 1; i >= 0; i--) {
514
+ val = this.setters[i].call(this, val, this);
515
+ }
517
516
  val = val.map(function(v) {
518
517
  if (utils.isObject(v) && v.$elemMatch) {
519
518
  return v;
@@ -684,9 +683,8 @@ handle.$in = SchemaType.prototype.$conditionalHandlers.$in;
684
683
 
685
684
  SchemaArray.prototype.toJSONSchema = function toJSONSchema(options) {
686
685
  const embeddedSchemaType = this.getEmbeddedSchemaType();
687
- const isRequired = this.options.required && typeof this.options.required !== 'function';
688
686
  return {
689
- ...createJSONSchemaTypeDefinition('array', 'array', options?.useBsonType, isRequired),
687
+ ...this._createJSONSchemaTypeDefinition('array', 'array', options),
690
688
  items: embeddedSchemaType.toJSONSchema(options)
691
689
  };
692
690
  };
@@ -7,7 +7,6 @@
7
7
  const CastError = require('../error/cast');
8
8
  const SchemaType = require('../schemaType');
9
9
  const castBigInt = require('../cast/bigint');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
 
12
11
  /**
13
12
  * BigInt SchemaType constructor.
@@ -267,8 +266,7 @@ SchemaBigInt.prototype._castNullish = function _castNullish(v) {
267
266
  */
268
267
 
269
268
  SchemaBigInt.prototype.toJSONSchema = function toJSONSchema(options) {
270
- const isRequired = this.options.required && typeof this.options.required !== 'function';
271
- return createJSONSchemaTypeDefinition('string', 'long', options?.useBsonType, isRequired);
269
+ return this._createJSONSchemaTypeDefinition('string', 'long', options);
272
270
  };
273
271
 
274
272
  SchemaBigInt.prototype.autoEncryptionType = function autoEncryptionType() {
@@ -7,7 +7,6 @@
7
7
  const CastError = require('../error/cast');
8
8
  const SchemaType = require('../schemaType');
9
9
  const castBoolean = require('../cast/boolean');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
 
12
11
  /**
13
12
  * Boolean SchemaType constructor.
@@ -317,8 +316,7 @@ SchemaBoolean.prototype._castNullish = function _castNullish(v) {
317
316
  */
318
317
 
319
318
  SchemaBoolean.prototype.toJSONSchema = function toJSONSchema(options) {
320
- const isRequired = this.options.required && typeof this.options.required !== 'function';
321
- return createJSONSchemaTypeDefinition('boolean', 'bool', options?.useBsonType, isRequired);
319
+ return this._createJSONSchemaTypeDefinition('boolean', 'bool', options);
322
320
  };
323
321
 
324
322
  SchemaBoolean.prototype.autoEncryptionType = function autoEncryptionType() {
@@ -7,7 +7,6 @@
7
7
  const MongooseBuffer = require('../types/buffer');
8
8
  const SchemaBufferOptions = require('../options/schemaBufferOptions');
9
9
  const SchemaType = require('../schemaType');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
  const handleBitwiseOperator = require('./operators/bitwise');
12
11
  const utils = require('../utils');
13
12
 
@@ -328,8 +327,7 @@ SchemaBuffer.prototype.castForQuery = function($conditional, val, context) {
328
327
  */
329
328
 
330
329
  SchemaBuffer.prototype.toJSONSchema = function toJSONSchema(options) {
331
- const isRequired = this.options.required && typeof this.options.required !== 'function';
332
- return createJSONSchemaTypeDefinition('string', 'binData', options?.useBsonType, isRequired);
330
+ return this._createJSONSchemaTypeDefinition('string', 'binData', options);
333
331
  };
334
332
 
335
333
  SchemaBuffer.prototype.autoEncryptionType = function autoEncryptionType() {
@@ -8,7 +8,6 @@ const MongooseError = require('../error/index');
8
8
  const SchemaDateOptions = require('../options/schemaDateOptions');
9
9
  const SchemaType = require('../schemaType');
10
10
  const castDate = require('../cast/date');
11
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
12
11
  const getConstructorName = require('../helpers/getConstructorName');
13
12
  const utils = require('../utils');
14
13
 
@@ -189,16 +188,18 @@ SchemaDate.prototype.expires = function(when) {
189
188
  SchemaDate._checkRequired = v => v instanceof Date;
190
189
 
191
190
  /**
192
- * Override the function the required validator uses to check whether a string
191
+ * Override the function the required validator uses to check whether a date
193
192
  * passes the `required` check.
194
193
  *
195
194
  * #### Example:
196
195
  *
197
- * // Allow empty strings to pass `required` check
198
- * mongoose.Schema.Types.String.checkRequired(v => v != null);
196
+ * // Disallow "invalid date"
197
+ * mongoose.Schema.Types.Date.checkRequired(v => v instanceof Date && !isNaN(v.valueOf()));
199
198
  *
200
- * const M = mongoose.model({ str: { type: String, required: true } });
201
- * new M({ str: '' }).validateSync(); // `null`, validation passes!
199
+ * const schema = new mongoose.Schema({ myDate: { type: Date, required: true } });
200
+ * const M = mongoose.model('Test', schema);
201
+ * // returns a ValidationError due to date being invalid
202
+ * new M({ myDate: new Date('invalid') }).validateSync();
202
203
  *
203
204
  * @param {Function} fn
204
205
  * @return {Function}
@@ -452,8 +453,7 @@ SchemaDate.prototype.castForQuery = function($conditional, val, context) {
452
453
  */
453
454
 
454
455
  SchemaDate.prototype.toJSONSchema = function toJSONSchema(options) {
455
- const isRequired = this.options.required && typeof this.options.required !== 'function';
456
- return createJSONSchemaTypeDefinition('string', 'date', options?.useBsonType, isRequired);
456
+ return this._createJSONSchemaTypeDefinition('string', 'date', options);
457
457
  };
458
458
 
459
459
  SchemaDate.prototype.autoEncryptionType = function autoEncryptionType() {
@@ -7,7 +7,6 @@
7
7
  const SchemaType = require('../schemaType');
8
8
  const CastError = SchemaType.CastError;
9
9
  const castDecimal128 = require('../cast/decimal128');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
  const isBsonType = require('../helpers/isBsonType');
12
11
 
13
12
  /**
@@ -248,8 +247,7 @@ Object.defineProperty(SchemaDecimal128.prototype, '$conditionalHandlers', {
248
247
  */
249
248
 
250
249
  SchemaDecimal128.prototype.toJSONSchema = function toJSONSchema(options) {
251
- const isRequired = this.options.required && typeof this.options.required !== 'function';
252
- return createJSONSchemaTypeDefinition('string', 'decimal', options?.useBsonType, isRequired);
250
+ return this._createJSONSchemaTypeDefinition('string', 'decimal', options);
253
251
  };
254
252
 
255
253
  SchemaDecimal128.prototype.autoEncryptionType = function autoEncryptionType() {
@@ -12,7 +12,6 @@ const SchemaDocumentArrayOptions =
12
12
  require('../options/schemaDocumentArrayOptions');
13
13
  const SchemaType = require('../schemaType');
14
14
  const cast = require('../cast');
15
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
16
15
  const discriminator = require('../helpers/model/discriminator');
17
16
  const handleIdOption = require('../helpers/schema/handleIdOption');
18
17
  const handleSpreadDoc = require('../helpers/document/handleSpreadDoc');
@@ -315,7 +314,7 @@ SchemaDocumentArray.prototype.doValidateSync = function(array, scope, options) {
315
314
  continue;
316
315
  }
317
316
 
318
- const subdocValidateError = doc.validateSync(options);
317
+ const subdocValidateError = doc.$__validateSync(options);
319
318
 
320
319
  if (subdocValidateError && resultError == null) {
321
320
  resultError = subdocValidateError;
@@ -651,10 +650,9 @@ function cast$elemMatch(val, context) {
651
650
  */
652
651
 
653
652
  SchemaDocumentArray.prototype.toJSONSchema = function toJSONSchema(options) {
654
- const itemsTypeDefinition = createJSONSchemaTypeDefinition('object', 'object', options?.useBsonType, false);
655
- const isRequired = this.options.required && typeof this.options.required !== 'function';
653
+ const itemsTypeDefinition = this._createJSONSchemaTypeDefinition('object', 'object', { ...options, _overrideRequired: false });
656
654
  return {
657
- ...createJSONSchemaTypeDefinition('array', 'array', options?.useBsonType, isRequired),
655
+ ...this._createJSONSchemaTypeDefinition('array', 'array', options),
658
656
  items: { ...itemsTypeDefinition, ...this.schema.toJSONSchema(options) }
659
657
  };
660
658
  };
@@ -7,7 +7,6 @@
7
7
  const CastError = require('../error/cast');
8
8
  const SchemaType = require('../schemaType');
9
9
  const castDouble = require('../cast/double');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
 
12
11
  /**
13
12
  * Double SchemaType constructor.
@@ -231,8 +230,7 @@ Object.defineProperty(SchemaDouble.prototype, '$conditionalHandlers', {
231
230
  */
232
231
 
233
232
  SchemaDouble.prototype.toJSONSchema = function toJSONSchema(options) {
234
- const isRequired = this.options.required && typeof this.options.required !== 'function';
235
- return createJSONSchemaTypeDefinition('number', 'double', options?.useBsonType, isRequired);
233
+ return this._createJSONSchemaTypeDefinition('number', 'double', options);
236
234
  };
237
235
 
238
236
  SchemaDouble.prototype.autoEncryptionType = function autoEncryptionType() {
@@ -7,7 +7,6 @@
7
7
  const CastError = require('../error/cast');
8
8
  const SchemaType = require('../schemaType');
9
9
  const castInt32 = require('../cast/int32');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
  const handleBitwiseOperator = require('./operators/bitwise');
12
11
 
13
12
  /**
@@ -273,8 +272,7 @@ SchemaInt32.prototype.castForQuery = function($conditional, val, context) {
273
272
  */
274
273
 
275
274
  SchemaInt32.prototype.toJSONSchema = function toJSONSchema(options) {
276
- const isRequired = this.options.required && typeof this.options.required !== 'function';
277
- return createJSONSchemaTypeDefinition('number', 'int', options?.useBsonType, isRequired);
275
+ return this._createJSONSchemaTypeDefinition('number', 'int', options);
278
276
  };
279
277
 
280
278
  SchemaInt32.prototype.autoEncryptionType = function autoEncryptionType() {
package/lib/schema/map.js CHANGED
@@ -7,7 +7,6 @@
7
7
  const MongooseMap = require('../types/map');
8
8
  const SchemaMapOptions = require('../options/schemaMapOptions');
9
9
  const SchemaType = require('../schemaType');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
  const MongooseError = require('../error/mongooseError');
12
11
  const Schema = require('../schema');
13
12
  const utils = require('../utils');
@@ -125,11 +124,9 @@ class SchemaMap extends SchemaType {
125
124
  */
126
125
 
127
126
  toJSONSchema(options) {
128
- const useBsonType = options?.useBsonType;
129
127
  const embeddedSchemaType = this.getEmbeddedSchemaType();
130
128
 
131
- const isRequired = this.options.required && typeof this.options.required !== 'function';
132
- const result = createJSONSchemaTypeDefinition('object', 'object', useBsonType, isRequired);
129
+ const result = this._createJSONSchemaTypeDefinition('object', 'object', options);
133
130
  result.additionalProperties = embeddedSchemaType.toJSONSchema(options);
134
131
 
135
132
  return result;
@@ -8,7 +8,6 @@ const MongooseError = require('../error/index');
8
8
  const SchemaNumberOptions = require('../options/schemaNumberOptions');
9
9
  const SchemaType = require('../schemaType');
10
10
  const castNumber = require('../cast/number');
11
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
12
11
  const handleBitwiseOperator = require('./operators/bitwise');
13
12
  const utils = require('../utils');
14
13
 
@@ -473,7 +472,7 @@ SchemaNumber.prototype.castForQuery = function($conditional, val, context) {
473
472
  if ($conditional != null) {
474
473
  handler = this.$conditionalHandlers[$conditional];
475
474
  if (!handler) {
476
- throw new CastError('number', val, this.path, null, this);
475
+ throw new MongooseError('Can\'t use ' + $conditional + ' with Number.');
477
476
  }
478
477
  return handler.call(this, val, context);
479
478
  }
@@ -499,8 +498,7 @@ SchemaNumber.prototype.castForQuery = function($conditional, val, context) {
499
498
  */
500
499
 
501
500
  SchemaNumber.prototype.toJSONSchema = function toJSONSchema(options) {
502
- const isRequired = (this.options.required && typeof this.options.required !== 'function') || this.path === '_id';
503
- return createJSONSchemaTypeDefinition('number', 'number', options?.useBsonType, isRequired);
501
+ return this._createJSONSchemaTypeDefinition('number', 'number', options);
504
502
  };
505
503
 
506
504
  /*!
@@ -7,7 +7,6 @@
7
7
  const SchemaObjectIdOptions = require('../options/schemaObjectIdOptions');
8
8
  const SchemaType = require('../schemaType');
9
9
  const castObjectId = require('../cast/objectid');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
  const getConstructorName = require('../helpers/getConstructorName');
12
11
  const oid = require('../types/objectid');
13
12
  const isBsonType = require('../helpers/isBsonType');
@@ -318,8 +317,13 @@ function resetId(v) {
318
317
  */
319
318
 
320
319
  SchemaObjectId.prototype.toJSONSchema = function toJSONSchema(options) {
321
- const isRequired = (this.options.required && typeof this.options.required !== 'function') || this.path === '_id';
322
- return createJSONSchemaTypeDefinition('string', 'objectId', options?.useBsonType, isRequired);
320
+ const jsonSchema = this._createJSONSchemaTypeDefinition('string', 'objectId', options);
321
+ // `bsonType: 'objectId'` already validates ObjectIds, so the regex is only useful
322
+ // for the portable `type: 'string'` representation (e.g. for ajv, zod, etc.)
323
+ if (!options?.useBsonType) {
324
+ jsonSchema.pattern = '^[A-Fa-f0-9]{24}$';
325
+ }
326
+ return jsonSchema;
323
327
  };
324
328
 
325
329
  SchemaObjectId.prototype.autoEncryptionType = function autoEncryptionType() {
@@ -8,7 +8,6 @@ const SchemaType = require('../schemaType');
8
8
  const MongooseError = require('../error/index');
9
9
  const SchemaStringOptions = require('../options/schemaStringOptions');
10
10
  const castString = require('../cast/string');
11
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
12
11
  const utils = require('../utils');
13
12
  const isBsonType = require('../helpers/isBsonType');
14
13
 
@@ -474,12 +473,14 @@ SchemaString.prototype.maxlength = function(value, message) {
474
473
 
475
474
  if (value != null) {
476
475
  let msg = message || MongooseError.messages.String.maxlength;
477
- msg = msg.replace(/{MAXLENGTH}/, value);
476
+ if (typeof msg !== 'function') {
477
+ msg = msg.replace(/{MAXLENGTH}/, value);
478
+ }
478
479
  this.validators.push({
479
480
  validator: this.maxlengthValidator = function(v) {
480
481
  return v === null || v.length <= value;
481
482
  },
482
- message: msg,
483
+ message: formatMaxLengthValidatorMessage(msg),
483
484
  type: 'maxlength',
484
485
  maxlength: value
485
486
  });
@@ -718,8 +719,7 @@ SchemaString.prototype.castForQuery = function($conditional, val, context) {
718
719
  */
719
720
 
720
721
  SchemaString.prototype.toJSONSchema = function toJSONSchema(options) {
721
- const isRequired = this.options.required && typeof this.options.required !== 'function';
722
- return createJSONSchemaTypeDefinition('string', 'string', options?.useBsonType, isRequired);
722
+ return this._createJSONSchemaTypeDefinition('string', 'string', options);
723
723
  };
724
724
 
725
725
  SchemaString.prototype.autoEncryptionType = function autoEncryptionType() {
@@ -731,3 +731,18 @@ SchemaString.prototype.autoEncryptionType = function autoEncryptionType() {
731
731
  */
732
732
 
733
733
  module.exports = SchemaString;
734
+
735
+ function formatMaxLengthValidatorMessage(msg) {
736
+ return function(props, doc) {
737
+ if (typeof msg === 'function') {
738
+ return MongooseError.ValidatorError.prototype.formatMessage(msg, props, doc);
739
+ }
740
+
741
+ if (typeof msg === 'string' && typeof props.value === 'string' && props.value.length > 30) {
742
+ props = Object.assign({}, props, {
743
+ value: props.value.slice(0, 30) + '...'
744
+ });
745
+ }
746
+ return MongooseError.ValidatorError.prototype.formatMessage(msg, props, doc);
747
+ };
748
+ }
@@ -12,7 +12,6 @@ const SchemaType = require('../schemaType');
12
12
  const applyDefaults = require('../helpers/document/applyDefaults');
13
13
  const $exists = require('./operators/exists');
14
14
  const castToNumber = require('./operators/helpers').castToNumber;
15
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
16
15
  const discriminator = require('../helpers/model/discriminator');
17
16
  const geospatial = require('./operators/geospatial');
18
17
  const getConstructor = require('../helpers/discriminator/getConstructor');
@@ -311,7 +310,7 @@ SchemaSubdocument.prototype.doValidateSync = function(value, scope, options) {
311
310
  if (!value) {
312
311
  return;
313
312
  }
314
- return value.validateSync();
313
+ return value.$__validateSync();
315
314
  };
316
315
 
317
316
  /**
@@ -428,9 +427,8 @@ SchemaSubdocument.prototype.clone = function() {
428
427
  */
429
428
 
430
429
  SchemaSubdocument.prototype.toJSONSchema = function toJSONSchema(options) {
431
- const isRequired = this.options.required && typeof this.options.required !== 'function';
432
430
  return {
433
431
  ...this.schema.toJSONSchema(options),
434
- ...createJSONSchemaTypeDefinition('object', 'object', options?.useBsonType, isRequired)
432
+ ...this._createJSONSchemaTypeDefinition('object', 'object', options)
435
433
  };
436
434
  };
@@ -28,6 +28,7 @@ class Union extends SchemaType {
28
28
  throw new Error('Union schema type requires an array of types');
29
29
  }
30
30
  this.schemaTypes = options.of.map(obj => parentSchema.interpretAsType(key, obj, schemaOptions));
31
+ this.$isSchemaUnion = true;
31
32
  }
32
33
 
33
34
  cast(val, doc, init, prev, options) {
@@ -90,12 +91,61 @@ class Union extends SchemaType {
90
91
  throw lastError;
91
92
  }
92
93
 
94
+ async doValidate(value, scope, options) {
95
+ if (options && options.skipSchemaValidators) {
96
+ if (value != null && typeof value.validate === 'function') {
97
+ return value.validate();
98
+ }
99
+ return;
100
+ }
101
+
102
+ await super.doValidate(value, scope, options);
103
+ if (value != null && typeof value.validate === 'function') {
104
+ await value.validate();
105
+ }
106
+ }
107
+
108
+ doValidateSync(value, scope, options) {
109
+ if (!options || !options.skipSchemaValidators) {
110
+ const schemaTypeError = super.doValidateSync(value, scope, options);
111
+ if (schemaTypeError) {
112
+ return schemaTypeError;
113
+ }
114
+ }
115
+ if (value != null && typeof value.$__validateSync === 'function') {
116
+ return value.$__validateSync();
117
+ }
118
+ }
119
+
93
120
  clone() {
94
121
  const schematype = super.clone();
95
122
 
96
123
  schematype.schemaTypes = this.schemaTypes.map(schemaType => schemaType.clone());
97
124
  return schematype;
98
125
  }
126
+
127
+ /**
128
+ * Returns this schema type's representation in a JSON schema.
129
+ *
130
+ * @param {object} [options]
131
+ * @param {boolean} [options.useBsonType=false] If true, return a representation with `bsonType` for use with MongoDB's `$jsonSchema`.
132
+ * @returns {object} JSON schema properties
133
+ */
134
+ toJSONSchema(options) {
135
+ const isRequired = this.options.required && typeof this.options.required !== 'function';
136
+ const childOptions = { ...options, _overrideRequired: true };
137
+ const jsonSchemas = this.schemaTypes.map(schemaType => schemaType.toJSONSchema(childOptions));
138
+ if (isRequired) {
139
+ return { anyOf: jsonSchemas };
140
+ }
141
+
142
+ return {
143
+ anyOf: [
144
+ options?.useBsonType ? { bsonType: 'null' } : { type: 'null' },
145
+ ...jsonSchemas
146
+ ]
147
+ };
148
+ }
99
149
  }
100
150
 
101
151
  /**
@@ -7,7 +7,6 @@
7
7
  const SchemaType = require('../schemaType');
8
8
  const CastError = SchemaType.CastError;
9
9
  const castUUID = require('../cast/uuid');
10
- const createJSONSchemaTypeDefinition = require('../helpers/createJSONSchemaTypeDefinition');
11
10
  const utils = require('../utils');
12
11
  const handleBitwiseOperator = require('./operators/bitwise');
13
12
 
@@ -290,8 +289,7 @@ SchemaUUID.prototype.castForQuery = function($conditional, val, context) {
290
289
  */
291
290
 
292
291
  SchemaUUID.prototype.toJSONSchema = function toJSONSchema(options) {
293
- const isRequired = this.options.required && typeof this.options.required !== 'function';
294
- return createJSONSchemaTypeDefinition('string', 'binData', options?.useBsonType, isRequired);
292
+ return this._createJSONSchemaTypeDefinition('string', 'binData', options);
295
293
  };
296
294
 
297
295
  SchemaUUID.prototype.autoEncryptionType = function autoEncryptionType() {
package/lib/schema.js CHANGED
@@ -59,7 +59,7 @@ const numberRE = /^\d+$/;
59
59
  * - [autoIndex](https://mongoosejs.com/docs/guide.html#autoIndex): bool - defaults to null (which means use the connection's autoIndex option)
60
60
  * - [autoCreate](https://mongoosejs.com/docs/guide.html#autoCreate): bool - defaults to null (which means use the connection's autoCreate option)
61
61
  * - [bufferCommands](https://mongoosejs.com/docs/guide.html#bufferCommands): bool - defaults to true
62
- * - [bufferTimeoutMS](https://mongoosejs.com/docs/guide.html#bufferTimeoutMS): number - defaults to 10000 (10 seconds). If `bufferCommands` is enabled, the amount of time Mongoose will wait for connectivity to be restablished before erroring out.
62
+ * - [bufferTimeoutMS](https://mongoosejs.com/docs/guide.html#bufferTimeoutMS): number - defaults to 10000 (10 seconds). If `bufferCommands` is enabled, the amount of time Mongoose will wait for connectivity to be established before erroring out.
63
63
  * - [capped](https://mongoosejs.com/docs/guide.html#capped): bool | number | object - defaults to false
64
64
  * - [collection](https://mongoosejs.com/docs/guide.html#collection): string - no default
65
65
  * - [discriminatorKey](https://mongoosejs.com/docs/guide.html#discriminatorKey): string - defaults to `__t`
@@ -72,6 +72,7 @@ const numberRE = /^\d+$/;
72
72
  * - [shardKey](https://mongoosejs.com/docs/guide.html#shardKey): object - defaults to `null`
73
73
  * - [strict](https://mongoosejs.com/docs/guide.html#strict): bool - defaults to true
74
74
  * - [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery): bool - defaults to false
75
+ * - [strictRead](https://mongoosejs.com/docs/guide.html#strictRead): bool or 'throw' - defaults to false. If set to `true`, fields not in the schema will be stripped when hydrating documents from MongoDB. If set to `'throw'`, an error will be thrown when a field not in the schema is encountered during document hydration.
75
76
  * - [toJSON](https://mongoosejs.com/docs/guide.html#toJSON) - object - no default
76
77
  * - [toObject](https://mongoosejs.com/docs/guide.html#toObject) - object - no default
77
78
  * - [typeKey](https://mongoosejs.com/docs/guide.html#typeKey) - string - defaults to 'type'
@@ -606,10 +607,12 @@ Schema.prototype.defaultOptions = function(options) {
606
607
  const baseOptions = this.base?.options || {};
607
608
  const defaultStrict = baseOptions.strict ?? true;
608
609
  const defaultStrictQuery = baseOptions.strictQuery ?? false;
610
+ const defaultStrictRead = baseOptions.strictRead ?? false;
609
611
  const defaultId = baseOptions.id ?? true;
610
612
  options = {
611
613
  strict: defaultStrict,
612
614
  strictQuery: defaultStrictQuery,
615
+ strictRead: defaultStrictRead,
613
616
  bufferCommands: true,
614
617
  capped: false, // { size, max, autoIndexId }
615
618
  versionKey: '__v',
@@ -750,7 +753,7 @@ Schema.prototype.encryptionType = function encryptionType(encryptionType) {
750
753
  return this.options.encryptionType;
751
754
  }
752
755
  if (!(typeof encryptionType === 'string' || encryptionType === null)) {
753
- throw new MongooseError('invalid `encryptionType`: ${encryptionType}');
756
+ throw new MongooseError(`invalid \`encryptionType\`: ${encryptionType}`);
754
757
  }
755
758
  this.options.encryptionType = encryptionType;
756
759
  };
@@ -1268,7 +1271,7 @@ reserved.collection = 1;
1268
1271
 
1269
1272
  Schema.prototype.path = function(path, obj) {
1270
1273
  if (obj === undefined) {
1271
- if (this.paths[path] != null) {
1274
+ if (Object.hasOwn(this.paths, path)) {
1272
1275
  return this.paths[path];
1273
1276
  }
1274
1277
  // Convert to '.$' to check subpaths re: gh-6405
@@ -1296,9 +1299,15 @@ Schema.prototype.path = function(path, obj) {
1296
1299
  : undefined;
1297
1300
  }
1298
1301
 
1302
+ const subpaths = path.indexOf('.') === -1 ? [path] : path.split('.');
1303
+ const last = subpaths.pop();
1304
+ if (utils.specialProperties.has(last)) {
1305
+ throw new MongooseError('Cannot set special property `' + last + '` on a schema');
1306
+ }
1307
+
1299
1308
  // some path names conflict with document methods
1300
- const firstPieceOfPath = path.split('.')[0];
1301
- if (reserved[firstPieceOfPath] && !this.options.suppressReservedKeysWarning) {
1309
+ const firstPieceOfPath = subpaths.length === 0 ? last : subpaths[0];
1310
+ if (Object.hasOwn(reserved, firstPieceOfPath) && !this.options.suppressReservedKeysWarning) {
1302
1311
  const errorMessage = `\`${firstPieceOfPath}\` is a reserved schema pathname and may break some functionality. ` +
1303
1312
  'You are allowed to use it, but use at your own risk. ' +
1304
1313
  'To disable this warning pass `suppressReservedKeysWarning` as a schema option.';
@@ -1311,8 +1320,6 @@ Schema.prototype.path = function(path, obj) {
1311
1320
  }
1312
1321
 
1313
1322
  // update the tree
1314
- const subpaths = path.split(/\./);
1315
- const last = subpaths.pop();
1316
1323
  let branch = this.tree;
1317
1324
  let fullPath = '';
1318
1325
 
@@ -1536,7 +1543,7 @@ function getMapPath(schema, path) {
1536
1543
  } else if (val.schema && path.startsWith(cleanPath + '.')) {
1537
1544
  let remnant = path.slice(cleanPath.length + 1);
1538
1545
  remnant = remnant.slice(remnant.indexOf('.') + 1);
1539
- return val.schema.paths[remnant];
1546
+ return val.schema.path(remnant);
1540
1547
  } else if (val.$isSchemaMap && path.startsWith(cleanPath + '.')) {
1541
1548
  let remnant = path.slice(cleanPath.length + 1);
1542
1549
  remnant = remnant.slice(remnant.indexOf('.') + 1);
@@ -3026,7 +3033,7 @@ Schema.prototype._getPathType = function(path) {
3026
3033
  };
3027
3034
  }
3028
3035
  return { schema: foundschema, pathType: 'real' };
3029
- } else if (p === parts.length && schema.nested[trypath]) {
3036
+ } else if (p === parts.length && Object.hasOwn(schema.nested, trypath)) {
3030
3037
  return { schema: schema, pathType: 'nested' };
3031
3038
  }
3032
3039
  }
@@ -3089,8 +3096,27 @@ function isArrayFilter(piece) {
3089
3096
 
3090
3097
  Schema.prototype._preCompile = function _preCompile() {
3091
3098
  this.plugin(idGetter, { deduplicate: true });
3099
+ _precomputeOptimisticConcurrency(this);
3092
3100
  };
3093
3101
 
3102
+ /*!
3103
+ * Build precomputed sets for optimisticConcurrency include/exclude,
3104
+ * expanding user-specified paths to include all schema subpaths so that
3105
+ * lookups at save time are a simple `Set.has()`.
3106
+ */
3107
+
3108
+ function _precomputeOptimisticConcurrency(schema) {
3109
+ const opt = schema.options.optimisticConcurrency;
3110
+ if (!opt || opt === true) {
3111
+ return;
3112
+ }
3113
+ if (Array.isArray(opt)) {
3114
+ schema.options._optimisticConcurrencySet = new Set(opt);
3115
+ } else if (Array.isArray(opt.exclude)) {
3116
+ schema.options._optimisticConcurrencyExcludeSet = new Set(opt.exclude);
3117
+ }
3118
+ }
3119
+
3094
3120
  /**
3095
3121
  * Returns a JSON schema representation of this Schema.
3096
3122
  *
@@ -3102,12 +3128,12 @@ Schema.prototype._preCompile = function _preCompile() {
3102
3128
  * - `enum` for strings and numbers
3103
3129
  *
3104
3130
  * #### Example:
3105
- * const schema = new Schema({ name: String });
3106
- * // { required: ['_id'], properties: { name: { type: ['string', 'null'] }, _id: { type: 'string' } } }
3107
- * schema.toJSONSchema();
3131
+ * const schema = new Schema({ name: String });
3132
+ * // { required: ['_id'], properties: { name: { type: ['string', 'null'] }, _id: { type: 'string' } } }
3133
+ * schema.toJSONSchema();
3108
3134
  *
3109
- * // { required: ['_id'], properties: { name: { bsonType: ['string', 'null'] }, _id: { bsonType: 'objectId' } } }
3110
- * schema.toJSONSchema({ useBsonType: true });
3135
+ * // { required: ['_id'], properties: { name: { bsonType: ['string', 'null'] }, _id: { bsonType: 'objectId' } } }
3136
+ * schema.toJSONSchema({ useBsonType: true });
3111
3137
  *
3112
3138
  * @param {object} [options]
3113
3139
  * @param {boolean} [options.useBsonType=false] if true, specify each path's type using `bsonType` rather than `type` for MongoDB $jsonSchema support