mongoose 9.9.5 → 9.10.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.
package/eslint.config.mjs CHANGED
@@ -43,6 +43,7 @@ export default defineConfig([
43
43
  'no-constant-condition': 'off',
44
44
  'no-multi-spaces': 'error',
45
45
  'func-call-spacing': 'error',
46
+ 'no-unused-vars': ['error', { ignoreRestSiblings: true }],
46
47
  'no-trailing-spaces': 'error',
47
48
  'no-undef': 'error',
48
49
  'no-unneeded-ternary': 'error',
package/lib/aggregate.js CHANGED
@@ -810,8 +810,7 @@ Aggregate.prototype.explain = async function explain(verbosity) {
810
810
  const postFilter = buildMiddlewareFilter(this.options, 'post');
811
811
 
812
812
  // Remove middleware option before passing to MongoDB
813
- const options = this.options != null ? { ...this.options } : {};
814
- delete options.middleware;
813
+ const { middleware, ...options } = this.options ?? {};
815
814
 
816
815
  try {
817
816
  await model.hooks.execPre('aggregate', this, [], { filter: preFilter });
@@ -925,6 +924,8 @@ Aggregate.prototype.option = function(value) {
925
924
  * Sets the `cursor` option and executes this aggregation, returning an aggregation cursor.
926
925
  * Cursors are useful if you want to process the results of the aggregation one-at-a-time
927
926
  * because the aggregation result is too big to fit into memory.
927
+ * Creating an aggregation cursor runs pre aggregate hooks, but not post aggregate hooks.
928
+ * To skip pre aggregate hooks, pass `middleware: false` to `.cursor()`.
928
929
  *
929
930
  * #### Example:
930
931
  *
@@ -935,6 +936,8 @@ Aggregate.prototype.option = function(value) {
935
936
  *
936
937
  * @param {object} options
937
938
  * @param {number} [options.batchSize] set the cursor batch size
939
+ * @param {boolean|object} [options.middleware=true] set to `false` to skip user-defined pre aggregate middleware
940
+ * @param {boolean} [options.middleware.pre=true] set to `false` to skip pre aggregate middleware
938
941
  * @param {boolean} [options.useMongooseAggCursor] use experimental mongoose-specific aggregation cursor (for `eachAsync()` and other query cursor semantics)
939
942
  * @return {AggregationCursor} cursor representing this aggregation
940
943
  * @api public
@@ -943,6 +946,11 @@ Aggregate.prototype.option = function(value) {
943
946
 
944
947
  Aggregate.prototype.cursor = function(options) {
945
948
  this._optionsForExec();
949
+ if (utils.hasUserDefinedProperty(options, 'middleware')) {
950
+ const { middleware, ...cursorOptions } = options;
951
+ this.options.middleware = middleware;
952
+ options = cursorOptions;
953
+ }
946
954
  this.options.cursor = options || {};
947
955
  return new AggregationCursor(this); // return this;
948
956
  };
@@ -1122,8 +1130,7 @@ Aggregate.prototype.exec = async function exec() {
1122
1130
  throw new MongooseError('Aggregate has empty pipeline');
1123
1131
  }
1124
1132
 
1125
- const options = clone(_this.options || {});
1126
- delete options.middleware;
1133
+ const { middleware, ...options } = clone(_this.options || {});
1127
1134
 
1128
1135
  let result;
1129
1136
  try {
@@ -11,6 +11,7 @@ const immediate = require('../helpers/immediate');
11
11
  const kareem = require('kareem');
12
12
  const util = require('util');
13
13
  const { cursorNextChannel } = require('../tracing');
14
+ const { buildMiddlewareFilter } = require('../helpers/buildMiddlewareFilter');
14
15
 
15
16
  /**
16
17
  * An AggregationCursor is a concurrency primitive for processing aggregation
@@ -49,7 +50,14 @@ function AggregationCursor(agg) {
49
50
  this._mongooseOptions = {};
50
51
 
51
52
  if (connection) {
52
- this.cursor = connection.db.aggregate(agg._pipeline, agg.options || {});
53
+ let options = agg.options || {};
54
+ if (Object.hasOwn(options, 'middleware')) {
55
+ // `middleware` is a Mongoose-only option used to skip user hooks; strip
56
+ // it from the clone passed through to the MongoDB driver.
57
+ const { middleware, ...driverOptions } = options;
58
+ options = driverOptions;
59
+ }
60
+ this.cursor = connection.db.aggregate(agg._pipeline, options);
53
61
  setImmediate(() => this.emit('cursor', this.cursor));
54
62
  } else {
55
63
  _init(model, this, agg);
@@ -63,7 +71,8 @@ util.inherits(AggregationCursor, Readable);
63
71
  */
64
72
 
65
73
  function _init(model, c, agg) {
66
- model.hooks.execPre('aggregate', agg).then(() => onPreComplete(null), err => onPreComplete(err));
74
+ const preFilter = buildMiddlewareFilter(agg.options, 'pre');
75
+ model.hooks.execPre('aggregate', agg, [], { filter: preFilter }).then(() => onPreComplete(null), err => onPreComplete(err));
67
76
 
68
77
  function onPreComplete(err) {
69
78
  if (err != null) {
@@ -90,7 +99,14 @@ function _init(model, c, agg) {
90
99
 
91
100
  function _getRawCursor(model, aggregationCursor, agg) {
92
101
  try {
93
- const cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
102
+ let options = agg.options;
103
+ if (Object.hasOwn(options, 'middleware')) {
104
+ // `middleware` is a Mongoose-only option used to skip user hooks; strip
105
+ // it from the clone passed through to the MongoDB driver.
106
+ const { middleware, ...driverOptions } = options;
107
+ options = driverOptions;
108
+ }
109
+ const cursor = model.collection.aggregate(agg._pipeline, options);
94
110
  aggregationCursor.cursor = cursor;
95
111
  aggregationCursor.emit('cursor', cursor);
96
112
  } catch (err) {
@@ -13,6 +13,7 @@ const immediate = require('../helpers/immediate');
13
13
  const { once } = require('events');
14
14
  const util = require('util');
15
15
  const { cursorNextChannel } = require('../tracing');
16
+ const { buildMiddlewareFilter } = require('../helpers/buildMiddlewareFilter');
16
17
 
17
18
  /**
18
19
  * A QueryCursor is a concurrency primitive for processing query results
@@ -21,7 +22,8 @@ const { cursorNextChannel } = require('../tracing');
21
22
  * one at a time.
22
23
  *
23
24
  * QueryCursors execute the model's pre `find` hooks before loading any documents
24
- * from MongoDB, and the model's post `find` hooks after loading each document.
25
+ * from MongoDB, and the model's post `find` hooks once per document,
26
+ * with an array containing that document.
25
27
  *
26
28
  * Unless you're an advanced user, do **not** instantiate this class directly.
27
29
  * Use [`Query#cursor()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.cursor()) instead.
@@ -50,6 +52,7 @@ function QueryCursor(query) {
50
52
  this._transforms = [];
51
53
  this.model = model;
52
54
  this.options = {};
55
+ this._postMiddlewareFilter = null;
53
56
 
54
57
  const onPreComplete = (err) => {
55
58
  if (err != null) {
@@ -74,7 +77,15 @@ function QueryCursor(query) {
74
77
  this.listeners('error').length > 0 && this.emit('error', err);
75
78
  return;
76
79
  }
77
- Object.assign(this.options, query._optionsForExec());
80
+ const { middleware, ...options } = query._optionsForExec();
81
+ Object.assign(this.options, options);
82
+ // `middleware` is a Mongoose-only option used to skip user hooks; strip it
83
+ // so it isn't passed through to the MongoDB driver. `this.options` is public
84
+ // and may be modified while pre hooks run, so check the target after assign.
85
+ if (Object.hasOwn(this.options, 'middleware')) {
86
+ delete this.options.middleware;
87
+ }
88
+ this._postMiddlewareFilter = buildMiddlewareFilter(query.options, 'post');
78
89
  this._transforms = this._transforms.concat(query._transforms.slice());
79
90
  if (this.options.transform) {
80
91
  this._transforms.push(this.options.transform);
@@ -98,7 +109,8 @@ function QueryCursor(query) {
98
109
  }
99
110
  };
100
111
 
101
- model.hooks.execPre('find', query).then(() => onPreComplete(null), err => onPreComplete(err));
112
+ const preFilter = buildMiddlewareFilter(query.options, 'pre');
113
+ model.hooks.execPre('find', query, [], { filter: preFilter }).then(() => onPreComplete(null), err => onPreComplete(err));
102
114
  }
103
115
 
104
116
  util.inherits(QueryCursor, Readable);
@@ -607,7 +619,7 @@ function _populateBatch() {
607
619
 
608
620
  function _nextDoc(ctx, doc, pop, callback) {
609
621
  if (ctx.query._mongooseOptions.lean) {
610
- return ctx.model.hooks.execPost('find', ctx.query, [[doc]]).then(() => callback(null, doc), err => callback(err));
622
+ return ctx.model.hooks.execPost('find', ctx.query, [[doc]], { filter: ctx._postMiddlewareFilter }).then(() => callback(null, doc), err => callback(err));
611
623
  }
612
624
 
613
625
  const { model, _fields, _userProvidedFields, options } = ctx.query;
@@ -618,7 +630,7 @@ function _nextDoc(ctx, doc, pop, callback) {
618
630
  if (options.session != null) {
619
631
  doc.$session(options.session);
620
632
  }
621
- ctx.model.hooks.execPost('find', ctx.query, [[doc]]).then(() => callback(null, doc), err => callback(err));
633
+ ctx.model.hooks.execPost('find', ctx.query, [[doc]], { filter: ctx._postMiddlewareFilter }).then(() => callback(null, doc), err => callback(err));
622
634
  });
623
635
  }
624
636
 
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const isPOJO = require('./isPOJO');
3
4
  const symbols = require('../schema/symbols');
4
5
 
5
6
  /**
@@ -15,10 +16,27 @@ const isBuiltInMiddleware = hook => hook.fn[symbols.builtInMiddleware];
15
16
  * @returns {Function|null} - null runs all middleware, isBuiltInMiddleware skips user middleware
16
17
  */
17
18
  function buildMiddlewareFilter(options, phase) {
18
- const shouldRun = options?.middleware?.[phase] ?? options?.middleware ?? true;
19
- return shouldRun ? null : isBuiltInMiddleware;
19
+ const shouldSkip = options?.middleware === false || options?.middleware?.[phase] === false;
20
+ return shouldSkip ? isBuiltInMiddleware : null;
21
+ }
22
+
23
+ /**
24
+ * kareem `getOptions` implementation for custom statics and methods: reads the
25
+ * `middleware` option from the call's last argument if it is a plain object.
26
+ *
27
+ * @param {Array} args - Arguments the custom static or method was called with
28
+ * @returns {object} - Per-call kareem options with pre/post filters
29
+ */
30
+ function middlewareFiltersFromLastArg(args) {
31
+ const lastArg = args[args.length - 1];
32
+ const options = isPOJO(lastArg) ? lastArg : null;
33
+ return {
34
+ pre: { filter: buildMiddlewareFilter(options, 'pre') },
35
+ post: { filter: buildMiddlewareFilter(options, 'post') }
36
+ };
20
37
  }
21
38
 
22
39
  module.exports = {
23
- buildMiddlewareFilter
40
+ buildMiddlewareFilter,
41
+ middlewareFiltersFromLastArg
24
42
  };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { buildMiddlewareFilter } = require('../buildMiddlewareFilter');
3
+ const { buildMiddlewareFilter, middlewareFiltersFromLastArg } = require('../buildMiddlewareFilter');
4
4
 
5
5
  /*!
6
6
  * ignore
@@ -118,8 +118,15 @@ function applyHooks(model, schema, options) {
118
118
  }
119
119
  const originalMethod = objToDecorate[method];
120
120
  objToDecorate[`$__${method}`] = objToDecorate[method];
121
+ // Only read the `middleware` option from the method's last argument if
122
+ // the method opts in via `supportsMiddlewareOption`. Custom methods have
123
+ // arbitrary signatures, so the last argument may be an object with an
124
+ // unrelated `middleware` property.
125
+ const methodOptions = originalMethod.supportsMiddlewareOption === true ?
126
+ { ...customMethodOptions, getOptions: middlewareFiltersFromLastArg } :
127
+ customMethodOptions;
121
128
  objToDecorate[method] = middleware.
122
- createWrapper(method, originalMethod, null, customMethodOptions);
129
+ createWrapper(method, originalMethod, null, methodOptions);
123
130
  }
124
131
  }
125
132
 
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const { middlewareFiltersFromLastArg } = require('../buildMiddlewareFilter');
3
4
  const { queryMiddlewareFunctions, aggregateMiddlewareFunctions, modelMiddlewareFunctions, documentMiddlewareFunctions } = require('../../constants');
4
5
 
5
6
  const middlewareFunctions = Array.from(
@@ -27,7 +28,14 @@ module.exports = function applyStaticHooks(model, hooks, statics) {
27
28
  if (hooks.hasHooks(key)) {
28
29
  const original = model[key];
29
30
 
30
- model[key] = hooks.createWrapper(key, original);
31
+ // Only read the `middleware` option from the static's last argument if
32
+ // the static opts in via `supportsMiddlewareOption`. Custom statics have
33
+ // arbitrary signatures, so the last argument may be an object with an
34
+ // unrelated `middleware` property.
35
+ const wrapperOptions = original.supportsMiddlewareOption === true ?
36
+ { getOptions: middlewareFiltersFromLastArg } :
37
+ undefined;
38
+ model[key] = hooks.createWrapper(key, original, null, wrapperOptions);
31
39
  }
32
40
  }
33
41
  };
package/lib/model.js CHANGED
@@ -1229,8 +1229,8 @@ Model.createCollection = async function createCollection(options) {
1229
1229
 
1230
1230
  // Remove middleware option before passing to MongoDB
1231
1231
  if (options?.middleware != null) {
1232
- options = { ...options };
1233
- delete options.middleware;
1232
+ const { middleware, ...driverOptions } = options;
1233
+ options = driverOptions;
1234
1234
  }
1235
1235
 
1236
1236
  [options] = await this.hooks.execPre('createCollection', this, [options], { filter: preFilter }).catch(err => {
@@ -2134,6 +2134,48 @@ Model.find = function find(conditions, projection, options) {
2134
2134
  return mq.find(conditions);
2135
2135
  };
2136
2136
 
2137
+ /**
2138
+ * Finds documents and counts the number of documents matching the filter.
2139
+ *
2140
+ * This function fires both `find` and `countDocuments` middleware.
2141
+ *
2142
+ * #### Example:
2143
+ *
2144
+ * const [adventures, total] = await Adventure.findAndCount(
2145
+ * { type: 'jungle' },
2146
+ * null,
2147
+ * { sort: { name: 1 }, skip: 10, limit: 10 }
2148
+ * );
2149
+ *
2150
+ * adventures; // Array of at most 10 documents matching type 'jungle'
2151
+ * total; // Number of documents matching type 'jungle'
2152
+ *
2153
+ * @param {object|ObjectId} filter
2154
+ * @param {object|string|string[]} [projection] optional fields to return
2155
+ * @param {object} [options] optional query options
2156
+ * @return {Promise<Array>} a promise that resolves to `[documents, total]`
2157
+ * @api public
2158
+ */
2159
+
2160
+ Model.findAndCount = async function findAndCount(conditions, projection, options) {
2161
+ _checkContext(this, 'findAndCount');
2162
+
2163
+ if (options == null) {
2164
+ throw new MongooseError('Model.findAndCount() requires an `options` argument with `sort` and `limit`');
2165
+ }
2166
+ if (options.sort == null) {
2167
+ throw new MongooseError('Model.findAndCount() requires `options.sort`');
2168
+ }
2169
+ if (options.limit == null) {
2170
+ throw new MongooseError('Model.findAndCount() requires `options.limit`');
2171
+ }
2172
+
2173
+ const findQuery = this.find(conditions, projection, options);
2174
+ const countQuery = findQuery.clone().countDocuments().skip(0).limit(null);
2175
+
2176
+ return Promise.all([findQuery.exec(), countQuery.exec()]);
2177
+ };
2178
+
2137
2179
  /**
2138
2180
  * Finds a single document by its _id field. `findById(id)` is equivalent to `findOne({ _id: id })`.
2139
2181
  *
@@ -4597,8 +4639,8 @@ async function _populatePath(model, docs, populateOptions) {
4597
4639
  select = select.filter(field => field !== '-_id');
4598
4640
  } else {
4599
4641
  // preserve original select conditions by copying
4600
- select = { ...select };
4601
- delete select._id;
4642
+ const { _id, ...selectWithoutId } = select;
4643
+ select = selectWithoutId;
4602
4644
  }
4603
4645
  }
4604
4646
 
package/lib/query.js CHANGED
@@ -5329,7 +5329,8 @@ Query.prototype._applyPaths = function applyPaths() {
5329
5329
  * Returns a wrapper around a [mongodb driver cursor](https://mongodb.github.io/node-mongodb-native/7.0/classes/FindCursor.html).
5330
5330
  * A QueryCursor exposes a Streams3 interface, as well as a `.next()` function.
5331
5331
  *
5332
- * The `.cursor()` function triggers pre find hooks, but **not** post find hooks.
5332
+ * The `.cursor()` function triggers pre find hooks before opening the cursor
5333
+ * and post find hooks once per document, with an array containing that document.
5333
5334
  *
5334
5335
  * #### Example:
5335
5336
  *
@@ -5358,9 +5359,14 @@ Query.prototype._applyPaths = function applyPaths() {
5358
5359
  * #### Valid options
5359
5360
  *
5360
5361
  * - `transform`: optional function which accepts a mongoose document. The return value of the function will be emitted on `data` and returned by `.next()`.
5362
+ * - [`middleware`](https://mongoosejs.com/docs/middleware.html#skipping): set to `false` to skip all user-defined middleware, or `{ pre: false }` / `{ post: false }` to skip only pre or post find hooks
5361
5363
  *
5362
5364
  * @return {QueryCursor}
5363
5365
  * @param {object} [options]
5366
+ * @param {Function} [options.transform] optional function which accepts a mongoose document. The return value of the function will be emitted on `data` and returned by `.next()`.
5367
+ * @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware
5368
+ * @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
5369
+ * @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks
5364
5370
  * @see QueryCursor https://mongoosejs.com/docs/api/querycursor.html
5365
5371
  * @api public
5366
5372
  */
@@ -400,7 +400,15 @@ SchemaArray.prototype.cast = function(value, doc, init, prev, options) {
400
400
  if (options.virtuals) {
401
401
  opts.virtuals = options.virtuals;
402
402
  }
403
- rawValue[i] = caster.applySetters(rawValue[i], doc, init, void 0, opts);
403
+ if (rawValue[i] === undefined && caster.defaultValue !== undefined) {
404
+ // No need to add _skipMarkModified to opts here (fallback for `getDefault()` opts parameter)
405
+ // because array setters don't rely on Document.prototype.set() - subdocuments do rely on
406
+ // Document.prototype.set() when applying setters, which is why _skipMarkModified is necessary
407
+ // for subdocuments. But here we know that `caster` is an array not a subdocument.
408
+ rawValue[i] = caster.getDefault(doc, init, null, opts);
409
+ } else {
410
+ rawValue[i] = caster.applySetters(rawValue[i], doc, init, void 0, opts);
411
+ }
404
412
  }
405
413
  } catch (e) {
406
414
  // rethrow
@@ -466,8 +466,8 @@ SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) {
466
466
  // Don't pass `path` to $init - it's only for this DocumentArray itself, not its element fields.
467
467
  // Element subdocuments use relative paths internally for change tracking.
468
468
  if (options.path != null) {
469
- options = { ...options };
470
- delete options.path;
469
+ const { path, ...initOptions } = options;
470
+ options = initOptions;
471
471
  }
472
472
  rawArray[i] = subdoc.$init(rawArray[i], options);
473
473
  } else {
@@ -206,8 +206,8 @@ SchemaSubdocument.prototype.cast = function(val, doc, init, priorVal, options) {
206
206
  // For change tracking, subdocuments use relative paths internally.
207
207
  // Here, `options.path` contains the absolute path and is only used by the subdocument constructor, not by $init.
208
208
  if (options.path != null) {
209
- options = { ...options };
210
- delete options.path;
209
+ const { path, ...initOptions } = options;
210
+ options = initOptions;
211
211
  }
212
212
  subdoc.$init(val, options);
213
213
  const exclude = isExclusive(selected);
package/lib/schema.js CHANGED
@@ -2280,6 +2280,37 @@ Schema.prototype.plugin = function(fn, opts) {
2280
2280
  return this;
2281
2281
  };
2282
2282
 
2283
+ /**
2284
+ * Adds a query helper to this schema. Equivalent to `schema.query[name] = fn`.
2285
+ *
2286
+ * #### Example:
2287
+ *
2288
+ * schema.queryHelper('byName', function(name) {
2289
+ * return this.where({ name });
2290
+ * });
2291
+ * const Test = mongoose.model('Test', schema);
2292
+ * await Test.find().byName('John'); // Equivalent to `Test.find({ name: 'John' })`
2293
+ *
2294
+ * @param {string} name The query helper name.
2295
+ * @param {Function} fn The query helper function.
2296
+ * @return {Schema} this
2297
+ * @api public
2298
+ */
2299
+
2300
+ Schema.prototype.queryHelper = function queryHelper(name, fn) {
2301
+ if (typeof name !== 'string') {
2302
+ throw new MongooseError('First param to `schema.queryHelper()` must be a string, ' +
2303
+ 'got "' + (typeof name) + '"');
2304
+ }
2305
+ if (typeof fn !== 'function') {
2306
+ throw new MongooseError('Second param to `schema.queryHelper()` must be a function, ' +
2307
+ 'got "' + (typeof fn) + '"');
2308
+ }
2309
+
2310
+ this.query[name] = fn;
2311
+ return this;
2312
+ };
2313
+
2283
2314
  /**
2284
2315
  * Adds an instance method to documents constructed from Models compiled from this schema.
2285
2316
  *
@@ -2310,9 +2341,21 @@ Schema.prototype.plugin = function(fn, opts) {
2310
2341
  *
2311
2342
  * NOTE: `Schema.method()` adds instance methods to the `Schema.methods` object. You can also add instance methods directly to the `Schema.methods` object as seen in the [guide](https://mongoosejs.com/docs/guide.html#methods)
2312
2343
  *
2344
+ * If the method has hooks registered via `schema.pre('meow')` or `schema.post('meow')`, you can allow callers
2345
+ * to skip them for a single call by setting `supportsMiddlewareOption = true` on the function:
2346
+ *
2347
+ * schema.method('meow', function(options = {}) {
2348
+ * console.log('meeeeeoooooooooooow');
2349
+ * });
2350
+ * schema.methods.meow.supportsMiddlewareOption = true;
2351
+ *
2352
+ * // Skips `pre('meow')` and `post('meow')` hooks
2353
+ * await fizz.meow({ middleware: false });
2354
+ *
2313
2355
  * @param {string|object} name The Method Name for a single function, or an Object of "string-function" pairs.
2314
2356
  * @param {Function} [fn] The Function in a single-function definition.
2315
2357
  * @api public
2358
+ * @see Skip Middleware https://mongoosejs.com/docs/middleware.html#skip-custom-statics-and-methods
2316
2359
  */
2317
2360
 
2318
2361
  Schema.prototype.method = function(name, fn, options) {
@@ -2355,10 +2398,22 @@ Schema.prototype.method = function(name, fn, options) {
2355
2398
  *
2356
2399
  * If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
2357
2400
  *
2401
+ * If the static has hooks registered via `schema.pre('findByName')` or `schema.post('findByName')`, you can allow
2402
+ * callers to skip them for a single call by setting `supportsMiddlewareOption = true` on the function:
2403
+ *
2404
+ * schema.static('findByName', function(name, options = {}) {
2405
+ * return this.find({ name: name });
2406
+ * });
2407
+ * schema.statics.findByName.supportsMiddlewareOption = true;
2408
+ *
2409
+ * // Skips `pre('findByName')` and `post('findByName')` hooks
2410
+ * await Drink.findByName('LaCroix', { middleware: false });
2411
+ *
2358
2412
  * @param {string|object} name The Method Name for a single function, or an Object of "string-function" pairs.
2359
2413
  * @param {Function} [fn] The Function in a single-function definition.
2360
2414
  * @api public
2361
2415
  * @see Statics https://mongoosejs.com/docs/guide.html#statics
2416
+ * @see Skip Middleware https://mongoosejs.com/docs/middleware.html#skip-custom-statics-and-methods
2362
2417
  */
2363
2418
 
2364
2419
  Schema.prototype.static = function(name, fn) {
package/lib/schemaType.js CHANGED
@@ -176,8 +176,7 @@ SchemaType.prototype.path;
176
176
  */
177
177
 
178
178
  SchemaType.prototype.toJSON = function toJSON() {
179
- const res = { ...this };
180
- delete res.parentSchema;
179
+ const { parentSchema, ...res } = this;
181
180
  return res;
182
181
  };
183
182
 
@@ -1299,11 +1298,12 @@ SchemaType.prototype.ref = function(ref) {
1299
1298
  * @param {object} options
1300
1299
  * @param {object} [options.context]
1301
1300
  * @param {boolean} [options.skipCast]
1301
+ * @param {object} [applySettersOptions] passed to applySetters
1302
1302
  * @return {any} The Stored default value.
1303
1303
  * @api private
1304
1304
  */
1305
1305
 
1306
- SchemaType.prototype.getDefault = function getDefault(parentDoc, init, options) {
1306
+ SchemaType.prototype.getDefault = function getDefault(parentDoc, init, options, applySettersOptions) {
1307
1307
  let ret;
1308
1308
  if (this.defaultValue == null) {
1309
1309
  return this.defaultValue;
@@ -1337,7 +1337,13 @@ SchemaType.prototype.getDefault = function getDefault(parentDoc, init, options)
1337
1337
  return this._applySetters(ret, parentDoc);
1338
1338
  }
1339
1339
 
1340
- const casted = this.applySetters(ret, parentDoc, init, undefined, setOptionsForDefaults);
1340
+ const casted = this.applySetters(
1341
+ ret,
1342
+ parentDoc,
1343
+ init,
1344
+ undefined,
1345
+ applySettersOptions ?? setOptionsForDefaults
1346
+ );
1341
1347
  if (casted && !Array.isArray(casted) && casted.$isSingleNested) {
1342
1348
  casted.$__parent = parentDoc;
1343
1349
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "9.9.5",
4
+ "version": "9.10.0",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -21,8 +21,8 @@
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
23
  "@standard-schema/spec": "^1.1.0",
24
- "kareem": "3.3.0",
25
- "mongodb": "~7.5",
24
+ "kareem": "3.4.0",
25
+ "mongodb": "~7.6",
26
26
  "mpath": "0.9.0",
27
27
  "mquery": "6.0.0",
28
28
  "ms": "2.1.3",
@@ -53,7 +53,7 @@
53
53
  "markdownlint-cli2": "0.23.2",
54
54
  "marked": "18.0.11",
55
55
  "mkdirp": "^3.0.1",
56
- "mocha": "12.0.0-rc.5",
56
+ "mocha": "12.0.0-rc.6",
57
57
  "moment": "2.30.1",
58
58
  "mongodb-client-encryption": "^7.2.0",
59
59
  "mongodb-memory-server": "11.2.0",
@@ -10,6 +10,19 @@ declare module 'mongoose' {
10
10
  [key: string]: any;
11
11
  }
12
12
 
13
+ interface AggregateCursorMiddlewareOptions {
14
+ /** If `false`, skip pre aggregate middleware. Aggregate cursors do not run post aggregate middleware. */
15
+ pre?: boolean;
16
+ post?: never;
17
+ }
18
+
19
+ interface AggregateCursorOptions extends Omit<mongodb.AggregationCursorOptions & mongodb.Abortable, 'session'>, SessionOption {
20
+ middleware?: boolean | AggregateCursorMiddlewareOptions;
21
+ transform?: (doc: any) => any;
22
+ useMongooseAggCursor?: boolean;
23
+ [key: string]: unknown;
24
+ }
25
+
13
26
  class Aggregate<ResultType> implements SessionOperation {
14
27
  /**
15
28
  * Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js)
@@ -62,7 +75,7 @@ declare module 'mongoose' {
62
75
  /**
63
76
  * Sets the cursor option for the aggregation query
64
77
  */
65
- cursor<DocType = any>(options?: Record<string, unknown>): Cursor<DocType>;
78
+ cursor<DocType = any>(options?: AggregateCursorOptions): Cursor<DocType>;
66
79
 
67
80
 
68
81
  /** Executes the aggregate pipeline on the currently bound Model. */
@@ -96,7 +96,7 @@ declare module 'mongoose' {
96
96
 
97
97
  /** Returns the model with the given name on this document's associated connection. */
98
98
  $model<ModelType extends Model<unknown>>(name: string): ModelType;
99
- $model<ModelType extends Model<DocType>>(): ModelType;
99
+ $model<ModelType extends Model<DocType, any, any, any>>(): ModelType;
100
100
 
101
101
  /**
102
102
  * A string containing the current operation that Mongoose is executing
@@ -232,7 +232,7 @@ declare module 'mongoose' {
232
232
 
233
233
  /** Returns the model with the given name on this document's associated connection. */
234
234
  model<ModelType extends Model<unknown>>(name: string): ModelType;
235
- model<ModelType extends Model<DocType>>(): ModelType;
235
+ model<ModelType extends Model<DocType, any, any, any>>(): ModelType;
236
236
 
237
237
  /** Returns the list of paths that have been modified. */
238
238
  modifiedPaths(options?: { includeChildren?: boolean }): Array<string>;
package/types/index.d.ts CHANGED
@@ -493,6 +493,24 @@ declare module 'mongoose' {
493
493
  method<Context = THydratedDocumentType>(name: string, fn: (this: Context, ...args: any[]) => any, opts?: any): this;
494
494
  method(obj: Partial<TInstanceMethods>): this;
495
495
 
496
+ /** Adds a query helper to this schema. */
497
+ queryHelper<Name extends string, Fn extends (this: QueryWithHelpers<any, DocType, TQueryHelpers, RawDocType>, ...args: any[]) => any>(
498
+ name: Name,
499
+ fn: Fn
500
+ ): Schema<
501
+ RawDocType,
502
+ TModelType,
503
+ TInstanceMethods,
504
+ TQueryHelpers & { [K in Name]: Fn },
505
+ TVirtuals,
506
+ TStaticMethods,
507
+ TSchemaOptions,
508
+ DocType,
509
+ THydratedDocumentType,
510
+ TSchemaDefinition,
511
+ LeanResultType
512
+ >;
513
+
496
514
  /** Object of currently defined methods on this schema. */
497
515
  methods: AddThisParameter<TInstanceMethods, THydratedDocumentType> & AnyObject;
498
516
 
@@ -667,12 +685,41 @@ declare module 'mongoose' {
667
685
 
668
686
  /** Adds static "class" methods to Models compiled from this schema. */
669
687
  static<K extends keyof TStaticMethods>(name: K, fn: TStaticMethods[K]): this;
670
- static(obj: Partial<TStaticMethods> & { [name: string]: (this: TModelType, ...args: any[]) => any }): this;
671
- static(name: string, fn: (this: TModelType, ...args: any[]) => any): this;
688
+ static<Fns extends Partial<TStaticMethods> & { [name: string]: (this: TModelType, ...args: any[]) => any }>(
689
+ obj: Fns
690
+ ): Schema<
691
+ RawDocType,
692
+ TModelType,
693
+ TInstanceMethods,
694
+ TQueryHelpers,
695
+ TVirtuals,
696
+ TStaticMethods & Fns,
697
+ TSchemaOptions,
698
+ DocType,
699
+ THydratedDocumentType,
700
+ TSchemaDefinition,
701
+ LeanResultType
702
+ >;
703
+ static<Name extends string, Fn extends (this: TModelType, ...args: any[]) => any>(
704
+ name: Name,
705
+ fn: Fn
706
+ ): Schema<
707
+ RawDocType,
708
+ TModelType,
709
+ TInstanceMethods,
710
+ TQueryHelpers,
711
+ TVirtuals,
712
+ TStaticMethods & { [K in Name]: Fn },
713
+ TSchemaOptions,
714
+ DocType,
715
+ THydratedDocumentType,
716
+ TSchemaDefinition,
717
+ LeanResultType
718
+ >;
672
719
 
673
720
  /** Object of currently defined statics on this schema. */
674
721
  statics: { [F in keyof TStaticMethods]: TStaticMethods[F] } &
675
- { [name: string]: (this: TModelType, ...args: any[]) => unknown };
722
+ { [name: string]: ((this: TModelType, ...args: any[]) => unknown) & SupportsMiddlewareOption };
676
723
 
677
724
  toJSONSchema(options?: { useBsonType?: boolean }): Record<string, any>;
678
725
 
@@ -825,6 +872,7 @@ declare module 'mongoose' {
825
872
  export type ReturnsNewDoc = { new: true } | { returnOriginal: false } | { returnDocument: 'after' };
826
873
 
827
874
  export type ArrayProjectionOperators = { $slice: number | [number, number]; $elemMatch?: never } | { $elemMatch: Record<string, any>; $slice?: never };
875
+ export type ProjectionOperators = { $meta: string };
828
876
  /**
829
877
  * This Type Assigns `Element | undefined` recursively to the `T` type.
830
878
  * if it is an array it will do this to the element of the array, if it is an object it will do this for the properties of the object.
@@ -842,14 +890,14 @@ declare module 'mongoose' {
842
890
  }
843
891
  */
844
892
  export type Projector<T, Element> = T extends Array<infer U>
845
- ? Projector<U, Element> | ArrayProjectionOperators
893
+ ? Projector<U, Element> | ArrayProjectionOperators | ProjectionOperators
846
894
  : T extends TreatAsPrimitives
847
- ? Element
895
+ ? Element | ProjectionOperators
848
896
  : T extends Record<string, any>
849
897
  ? {
850
- [K in keyof T]?: T[K] extends Record<string, any> ? Projector<T[K], Element> | Element : Element;
898
+ [K in keyof T]?: T[K] extends Record<string, any> ? Projector<T[K], Element> | Element | ProjectionOperators : Element | ProjectionOperators;
851
899
  }
852
- : Element;
900
+ : Element | ProjectionOperators;
853
901
  type _IDType = { _id?: boolean | number };
854
902
  export type InclusionProjection<T> = IsItRecordAndNotAny<T> extends true
855
903
  ? Omit<Projector<WithLevel1NestedPaths<T>, boolean | number>, '_id'> & _IDType
@@ -1181,7 +1229,7 @@ declare module 'mongoose' {
1181
1229
  /* for ts-mongoose */
1182
1230
  export class mquery { }
1183
1231
 
1184
- export function overwriteMiddlewareResult(val: any): Kareem.OverwriteMiddlewareResult;
1232
+ export function overwriteMiddlewareResult(val: any): Kareem.OverwriteResult;
1185
1233
 
1186
1234
  export function skipMiddlewareFunction(val: any): Kareem.SkipWrappedFunction;
1187
1235
 
@@ -41,6 +41,15 @@ declare module 'mongoose' {
41
41
  post?: boolean;
42
42
  }
43
43
 
44
+ interface SupportsMiddlewareOption {
45
+ /**
46
+ * Set to `true` to let callers skip this custom static's or method's middleware
47
+ * by passing `{ middleware: false }` as the last argument.
48
+ * See [skipping middleware for custom statics and methods](https://mongoosejs.com/docs/middleware.html#skip-custom-statics-and-methods).
49
+ */
50
+ supportsMiddlewareOption?: boolean;
51
+ }
52
+
44
53
  type PreMiddlewareFunction<ThisType = any> = (
45
54
  this: ThisType,
46
55
  opts?: Record<string, any>
@@ -60,7 +69,7 @@ declare module 'mongoose' {
60
69
  this: ThisType,
61
70
  opts: SaveOptions
62
71
  ) => void | Promise<void> | Kareem.SkipWrappedFunction;
63
- type PostMiddlewareFunction<ThisType = any, ResType = any> = (this: ThisType, res: ResType, next: CallbackWithoutResultAndOptionalError) => void | Promise<void> | Kareem.OverwriteMiddlewareResult;
72
+ type PostMiddlewareFunction<ThisType = any, ResType = any> = (this: ThisType, res: ResType, next: CallbackWithoutResultAndOptionalError) => void | Promise<void> | Kareem.OverwriteResult;
64
73
  type ErrorHandlingMiddlewareFunction<ThisType = any, ResType = any> = (this: ThisType, err: NativeError, res: ResType, next: CallbackWithoutResultAndOptionalError) => void;
65
- type ErrorHandlingMiddlewareWithOption<ThisType = any, ResType = any> = (this: ThisType, err: NativeError, res: ResType | null, next: CallbackWithoutResultAndOptionalError) => void | Promise<void> | Kareem.OverwriteMiddlewareResult;
74
+ type ErrorHandlingMiddlewareWithOption<ThisType = any, ResType = any> = (this: ThisType, err: NativeError, res: ResType | null, next: CallbackWithoutResultAndOptionalError) => void | Promise<void> | Kareem.OverwriteResult;
66
75
  }
package/types/models.d.ts CHANGED
@@ -445,6 +445,18 @@ declare module 'mongoose' {
445
445
  * equivalent to `findOne({ _id: id })`. If you want to query by a document's
446
446
  * `_id`, use `findById()` instead of `findOne()`.
447
447
  */
448
+ findById<const Projection extends ProjectionType<TRawDocType>>(
449
+ id: any,
450
+ projection: Projection,
451
+ options: QueryOptions<TRawDocType> & { lean: true }
452
+ ): QueryWithHelpers<
453
+ ApplyProjection<TRawDocType, Projection> | null,
454
+ THydratedDocumentType,
455
+ TQueryHelpers,
456
+ TLeanResultType,
457
+ 'findOne',
458
+ TInstanceMethods & TVirtuals
459
+ >;
448
460
  findById<ResultDoc = THydratedDocumentType>(
449
461
  id: any,
450
462
  projection: ProjectionType<TRawDocType> | null | undefined,
@@ -483,6 +495,54 @@ declare module 'mongoose' {
483
495
  >;
484
496
 
485
497
  /** Finds one document. */
498
+ findOne<const Projection extends ProjectionType<TRawDocType>>(
499
+ filter: QueryFilter<TRawDocType>,
500
+ projection: Projection,
501
+ options?: QueryOptions<TRawDocType> & { lean?: false } & mongodb.Abortable
502
+ ): QueryWithHelpers<
503
+ ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals> | null,
504
+ THydratedDocumentType,
505
+ TQueryHelpers,
506
+ TLeanResultType,
507
+ 'findOne',
508
+ TInstanceMethods & TVirtuals
509
+ >;
510
+ findOne<const Projection extends ProjectionType<TRawDocType>>(
511
+ filter: QueryFilter<TRawDocType>,
512
+ projection: undefined | null,
513
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean?: false } & mongodb.Abortable
514
+ ): QueryWithHelpers<
515
+ ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals> | null,
516
+ THydratedDocumentType,
517
+ TQueryHelpers,
518
+ TLeanResultType,
519
+ 'findOne',
520
+ TInstanceMethods & TVirtuals
521
+ >;
522
+ findOne<const Projection extends ProjectionType<TRawDocType>>(
523
+ filter: QueryFilter<TRawDocType>,
524
+ projection: Projection,
525
+ options: QueryOptions<TRawDocType> & { lean: true } & mongodb.Abortable
526
+ ): QueryWithHelpers<
527
+ ApplyProjection<TRawDocType, Projection> | null,
528
+ THydratedDocumentType,
529
+ TQueryHelpers,
530
+ TLeanResultType,
531
+ 'findOne',
532
+ TInstanceMethods & TVirtuals
533
+ >;
534
+ findOne<const Projection extends ProjectionType<TRawDocType>>(
535
+ filter: QueryFilter<TRawDocType>,
536
+ projection: undefined | null,
537
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true } & mongodb.Abortable
538
+ ): QueryWithHelpers<
539
+ ApplyProjection<TRawDocType, Projection> | null,
540
+ THydratedDocumentType,
541
+ TQueryHelpers,
542
+ TLeanResultType,
543
+ 'findOne',
544
+ TInstanceMethods & TVirtuals
545
+ >;
486
546
  findOne<ResultDoc = THydratedDocumentType>(
487
547
  filter: QueryFilter<TRawDocType>,
488
548
  projection: ProjectionType<TRawDocType> | null | undefined,
@@ -780,6 +840,54 @@ declare module 'mongoose' {
780
840
  >;
781
841
 
782
842
  /** Creates a `find` query: gets a list of documents that match `filter`. */
843
+ find<const Projection extends ProjectionType<TRawDocType>>(
844
+ filter: QueryFilter<TRawDocType>,
845
+ projection: Projection,
846
+ options?: QueryOptions<TRawDocType> & { lean?: false } & mongodb.Abortable
847
+ ): QueryWithHelpers<
848
+ ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals>[],
849
+ THydratedDocumentType,
850
+ TQueryHelpers,
851
+ TLeanResultType,
852
+ 'find',
853
+ TInstanceMethods & TVirtuals
854
+ >;
855
+ find<const Projection extends ProjectionType<TRawDocType>>(
856
+ filter: QueryFilter<TRawDocType>,
857
+ projection: undefined | null,
858
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean?: false } & mongodb.Abortable
859
+ ): QueryWithHelpers<
860
+ ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals>[],
861
+ THydratedDocumentType,
862
+ TQueryHelpers,
863
+ TLeanResultType,
864
+ 'find',
865
+ TInstanceMethods & TVirtuals
866
+ >;
867
+ find<const Projection extends ProjectionType<TRawDocType>>(
868
+ filter: QueryFilter<TRawDocType>,
869
+ projection: Projection,
870
+ options: QueryOptions<TRawDocType> & { lean: true } & mongodb.Abortable
871
+ ): QueryWithHelpers<
872
+ ApplyProjection<TRawDocType, Projection>[],
873
+ THydratedDocumentType,
874
+ TQueryHelpers,
875
+ TLeanResultType,
876
+ 'find',
877
+ TInstanceMethods & TVirtuals
878
+ >;
879
+ find<const Projection extends ProjectionType<TRawDocType>>(
880
+ filter: QueryFilter<TRawDocType>,
881
+ projection: undefined | null,
882
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true } & mongodb.Abortable
883
+ ): QueryWithHelpers<
884
+ ApplyProjection<TRawDocType, Projection>[],
885
+ THydratedDocumentType,
886
+ TQueryHelpers,
887
+ TLeanResultType,
888
+ 'find',
889
+ TInstanceMethods & TVirtuals
890
+ >;
783
891
  find<ResultDoc = THydratedDocumentType>(
784
892
  filter: QueryFilter<TRawDocType>,
785
893
  projection: ProjectionType<TRawDocType> | null | undefined,
@@ -841,7 +949,51 @@ declare module 'mongoose' {
841
949
  TInstanceMethods & TVirtuals
842
950
  >;
843
951
 
952
+ /** Finds documents and counts the number of documents matching `filter`. */
953
+ findAndCount<const Projection extends ProjectionType<TRawDocType>>(
954
+ filter: QueryFilter<TRawDocType>,
955
+ projection: Projection,
956
+ options: QueryOptions<TRawDocType> & { sort: any; limit: number; lean: true } & mongodb.Abortable
957
+ ): Promise<[ApplyProjection<TRawDocType, Projection>[], number]>;
958
+ findAndCount<ResultDoc = THydratedDocumentType>(
959
+ filter: QueryFilter<TRawDocType>,
960
+ projection: ProjectionType<TRawDocType> | null | undefined,
961
+ options: QueryOptions<TRawDocType> & { sort: any; limit: number; lean: true } & mongodb.Abortable
962
+ ): Promise<[GetLeanResultType<TRawDocType, TRawDocType[], 'find'>, number]>;
963
+ findAndCount<ResultDoc = THydratedDocumentType>(
964
+ filter: QueryFilter<TRawDocType>,
965
+ projection: ProjectionType<TRawDocType> | null | undefined,
966
+ options: QueryOptions<TRawDocType> & { sort: any; limit: number; lean: false } & mongodb.Abortable
967
+ ): Promise<[ResultDoc[], number]>;
968
+ findAndCount<ResultDoc = THydratedDocumentType>(
969
+ filter: QueryFilter<TRawDocType>,
970
+ projection: ProjectionType<TRawDocType> | null | undefined,
971
+ options: QueryOptions<TRawDocType> & { sort: any; limit: number } & mongodb.Abortable
972
+ ): Promise<[HasLeanOption<TSchema> extends true ? TLeanResultType[] : ResultDoc[], number]>;
973
+
844
974
  /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */
975
+ findByIdAndDelete<const Projection extends ProjectionType<TRawDocType>>(
976
+ id: mongodb.ObjectId | any,
977
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata?: false }
978
+ ): QueryWithHelpers<
979
+ ApplyProjection<TRawDocType, Projection> | null,
980
+ THydratedDocumentType,
981
+ TQueryHelpers,
982
+ TLeanResultType,
983
+ 'findOneAndDelete',
984
+ TInstanceMethods & TVirtuals
985
+ >;
986
+ findByIdAndDelete<const Projection extends ProjectionType<TRawDocType>>(
987
+ id: mongodb.ObjectId | any,
988
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata: true }
989
+ ): QueryWithHelpers<
990
+ ModifyResult<ApplyProjection<TRawDocType, Projection>>,
991
+ THydratedDocumentType,
992
+ TQueryHelpers,
993
+ TLeanResultType,
994
+ 'findOneAndDelete',
995
+ TInstanceMethods & TVirtuals
996
+ >;
845
997
  findByIdAndDelete<ResultDoc = THydratedDocumentType>(
846
998
  id: mongodb.ObjectId | any,
847
999
  options: QueryOptions<TRawDocType> & { includeResultMetadata: true, lean: true }
@@ -900,6 +1052,30 @@ declare module 'mongoose' {
900
1052
 
901
1053
 
902
1054
  /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */
1055
+ findByIdAndUpdate<const Projection extends ProjectionType<TRawDocType>>(
1056
+ id: mongodb.ObjectId | any,
1057
+ update: UpdateQuery<TRawDocType>,
1058
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata?: false }
1059
+ ): QueryWithHelpers<
1060
+ ApplyProjection<TRawDocType, Projection> | null,
1061
+ THydratedDocumentType,
1062
+ TQueryHelpers,
1063
+ TLeanResultType,
1064
+ 'findOneAndUpdate',
1065
+ TInstanceMethods & TVirtuals
1066
+ >;
1067
+ findByIdAndUpdate<const Projection extends ProjectionType<TRawDocType>>(
1068
+ id: mongodb.ObjectId | any,
1069
+ update: UpdateQuery<TRawDocType>,
1070
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata: true }
1071
+ ): QueryWithHelpers<
1072
+ ModifyResult<ApplyProjection<TRawDocType, Projection>>,
1073
+ THydratedDocumentType,
1074
+ TQueryHelpers,
1075
+ TLeanResultType,
1076
+ 'findOneAndUpdate',
1077
+ TInstanceMethods & TVirtuals
1078
+ >;
903
1079
  findByIdAndUpdate<ResultDoc = THydratedDocumentType>(
904
1080
  filter: QueryFilter<TRawDocType>,
905
1081
  update: UpdateQuery<TRawDocType>,
@@ -986,6 +1162,50 @@ declare module 'mongoose' {
986
1162
  >;
987
1163
 
988
1164
  /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */
1165
+ findOneAndDelete<const Projection extends ProjectionType<TRawDocType>>(
1166
+ filter: QueryFilter<TRawDocType>,
1167
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean?: false; includeResultMetadata?: false }
1168
+ ): QueryWithHelpers<
1169
+ ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals> | null,
1170
+ THydratedDocumentType,
1171
+ TQueryHelpers,
1172
+ TLeanResultType,
1173
+ 'findOneAndDelete',
1174
+ TInstanceMethods & TVirtuals
1175
+ >;
1176
+ findOneAndDelete<const Projection extends ProjectionType<TRawDocType>>(
1177
+ filter: QueryFilter<TRawDocType>,
1178
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean?: false; includeResultMetadata: true }
1179
+ ): QueryWithHelpers<
1180
+ ModifyResult<ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals>>,
1181
+ THydratedDocumentType,
1182
+ TQueryHelpers,
1183
+ TLeanResultType,
1184
+ 'findOneAndDelete',
1185
+ TInstanceMethods & TVirtuals
1186
+ >;
1187
+ findOneAndDelete<const Projection extends ProjectionType<TRawDocType>>(
1188
+ filter: QueryFilter<TRawDocType>,
1189
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata?: false }
1190
+ ): QueryWithHelpers<
1191
+ ApplyProjection<TRawDocType, Projection> | null,
1192
+ THydratedDocumentType,
1193
+ TQueryHelpers,
1194
+ TLeanResultType,
1195
+ 'findOneAndDelete',
1196
+ TInstanceMethods & TVirtuals
1197
+ >;
1198
+ findOneAndDelete<const Projection extends ProjectionType<TRawDocType>>(
1199
+ filter: QueryFilter<TRawDocType>,
1200
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata: true }
1201
+ ): QueryWithHelpers<
1202
+ ModifyResult<ApplyProjection<TRawDocType, Projection>>,
1203
+ THydratedDocumentType,
1204
+ TQueryHelpers,
1205
+ TLeanResultType,
1206
+ 'findOneAndDelete',
1207
+ TInstanceMethods & TVirtuals
1208
+ >;
989
1209
  findOneAndDelete<ResultDoc = THydratedDocumentType>(
990
1210
  filter: QueryFilter<TRawDocType>,
991
1211
  options: QueryOptions<TRawDocType> & { lean: true }
@@ -1076,6 +1296,54 @@ declare module 'mongoose' {
1076
1296
  >;
1077
1297
 
1078
1298
  /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */
1299
+ findOneAndReplace<const Projection extends ProjectionType<TRawDocType>>(
1300
+ filter: QueryFilter<TRawDocType>,
1301
+ replacement: TRawDocType | AnyObject,
1302
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean?: false; includeResultMetadata?: false }
1303
+ ): QueryWithHelpers<
1304
+ ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals> | null,
1305
+ THydratedDocumentType,
1306
+ TQueryHelpers,
1307
+ TLeanResultType,
1308
+ 'findOneAndReplace',
1309
+ TInstanceMethods & TVirtuals
1310
+ >;
1311
+ findOneAndReplace<const Projection extends ProjectionType<TRawDocType>>(
1312
+ filter: QueryFilter<TRawDocType>,
1313
+ replacement: TRawDocType | AnyObject,
1314
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean?: false; includeResultMetadata: true }
1315
+ ): QueryWithHelpers<
1316
+ ModifyResult<ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals>>,
1317
+ THydratedDocumentType,
1318
+ TQueryHelpers,
1319
+ TLeanResultType,
1320
+ 'findOneAndReplace',
1321
+ TInstanceMethods & TVirtuals
1322
+ >;
1323
+ findOneAndReplace<const Projection extends ProjectionType<TRawDocType>>(
1324
+ filter: QueryFilter<TRawDocType>,
1325
+ replacement: TRawDocType | AnyObject,
1326
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata?: false }
1327
+ ): QueryWithHelpers<
1328
+ ApplyProjection<TRawDocType, Projection> | null,
1329
+ THydratedDocumentType,
1330
+ TQueryHelpers,
1331
+ TLeanResultType,
1332
+ 'findOneAndReplace',
1333
+ TInstanceMethods & TVirtuals
1334
+ >;
1335
+ findOneAndReplace<const Projection extends ProjectionType<TRawDocType>>(
1336
+ filter: QueryFilter<TRawDocType>,
1337
+ replacement: TRawDocType | AnyObject,
1338
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata: true }
1339
+ ): QueryWithHelpers<
1340
+ ModifyResult<ApplyProjection<TRawDocType, Projection>>,
1341
+ THydratedDocumentType,
1342
+ TQueryHelpers,
1343
+ TLeanResultType,
1344
+ 'findOneAndReplace',
1345
+ TInstanceMethods & TVirtuals
1346
+ >;
1079
1347
  findOneAndReplace<ResultDoc = THydratedDocumentType>(
1080
1348
  filter: QueryFilter<TRawDocType>,
1081
1349
  replacement: TRawDocType | AnyObject,
@@ -1198,6 +1466,54 @@ declare module 'mongoose' {
1198
1466
  >;
1199
1467
 
1200
1468
  /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */
1469
+ findOneAndUpdate<const Projection extends ProjectionType<TRawDocType>>(
1470
+ filter: QueryFilter<TRawDocType>,
1471
+ update: UpdateQuery<TRawDocType>,
1472
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean?: false; includeResultMetadata?: false }
1473
+ ): QueryWithHelpers<
1474
+ ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals> | null,
1475
+ THydratedDocumentType,
1476
+ TQueryHelpers,
1477
+ TLeanResultType,
1478
+ 'findOneAndUpdate',
1479
+ TInstanceMethods & TVirtuals
1480
+ >;
1481
+ findOneAndUpdate<const Projection extends ProjectionType<TRawDocType>>(
1482
+ filter: QueryFilter<TRawDocType>,
1483
+ update: UpdateQuery<TRawDocType>,
1484
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean?: false; includeResultMetadata: true }
1485
+ ): QueryWithHelpers<
1486
+ ModifyResult<ProjectedHydratedDocument<TRawDocType, Projection, TInstanceMethods, TQueryHelpers, TVirtuals>>,
1487
+ THydratedDocumentType,
1488
+ TQueryHelpers,
1489
+ TLeanResultType,
1490
+ 'findOneAndUpdate',
1491
+ TInstanceMethods & TVirtuals
1492
+ >;
1493
+ findOneAndUpdate<const Projection extends ProjectionType<TRawDocType>>(
1494
+ filter: QueryFilter<TRawDocType>,
1495
+ update: UpdateQuery<TRawDocType>,
1496
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata?: false }
1497
+ ): QueryWithHelpers<
1498
+ ApplyProjection<TRawDocType, Projection> | null,
1499
+ THydratedDocumentType,
1500
+ TQueryHelpers,
1501
+ TLeanResultType,
1502
+ 'findOneAndUpdate',
1503
+ TInstanceMethods & TVirtuals
1504
+ >;
1505
+ findOneAndUpdate<const Projection extends ProjectionType<TRawDocType>>(
1506
+ filter: QueryFilter<TRawDocType>,
1507
+ update: UpdateQuery<TRawDocType>,
1508
+ options: QueryOptions<TRawDocType> & { projection: Projection; lean: true; includeResultMetadata: true }
1509
+ ): QueryWithHelpers<
1510
+ ModifyResult<ApplyProjection<TRawDocType, Projection>>,
1511
+ THydratedDocumentType,
1512
+ TQueryHelpers,
1513
+ TLeanResultType,
1514
+ 'findOneAndUpdate',
1515
+ TInstanceMethods & TVirtuals
1516
+ >;
1201
1517
  findOneAndUpdate<ResultDoc = THydratedDocumentType>(
1202
1518
  filter: QueryFilter<TRawDocType>,
1203
1519
  update: UpdateQuery<TRawDocType>,
@@ -1,4 +1,44 @@
1
1
  declare module 'mongoose' {
2
+ type IsNonDefiningProjection<Value, Key, Projection> = Value extends { $slice: any } | { $meta: any }
3
+ ? true
4
+ : Key extends '_id'
5
+ ? Value extends 0 | false
6
+ ? false
7
+ // Including `_id` only defines inclusion when it is the sole projected field.
8
+ : Exclude<keyof Projection, '_id'> extends never ? false : true
9
+ : false;
10
+ type ProjectionPath<Key> = Key extends `${infer Parent}.$` ? Parent : Key;
11
+ type DefiningProjectionKeys<Projection> = {
12
+ [Key in keyof Projection]-?: Key extends string
13
+ ? IsNonDefiningProjection<Projection[Key], Key, Projection> extends true ? never : ProjectionPath<Key>
14
+ : never
15
+ }[keyof Projection];
16
+ type DefiningProjectionValues<Projection> = {
17
+ [Key in keyof Projection]-?: IsNonDefiningProjection<Projection[Key], Key, Projection> extends true ? never : Projection[Key]
18
+ }[keyof Projection];
19
+
20
+ export type ApplyProjection<T, Projection> = Projection extends string
21
+ ? T
22
+ : Projection extends AnyObject
23
+ ? [DefiningProjectionKeys<Projection>] extends [never]
24
+ ? T
25
+ : Exclude<DefiningProjectionValues<Projection>, 0 | false | undefined> extends never
26
+ ? Omit<T, Extract<DefiningProjectionKeys<Projection>, keyof T>>
27
+ : Pick<T, Extract<DefiningProjectionKeys<Projection>, keyof T>> &
28
+ (Projection extends { _id?: infer Id }
29
+ ? Id extends 0 | false
30
+ ? unknown
31
+ : Pick<T, Extract<'_id', keyof T>>
32
+ : Pick<T, Extract<'_id', keyof T>>)
33
+ : T;
34
+
35
+ export type ProjectedHydratedDocument<RawDocType, Projection, TInstanceMethods = {}, TQueryHelpers = {}, TVirtuals = {}> =
36
+ Projection extends { _id?: infer Id }
37
+ ? Id extends 0 | false
38
+ ? Omit<HydratedDocument<ApplyProjection<RawDocType, Projection>, TInstanceMethods, TQueryHelpers, TVirtuals>, '_id'>
39
+ : HydratedDocument<ApplyProjection<RawDocType, Projection>, TInstanceMethods, TQueryHelpers, TVirtuals>
40
+ : HydratedDocument<ApplyProjection<RawDocType, Projection>, TInstanceMethods, TQueryHelpers, TVirtuals>;
41
+
2
42
  type IfAny<IFTYPE, THENTYPE, ELSETYPE = IFTYPE> = 0 extends 1 & IFTYPE
3
43
  ? THENTYPE
4
44
  : ELSETYPE;