mongoose 8.14.2 → 8.15.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.
@@ -12,6 +12,7 @@ const pkg = require('../../../package.json');
12
12
  const processConnectionOptions = require('../../helpers/processConnectionOptions');
13
13
  const setTimeout = require('../../helpers/timers').setTimeout;
14
14
  const utils = require('../../utils');
15
+ const Schema = require('../../schema');
15
16
 
16
17
  /**
17
18
  * A [node-mongodb-native](https://github.com/mongodb/node-mongodb-native) connection implementation.
@@ -320,6 +321,20 @@ NativeConnection.prototype.createClient = async function createClient(uri, optio
320
321
  };
321
322
  }
322
323
 
324
+ const { schemaMap, encryptedFieldsMap } = this._buildEncryptionSchemas();
325
+
326
+ if ((Object.keys(schemaMap).length > 0 || Object.keys(encryptedFieldsMap).length) && !options.autoEncryption) {
327
+ throw new Error('Must provide `autoEncryption` when connecting with encrypted schemas.');
328
+ }
329
+
330
+ if (Object.keys(schemaMap).length > 0) {
331
+ options.autoEncryption.schemaMap = schemaMap;
332
+ }
333
+
334
+ if (Object.keys(encryptedFieldsMap).length > 0) {
335
+ options.autoEncryption.encryptedFieldsMap = encryptedFieldsMap;
336
+ }
337
+
323
338
  this.readyState = STATES.connecting;
324
339
  this._connectionString = uri;
325
340
 
@@ -343,6 +358,56 @@ NativeConnection.prototype.createClient = async function createClient(uri, optio
343
358
  return this;
344
359
  };
345
360
 
361
+ /**
362
+ * Given a connection, which may or may not have encrypted models, build
363
+ * a schemaMap and/or an encryptedFieldsMap for the connection, combining all models
364
+ * into a single schemaMap and encryptedFields map.
365
+ *
366
+ * @returns the generated schemaMap and encryptedFieldsMap
367
+ */
368
+ NativeConnection.prototype._buildEncryptionSchemas = function() {
369
+ const qeMappings = {};
370
+ const csfleMappings = {};
371
+
372
+ const encryptedModels = Object.values(this.models).filter(model => model.schema._hasEncryptedFields());
373
+
374
+ // If discriminators are configured for the collection, there might be multiple models
375
+ // pointing to the same namespace. For this scenario, we merge all the schemas for each namespace
376
+ // into a single schema and then generate a schemaMap/encryptedFieldsMap for the combined schema.
377
+ for (const model of encryptedModels) {
378
+ const { schema, collection: { collectionName } } = model;
379
+ const namespace = `${this.$dbName}.${collectionName}`;
380
+ const mappings = schema.encryptionType() === 'csfle' ? csfleMappings : qeMappings;
381
+
382
+ mappings[namespace] ??= new Schema({}, { encryptionType: schema.encryptionType() });
383
+
384
+ const isNonRootDiscriminator = schema.discriminatorMapping && !schema.discriminatorMapping.isRoot;
385
+ if (isNonRootDiscriminator) {
386
+ const rootSchema = schema._baseSchema;
387
+ schema.eachPath((pathname) => {
388
+ if (rootSchema.path(pathname)) return;
389
+ if (!mappings[namespace]._hasEncryptedField(pathname)) return;
390
+
391
+ throw new Error(`Cannot have duplicate keys in discriminators with encryption. key=${pathname}`);
392
+ });
393
+ }
394
+
395
+ mappings[namespace].add(schema);
396
+ }
397
+
398
+ const schemaMap = Object.fromEntries(Object.entries(csfleMappings).map(
399
+ ([namespace, schema]) => ([namespace, schema._buildSchemaMap()])
400
+ ));
401
+
402
+ const encryptedFieldsMap = Object.fromEntries(Object.entries(qeMappings).map(
403
+ ([namespace, schema]) => ([namespace, schema._buildEncryptedFields()])
404
+ ));
405
+
406
+ return {
407
+ schemaMap, encryptedFieldsMap
408
+ };
409
+ };
410
+
346
411
  /*!
347
412
  * ignore
348
413
  */
@@ -7,3 +7,4 @@
7
7
  exports.BulkWriteResult = require('./bulkWriteResult');
8
8
  exports.Collection = require('./collection');
9
9
  exports.Connection = require('./connection');
10
+ exports.ClientEncryption = require('mongodb').ClientEncryption;
@@ -49,6 +49,7 @@ class MongooseServerSelectionError extends MongooseError {
49
49
  this[key] = err[key];
50
50
  }
51
51
  }
52
+ this.cause = reason;
52
53
 
53
54
  return this;
54
55
  }
@@ -17,6 +17,51 @@ const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
17
17
  methods: true
18
18
  };
19
19
 
20
+ /**
21
+ * Validate fields declared on the child schema when either schema is configured for encryption. Specifically, this function ensures that:
22
+ *
23
+ * - any encrypted fields are declared on exactly one of the schemas (not both)
24
+ * - encrypted fields cannot be declared on either the parent or child schema, where the other schema declares the same field without encryption.
25
+ *
26
+ * @param {Schema} parentSchema
27
+ * @param {Schema} childSchema
28
+ */
29
+ function validateDiscriminatorSchemasForEncryption(parentSchema, childSchema) {
30
+ if (parentSchema.encryptionType() == null && childSchema.encryptionType() == null) return;
31
+
32
+ const allSharedNestedPaths = setIntersection(
33
+ allNestedPaths(parentSchema),
34
+ allNestedPaths(childSchema)
35
+ );
36
+
37
+ for (const path of allSharedNestedPaths) {
38
+ if (parentSchema._hasEncryptedField(path) && childSchema._hasEncryptedField(path)) {
39
+ throw new Error(`encrypted fields cannot be declared on both the base schema and the child schema in a discriminator. path=${path}`);
40
+ }
41
+
42
+ if (parentSchema._hasEncryptedField(path) || childSchema._hasEncryptedField(path)) {
43
+ throw new Error(`encrypted fields cannot have the same path as a non-encrypted field for discriminators. path=${path}`);
44
+ }
45
+ }
46
+
47
+ function allNestedPaths(schema) {
48
+ return [...Object.keys(schema.paths), ...Object.keys(schema.singleNestedPaths)];
49
+ }
50
+
51
+ /**
52
+ * @param {Iterable<string>} i1
53
+ * @param {Iterable<string>} i2
54
+ */
55
+ function* setIntersection(i1, i2) {
56
+ const s1 = new Set(i1);
57
+ for (const item of i2) {
58
+ if (s1.has(item)) {
59
+ yield item;
60
+ }
61
+ }
62
+ }
63
+ }
64
+
20
65
  /*!
21
66
  * ignore
22
67
  */
@@ -80,6 +125,8 @@ module.exports = function discriminator(model, name, schema, tiedValue, applyPlu
80
125
  value = tiedValue;
81
126
  }
82
127
 
128
+ validateDiscriminatorSchemasForEncryption(model.schema, schema);
129
+
83
130
  function merge(schema, baseSchema) {
84
131
  // Retain original schema before merging base schema
85
132
  schema._baseSchema = baseSchema;
@@ -69,8 +69,11 @@ const dateOperators = new Set([
69
69
  const expressionOperator = new Set(['$not']);
70
70
 
71
71
  module.exports = function cast$expr(val, schema, strictQuery) {
72
+ if (typeof val === 'boolean') {
73
+ return val;
74
+ }
72
75
  if (typeof val !== 'object' || val === null) {
73
- throw new Error('`$expr` must be an object');
76
+ throw new Error('`$expr` must be an object or boolean literal');
74
77
  }
75
78
 
76
79
  return _castExpression(val, schema, strictQuery);
package/lib/model.js CHANGED
@@ -69,7 +69,6 @@ const minimize = require('./helpers/minimize');
69
69
  const MongooseBulkSaveIncompleteError = require('./error/bulkSaveIncompleteError');
70
70
  const ObjectExpectedError = require('./error/objectExpected');
71
71
  const decorateBulkWriteResult = require('./helpers/model/decorateBulkWriteResult');
72
-
73
72
  const modelCollectionSymbol = Symbol('mongoose#Model#collection');
74
73
  const modelDbSymbol = Symbol('mongoose#Model#db');
75
74
  const modelSymbol = require('./helpers/symbols').modelSymbol;
@@ -3394,7 +3393,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3394
3393
  return bulkWriteResult;
3395
3394
  }
3396
3395
 
3397
- const validations = ops.map(op => castBulkWrite(this, op, options));
3396
+ const validations = options?._skipCastBulkWrite ? [] : ops.map(op => castBulkWrite(this, op, options));
3398
3397
  const asyncLocalStorage = this.db.base.transactionAsyncLocalStorage?.getStore();
3399
3398
  if ((!options || !options.hasOwnProperty('session')) && asyncLocalStorage?.session != null) {
3400
3399
  options = { ...options, session: asyncLocalStorage.session };
@@ -3431,6 +3430,9 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3431
3430
  let validationErrors = [];
3432
3431
  const results = [];
3433
3432
  await new Promise((resolve) => {
3433
+ if (validations.length === 0) {
3434
+ return resolve();
3435
+ }
3434
3436
  for (let i = 0; i < validations.length; ++i) {
3435
3437
  validations[i]((err) => {
3436
3438
  if (err == null) {
@@ -3568,8 +3570,9 @@ Model.bulkSave = async function bulkSave(documents, options) {
3568
3570
 
3569
3571
  await Promise.all(documents.map(doc => buildPreSavePromise(doc, options)));
3570
3572
 
3571
- const writeOperations = this.buildBulkWriteOperations(documents, { skipValidation: true, timestamps: options.timestamps });
3572
- const { bulkWriteResult, bulkWriteError } = await this.bulkWrite(writeOperations, { skipValidation: true, ...options }).then(
3573
+ const writeOperations = this.buildBulkWriteOperations(documents, options);
3574
+ const opts = { skipValidation: true, _skipCastBulkWrite: true, ...options };
3575
+ const { bulkWriteResult, bulkWriteError } = await this.bulkWrite(writeOperations, opts).then(
3573
3576
  (res) => ({ bulkWriteResult: res, bulkWriteError: null }),
3574
3577
  (err) => ({ bulkWriteResult: null, bulkWriteError: err })
3575
3578
  );
@@ -3868,16 +3871,17 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3868
3871
  }
3869
3872
 
3870
3873
  setDefaultOptions();
3871
- const discriminatorKey = this.schema.options.discriminatorKey;
3872
3874
 
3873
- const writeOperations = documents.reduce((accumulator, document, i) => {
3875
+ const writeOperations = documents.map((document, i) => {
3874
3876
  if (!options.skipValidation) {
3875
3877
  if (!(document instanceof Document)) {
3876
3878
  throw new Error(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`);
3877
3879
  }
3878
- const validationError = document.validateSync();
3879
- if (validationError) {
3880
- throw validationError;
3880
+ if (options.validateBeforeSave == null || options.validateBeforeSave) {
3881
+ const err = document.validateSync();
3882
+ if (err != null) {
3883
+ throw err;
3884
+ }
3881
3885
  }
3882
3886
  }
3883
3887
 
@@ -3885,9 +3889,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3885
3889
  if (isANewDocument) {
3886
3890
  const writeOperation = { insertOne: { document } };
3887
3891
  utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
3888
- accumulator.push(writeOperation);
3889
-
3890
- return accumulator;
3892
+ return writeOperation;
3891
3893
  }
3892
3894
 
3893
3895
  const delta = document.$__delta();
@@ -3910,22 +3912,14 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3910
3912
  }
3911
3913
  }
3912
3914
 
3913
- // Set the discriminator key, so bulk write casting knows which
3914
- // schema to use re: gh-13907
3915
- if (document[discriminatorKey] != null && !(discriminatorKey in where)) {
3916
- where[discriminatorKey] = document[discriminatorKey];
3917
- }
3918
-
3919
3915
  document.$__version(where, delta);
3920
3916
  const writeOperation = { updateOne: { filter: where, update: changes } };
3921
3917
  utils.injectTimestampsOption(writeOperation.updateOne, options.timestamps);
3922
- accumulator.push(writeOperation);
3923
-
3924
- return accumulator;
3918
+ return writeOperation;
3925
3919
  }
3926
3920
 
3927
- return accumulator;
3928
- }, []);
3921
+ return null;
3922
+ }).filter(op => op !== null);
3929
3923
 
3930
3924
  return writeOperations;
3931
3925
 
@@ -4893,6 +4887,39 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
4893
4887
  return model;
4894
4888
  };
4895
4889
 
4890
+ /**
4891
+ * If auto encryption is enabled, returns a ClientEncryption instance that is configured with the same settings that
4892
+ * Mongoose's underlying MongoClient is using. If the client has not yet been configured, returns null.
4893
+ *
4894
+ * @returns {ClientEncryption | null}
4895
+ */
4896
+ Model.clientEncryption = function clientEncryption() {
4897
+ const ClientEncryption = this.base.driver.get().ClientEncryption;
4898
+ if (!ClientEncryption) {
4899
+ throw new Error('The mongodb driver must be used to obtain a ClientEncryption object.');
4900
+ }
4901
+
4902
+ const client = this.collection?.conn?.client;
4903
+
4904
+ if (!client) return null;
4905
+
4906
+ const autoEncryptionOptions = client.options.autoEncryption;
4907
+
4908
+ if (!autoEncryptionOptions) return null;
4909
+
4910
+ const {
4911
+ keyVaultNamespace,
4912
+ keyVaultClient,
4913
+ kmsProviders,
4914
+ credentialProviders,
4915
+ proxyOptions,
4916
+ tlsOptions
4917
+ } = autoEncryptionOptions;
4918
+ return new ClientEncryption(keyVaultClient ?? client,
4919
+ { keyVaultNamespace, kmsProviders, credentialProviders, proxyOptions, tlsOptions }
4920
+ );
4921
+ };
4922
+
4896
4923
  /**
4897
4924
  * Update this model to use the new connection, including updating all internal
4898
4925
  * references and creating a new `Collection` instance using the new connection.
package/lib/query.js CHANGED
@@ -165,6 +165,32 @@ function Query(conditions, options, model, collection) {
165
165
  }
166
166
  }
167
167
 
168
+ // Helper function to check if an object is empty or contains only empty objects/arrays
169
+ function isEmptyFilter(obj) {
170
+ if (obj == null) return true;
171
+ if (typeof obj !== 'object') return true;
172
+ if (Object.keys(obj).length === 0) return true;
173
+
174
+ // Check $and, $or, $nor arrays
175
+ for (const key of ['$and', '$or', '$nor']) {
176
+ if (Array.isArray(obj[key])) {
177
+ // If array is empty or all elements are empty objects, consider it empty
178
+ if (obj[key].length === 0 || obj[key].every(item => isEmptyFilter(item))) {
179
+ return true;
180
+ }
181
+ }
182
+ }
183
+
184
+ return false;
185
+ }
186
+
187
+ // Helper function to check for empty/invalid filter
188
+ function checkRequireFilter(filter, options) {
189
+ if (options && options.requireFilter && isEmptyFilter(filter)) {
190
+ throw new Error('Empty or invalid filter not allowed with requireFilter enabled');
191
+ }
192
+ }
193
+
168
194
  /*!
169
195
  * inherit mquery
170
196
  */
@@ -3111,6 +3137,7 @@ function _handleSortValue(val, key) {
3111
3137
  *
3112
3138
  * @param {Object|Query} [filter] mongodb selector
3113
3139
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3140
+ * @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
3114
3141
  * @return {Query} this
3115
3142
  * @see DeleteResult https://mongodb.github.io/node-mongodb-native/6.15/interfaces/DeleteResult.html
3116
3143
  * @see deleteOne https://mongodb.github.io/node-mongodb-native/6.15/classes/Collection.html#deleteOne
@@ -3148,6 +3175,9 @@ Query.prototype._deleteOne = async function _deleteOne() {
3148
3175
  this._applyTranslateAliases();
3149
3176
  this._castConditions();
3150
3177
 
3178
+ // Check for empty/invalid filter with requireFilter option
3179
+ checkRequireFilter(this._conditions, this.options);
3180
+
3151
3181
  if (this.error() != null) {
3152
3182
  throw this.error();
3153
3183
  }
@@ -3183,6 +3213,7 @@ Query.prototype._deleteOne = async function _deleteOne() {
3183
3213
  *
3184
3214
  * @param {Object|Query} [filter] mongodb selector
3185
3215
  * @param {Object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
3216
+ * @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
3186
3217
  * @return {Query} this
3187
3218
  * @see DeleteResult https://mongodb.github.io/node-mongodb-native/6.15/interfaces/DeleteResult.html
3188
3219
  * @see deleteMany https://mongodb.github.io/node-mongodb-native/6.15/classes/Collection.html#deleteMany
@@ -3220,6 +3251,9 @@ Query.prototype._deleteMany = async function _deleteMany() {
3220
3251
  this._applyTranslateAliases();
3221
3252
  this._castConditions();
3222
3253
 
3254
+ // Check for empty/invalid filter with requireFilter option
3255
+ checkRequireFilter(this._conditions, this.options);
3256
+
3223
3257
  if (this.error() != null) {
3224
3258
  throw this.error();
3225
3259
  }
@@ -3311,6 +3345,7 @@ function prepareDiscriminatorCriteria(query) {
3311
3345
  * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
3312
3346
  * - `runValidators`: if true, runs [update validators](https://mongoosejs.com/docs/validation.html#update-validators) on this command. Update validators validate the update operation against the model's schema.
3313
3347
  * - `setDefaultsOnInsert`: `true` by default. If `setDefaultsOnInsert` and `upsert` are true, mongoose will apply the [defaults](https://mongoosejs.com/docs/defaults.html) specified in the model's schema if a new document is created.
3348
+ * - `requireFilter`: bool - if true, throws an error if the filter is empty (`{}`). Defaults to false.
3314
3349
  *
3315
3350
  * #### Example:
3316
3351
  *
@@ -3337,6 +3372,7 @@ function prepareDiscriminatorCriteria(query) {
3337
3372
  * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3338
3373
  * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
3339
3374
  * @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
3375
+ * @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
3340
3376
  * @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
3341
3377
  * @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/
3342
3378
  * @see ModifyResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html
@@ -3417,6 +3453,9 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
3417
3453
  this._applyTranslateAliases();
3418
3454
  this._castConditions();
3419
3455
 
3456
+ // Check for empty/invalid filter with requireFilter option
3457
+ checkRequireFilter(this._conditions, this.options);
3458
+
3420
3459
  _castArrayFilters(this);
3421
3460
 
3422
3461
  if (this.error()) {
@@ -3500,6 +3539,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
3500
3539
  *
3501
3540
  * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
3502
3541
  * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
3542
+ * - `requireFilter`: bool - if true, throws an error if the filter is empty (`{}`). Defaults to false.
3503
3543
  *
3504
3544
  * #### Example:
3505
3545
  *
@@ -3512,6 +3552,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
3512
3552
  * @param {Object} [filter]
3513
3553
  * @param {Object} [options]
3514
3554
  * @param {Boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
3555
+ * @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
3515
3556
  * @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
3516
3557
  * @param {Boolean|String} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
3517
3558
  * @return {Query} this
@@ -3551,6 +3592,9 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
3551
3592
  this._applyTranslateAliases();
3552
3593
  this._castConditions();
3553
3594
 
3595
+ // Check for empty/invalid filter with requireFilter option
3596
+ checkRequireFilter(this._conditions, this.options);
3597
+
3554
3598
  if (this.error() != null) {
3555
3599
  throw this.error();
3556
3600
  }
@@ -3590,6 +3634,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
3590
3634
  * - `sort`: if multiple docs are found by the conditions, sets the sort order to choose which doc to update
3591
3635
  * - `maxTimeMS`: puts a time limit on the query - requires mongodb >= 2.6.0
3592
3636
  * - `includeResultMetadata`: if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html) rather than just the document
3637
+ * - `requireFilter`: bool - if true, throws an error if the filter is empty (`{}`). Defaults to false.
3593
3638
  *
3594
3639
  * #### Example:
3595
3640
  *
@@ -3612,6 +3657,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
3612
3657
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
3613
3658
  * @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`.
3614
3659
  * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
3660
+ * @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
3615
3661
  * @return {Query} this
3616
3662
  * @api public
3617
3663
  */
@@ -3667,6 +3713,10 @@ Query.prototype.findOneAndReplace = function(filter, replacement, options) {
3667
3713
  Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
3668
3714
  this._applyTranslateAliases();
3669
3715
  this._castConditions();
3716
+
3717
+ // Check for empty/invalid filter with requireFilter option
3718
+ checkRequireFilter(this._conditions, this.options);
3719
+
3670
3720
  if (this.error() != null) {
3671
3721
  throw this.error();
3672
3722
  }
@@ -3969,6 +4019,9 @@ async function _updateThunk(op) {
3969
4019
 
3970
4020
  this._castConditions();
3971
4021
 
4022
+ // Check for empty/invalid filter with requireFilter option
4023
+ checkRequireFilter(this._conditions, this.options);
4024
+
3972
4025
  _castArrayFilters(this);
3973
4026
 
3974
4027
  if (this.error() != null) {
@@ -4119,6 +4172,7 @@ Query.prototype._replaceOne = async function _replaceOne() {
4119
4172
  * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
4120
4173
  * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
4121
4174
  * @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
4175
+ * @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
4122
4176
  * @return {Query} this
4123
4177
  * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
4124
4178
  * @see Query docs https://mongoosejs.com/docs/queries.html
@@ -4193,6 +4247,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
4193
4247
  * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
4194
4248
  * @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
4195
4249
  * @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
4250
+ * @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
4196
4251
  * @return {Query} this
4197
4252
  * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
4198
4253
  * @see Query docs https://mongoosejs.com/docs/queries.html
@@ -4259,6 +4314,7 @@ Query.prototype.updateOne = function(conditions, doc, options, callback) {
4259
4314
  * @param {Object} [options.writeConcern=null] sets the [write concern](https://www.mongodb.com/docs/manual/reference/write-concern/) for replica sets. Overrides the [schema-level write concern](https://mongoosejs.com/docs/guide.html#writeConcern)
4260
4315
  * @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
4261
4316
  * @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
4317
+ * @param {Boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
4262
4318
  * @return {Query} this
4263
4319
  * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
4264
4320
  * @see Query docs https://mongoosejs.com/docs/queries.html
@@ -718,6 +718,10 @@ SchemaArray.prototype.toJSONSchema = function toJSONSchema(options) {
718
718
  };
719
719
  };
720
720
 
721
+ SchemaArray.prototype.autoEncryptionType = function autoEncryptionType() {
722
+ return 'array';
723
+ };
724
+
721
725
  /*!
722
726
  * Module exports.
723
727
  */
@@ -254,6 +254,10 @@ SchemaBigInt.prototype.toJSONSchema = function toJSONSchema(options) {
254
254
  return createJSONSchemaTypeDefinition('string', 'long', options?.useBsonType, isRequired);
255
255
  };
256
256
 
257
+ SchemaBigInt.prototype.autoEncryptionType = function autoEncryptionType() {
258
+ return 'long';
259
+ };
260
+
257
261
  /*!
258
262
  * Module exports.
259
263
  */
@@ -304,6 +304,10 @@ SchemaBoolean.prototype.toJSONSchema = function toJSONSchema(options) {
304
304
  return createJSONSchemaTypeDefinition('boolean', 'bool', options?.useBsonType, isRequired);
305
305
  };
306
306
 
307
+ SchemaBoolean.prototype.autoEncryptionType = function autoEncryptionType() {
308
+ return 'bool';
309
+ };
310
+
307
311
  /*!
308
312
  * Module exports.
309
313
  */
@@ -314,6 +314,10 @@ SchemaBuffer.prototype.toJSONSchema = function toJSONSchema(options) {
314
314
  return createJSONSchemaTypeDefinition('string', 'binData', options?.useBsonType, isRequired);
315
315
  };
316
316
 
317
+ SchemaBuffer.prototype.autoEncryptionType = function autoEncryptionType() {
318
+ return 'binData';
319
+ };
320
+
317
321
  /*!
318
322
  * Module exports.
319
323
  */
@@ -440,6 +440,10 @@ SchemaDate.prototype.toJSONSchema = function toJSONSchema(options) {
440
440
  return createJSONSchemaTypeDefinition('string', 'date', options?.useBsonType, isRequired);
441
441
  };
442
442
 
443
+ SchemaDate.prototype.autoEncryptionType = function autoEncryptionType() {
444
+ return 'date';
445
+ };
446
+
443
447
  /*!
444
448
  * Module exports.
445
449
  */
@@ -235,6 +235,10 @@ SchemaDecimal128.prototype.toJSONSchema = function toJSONSchema(options) {
235
235
  return createJSONSchemaTypeDefinition('string', 'decimal', options?.useBsonType, isRequired);
236
236
  };
237
237
 
238
+ SchemaDecimal128.prototype.autoEncryptionType = function autoEncryptionType() {
239
+ return 'decimal';
240
+ };
241
+
238
242
  /*!
239
243
  * Module exports.
240
244
  */
@@ -218,6 +218,10 @@ SchemaDouble.prototype.toJSONSchema = function toJSONSchema(options) {
218
218
  return createJSONSchemaTypeDefinition('number', 'double', options?.useBsonType, isRequired);
219
219
  };
220
220
 
221
+ SchemaDouble.prototype.autoEncryptionType = function autoEncryptionType() {
222
+ return 'double';
223
+ };
224
+
221
225
  /*!
222
226
  * Module exports.
223
227
  */
@@ -260,6 +260,10 @@ SchemaInt32.prototype.toJSONSchema = function toJSONSchema(options) {
260
260
  return createJSONSchemaTypeDefinition('number', 'int', options?.useBsonType, isRequired);
261
261
  };
262
262
 
263
+ SchemaInt32.prototype.autoEncryptionType = function autoEncryptionType() {
264
+ return 'int';
265
+ };
266
+
263
267
 
264
268
  /*!
265
269
  * Module exports.
package/lib/schema/map.js CHANGED
@@ -95,6 +95,10 @@ class SchemaMap extends SchemaType {
95
95
 
96
96
  return result;
97
97
  }
98
+
99
+ autoEncryptionType() {
100
+ return 'object';
101
+ }
98
102
  }
99
103
 
100
104
  /**
@@ -304,6 +304,10 @@ SchemaObjectId.prototype.toJSONSchema = function toJSONSchema(options) {
304
304
  return createJSONSchemaTypeDefinition('string', 'objectId', options?.useBsonType, isRequired);
305
305
  };
306
306
 
307
+ SchemaObjectId.prototype.autoEncryptionType = function autoEncryptionType() {
308
+ return 'objectId';
309
+ };
310
+
307
311
  /*!
308
312
  * Module exports.
309
313
  */
@@ -712,6 +712,10 @@ SchemaString.prototype.toJSONSchema = function toJSONSchema(options) {
712
712
  return createJSONSchemaTypeDefinition('string', 'string', options?.useBsonType, isRequired);
713
713
  };
714
714
 
715
+ SchemaString.prototype.autoEncryptionType = function autoEncryptionType() {
716
+ return 'string';
717
+ };
718
+
715
719
  /*!
716
720
  * Module exports.
717
721
  */
@@ -298,6 +298,10 @@ SchemaUUID.prototype.toJSONSchema = function toJSONSchema(options) {
298
298
  return createJSONSchemaTypeDefinition('string', 'binData', options?.useBsonType, isRequired);
299
299
  };
300
300
 
301
+ SchemaUUID.prototype.autoEncryptionType = function autoEncryptionType() {
302
+ return 'binData';
303
+ };
304
+
301
305
  /*!
302
306
  * Module exports.
303
307
  */