mongoose 9.6.2 → 9.7.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/README.md CHANGED
@@ -96,6 +96,10 @@ You can then run the above script using the following.
96
96
  deno run --allow-net --allow-read --allow-sys --allow-env mongoose-test.js
97
97
  ```
98
98
 
99
+ ## Mongoose Studio
100
+
101
+ [Mongoose Studio](https://mongoosestudio.app?utm_source=mongoose&utm_medium=referral&utm_campaign=readme) is a free, fully open-source, browser-based MongoDB GUI built by the Mongoose team for apps that already use Mongoose. Install `@mongoosejs/studio` from npm and run it alongside your app as Express middleware, or deploy it on Vercel or Netlify, to browse and edit documents, query with your existing models and schemas with autocomplete, build dashboards, visualize and edit GeoJSON, and use AI-assisted MongoDB workflows without moving data into a hosted third-party workspace or sharing raw MongoDB connection strings.
102
+
99
103
  ## Mongoose for Enterprise
100
104
 
101
105
  Available as part of the Tidelift Subscription
@@ -172,8 +176,6 @@ Comment.pre('save', function(next) {
172
176
  });
173
177
  ```
174
178
 
175
- Take a look at the example in [`examples/schema/schema.js`](https://github.com/Automattic/mongoose/blob/master/examples/schema/schema.js) for an end-to-end example of a typical setup.
176
-
177
179
  ### Accessing a Model
178
180
 
179
181
  Once we define a model through `mongoose.model('ModelName', mySchema)`, we can access it through the same function
package/lib/aggregate.js CHANGED
@@ -15,6 +15,8 @@ const { buildMiddlewareFilter } = require('./helpers/buildMiddlewareFilter');
15
15
  const stringifyFunctionOperators = require('./helpers/aggregate/stringifyFunctionOperators');
16
16
  const utils = require('./utils');
17
17
  const { modelSymbol } = require('./helpers/symbols');
18
+ const { createTracedChannel } = require('./tracing');
19
+ const { trace: traceAggregate } = createTracedChannel('mongoose:aggregate');
18
20
  const read = Query.prototype.read;
19
21
  const readConcern = Query.prototype.readConcern;
20
22
 
@@ -1032,7 +1034,7 @@ Aggregate.prototype.pipeline = function() {
1032
1034
  * const base = MyModel.aggregate().match({ test: 1 });
1033
1035
  * base.pipelineForUnionWith(); // [{ $match: { test: 1 } }]
1034
1036
  *
1035
- * @return {Array} The current pipeline with `$unionWith` stage restrictions
1037
+ * @return {PipelineStage[]} The current pipeline with `$unionWith` stage restrictions
1036
1038
  * @api public
1037
1039
  */
1038
1040
 
@@ -1071,8 +1073,20 @@ Aggregate.prototype.exec = async function exec() {
1071
1073
 
1072
1074
  this._optionsForExec();
1073
1075
 
1074
- const cursor = await this._connection.client.db().aggregate(this._pipeline, this.options);
1075
- return await cursor.toArray();
1076
+ const _this = this;
1077
+ return traceAggregate(async function maybeTracedConnectionAggregate() {
1078
+ const cursor = await _this._connection.client.db().aggregate(_this._pipeline, _this.options);
1079
+ return await cursor.toArray();
1080
+ }, () => ({
1081
+ operation: 'aggregate',
1082
+ database: _this._connection.name,
1083
+ serverAddress: _this._connection.host,
1084
+ serverPort: _this._connection.port,
1085
+ args: {
1086
+ pipeline: _this._pipeline,
1087
+ options: _this.options
1088
+ }
1089
+ }));
1076
1090
  }
1077
1091
 
1078
1092
  const model = this._model;
@@ -1090,32 +1104,45 @@ Aggregate.prototype.exec = async function exec() {
1090
1104
  prepareDiscriminatorPipeline(this._pipeline, this._model.schema);
1091
1105
  stringifyFunctionOperators(this._pipeline);
1092
1106
 
1093
- const preFilter = buildMiddlewareFilter(this.options, 'pre');
1094
- const postFilter = buildMiddlewareFilter(this.options, 'post');
1107
+ const _this = this;
1108
+ return traceAggregate(async function maybeTracedAggregateExec() {
1109
+ const preFilter = buildMiddlewareFilter(_this.options, 'pre');
1110
+ const postFilter = buildMiddlewareFilter(_this.options, 'post');
1095
1111
 
1096
- try {
1097
- await model.hooks.execPre('aggregate', this, [], { filter: preFilter });
1098
- } catch (error) {
1099
- return await model.hooks.execPost('aggregate', this, [null], { error, filter: postFilter });
1100
- }
1112
+ try {
1113
+ await model.hooks.execPre('aggregate', _this, [], { filter: preFilter });
1114
+ } catch (error) {
1115
+ return await model.hooks.execPost('aggregate', _this, [null], { error, filter: postFilter });
1116
+ }
1101
1117
 
1102
- if (!this._pipeline.length) {
1103
- throw new MongooseError('Aggregate has empty pipeline');
1104
- }
1118
+ if (!_this._pipeline.length) {
1119
+ throw new MongooseError('Aggregate has empty pipeline');
1120
+ }
1105
1121
 
1106
- const options = clone(this.options || {});
1107
- delete options.middleware;
1122
+ const options = clone(_this.options || {});
1123
+ delete options.middleware;
1108
1124
 
1109
- let result;
1110
- try {
1111
- const cursor = await collection.aggregate(this._pipeline, options);
1112
- result = await cursor.toArray();
1113
- } catch (error) {
1114
- return await model.hooks.execPost('aggregate', this, [null], { error, filter: postFilter });
1115
- }
1125
+ let result;
1126
+ try {
1127
+ const cursor = await collection.aggregate(_this._pipeline, options);
1128
+ result = await cursor.toArray();
1129
+ } catch (error) {
1130
+ return await model.hooks.execPost('aggregate', _this, [null], { error, filter: postFilter });
1131
+ }
1116
1132
 
1117
- await model.hooks.execPost('aggregate', this, [result], { error: null, filter: postFilter });
1118
- return result;
1133
+ await model.hooks.execPost('aggregate', _this, [result], { error: null, filter: postFilter });
1134
+ return result;
1135
+ }, () => ({
1136
+ operation: 'aggregate',
1137
+ collection: collection.name,
1138
+ database: model.db?.name,
1139
+ serverAddress: model.db?.host,
1140
+ serverPort: model.db?.port,
1141
+ args: {
1142
+ pipeline: _this._pipeline,
1143
+ options: _this.options
1144
+ }
1145
+ }));
1119
1146
  };
1120
1147
 
1121
1148
  /**
package/lib/connection.js CHANGED
@@ -1354,7 +1354,7 @@ Connection.prototype.onClose = function onClose(force) {
1354
1354
 
1355
1355
  /**
1356
1356
  * Retrieves a raw collection instance, creating it if not cached.
1357
- * This method returns a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)).
1357
+ * This method returns a thin wrapper around a [MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html).
1358
1358
  * Using a Collection bypasses Mongoose middleware, validation, and casting,
1359
1359
  * letting you use [MongoDB Node.js driver](https://mongodb.github.io/node-mongodb-native/) functionality directly.
1360
1360
  *
@@ -10,6 +10,7 @@ const eachAsync = require('../helpers/cursor/eachAsync');
10
10
  const immediate = require('../helpers/immediate');
11
11
  const kareem = require('kareem');
12
12
  const util = require('util');
13
+ const { cursorNextChannel } = require('../tracing');
13
14
 
14
15
  /**
15
16
  * An AggregationCursor is a concurrency primitive for processing aggregation
@@ -62,13 +63,7 @@ util.inherits(AggregationCursor, Readable);
62
63
  */
63
64
 
64
65
  function _init(model, c, agg) {
65
- if (!model.collection.buffer) {
66
- model.hooks.execPre('aggregate', agg).then(() => onPreComplete(null), err => onPreComplete(err));
67
- } else {
68
- model.collection.emitter.once('queue', function() {
69
- model.hooks.execPre('aggregate', agg).then(() => onPreComplete(null), err => onPreComplete(err));
70
- });
71
- }
66
+ model.hooks.execPre('aggregate', agg).then(() => onPreComplete(null), err => onPreComplete(err));
72
67
 
73
68
  function onPreComplete(err) {
74
69
  if (err != null) {
@@ -79,8 +74,28 @@ function _init(model, c, agg) {
79
74
  c._transforms.push(agg.options.cursor.transform);
80
75
  }
81
76
 
82
- c.cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
83
- c.emit('cursor', c.cursor);
77
+ if (model.collection._shouldBufferCommands() && model.collection.buffer) {
78
+ model.collection.queue.push([
79
+ () => _getRawCursor(model, c, agg)
80
+ ]);
81
+ } else {
82
+ _getRawCursor(model, c, agg);
83
+ }
84
+ }
85
+ }
86
+
87
+ /*!
88
+ * ignore
89
+ */
90
+
91
+ function _getRawCursor(model, aggregationCursor, agg) {
92
+ try {
93
+ const cursor = model.collection.aggregate(agg._pipeline, agg.options || {});
94
+ aggregationCursor.cursor = cursor;
95
+ aggregationCursor.emit('cursor', cursor);
96
+ } catch (err) {
97
+ aggregationCursor._markError(err);
98
+ aggregationCursor.listeners('error').length > 0 && aggregationCursor.emit('error', aggregationCursor._error);
84
99
  }
85
100
  }
86
101
 
@@ -277,14 +292,30 @@ AggregationCursor.prototype.next = async function next() {
277
292
  if (typeof arguments[0] === 'function') {
278
293
  throw new MongooseError('AggregationCursor.prototype.next() no longer accepts a callback');
279
294
  }
280
- return new Promise((resolve, reject) => {
281
- _next(this, (err, res) => {
282
- if (err != null) {
283
- return reject(err);
284
- }
285
- resolve(res);
295
+ const _this = this;
296
+ const model = this.agg._model;
297
+ return cursorNextChannel.trace(function maybeTracedAggCursorNext() {
298
+ return new Promise((resolve, reject) => {
299
+ _next(_this, (err, res) => {
300
+ if (err != null) {
301
+ return reject(err);
302
+ }
303
+ resolve(res);
304
+ });
286
305
  });
287
- });
306
+ }, () => ({
307
+ operation: 'aggregate',
308
+ collection: model?.collection?.name,
309
+ database: model?.db?.name,
310
+ serverAddress: model?.db?.host,
311
+ serverPort: model?.db?.port,
312
+ batchSize: _this.agg.options?.cursor?.batchSize,
313
+ tailable: false,
314
+ args: {
315
+ pipeline: _this.agg._pipeline,
316
+ options: _this.agg.options
317
+ }
318
+ }));
288
319
  };
289
320
 
290
321
  /**
@@ -12,6 +12,7 @@ const kareem = require('kareem');
12
12
  const immediate = require('../helpers/immediate');
13
13
  const { once } = require('events');
14
14
  const util = require('util');
15
+ const { cursorNextChannel } = require('../tracing');
15
16
 
16
17
  /**
17
18
  * A QueryCursor is a concurrency primitive for processing query results
@@ -310,14 +311,29 @@ QueryCursor.prototype.next = async function next() {
310
311
  if (this._closed) {
311
312
  throw new MongooseError('Cannot call `next()` on a closed cursor');
312
313
  }
313
- return new Promise((resolve, reject) => {
314
- _next(this, function(error, doc) {
315
- if (error) {
316
- return reject(error);
317
- }
318
- resolve(doc);
314
+ const _this = this;
315
+ return cursorNextChannel.trace(function maybeTracedQueryCursorNext() {
316
+ return new Promise((resolve, reject) => {
317
+ _next(_this, function(error, doc) {
318
+ if (error) {
319
+ return reject(error);
320
+ }
321
+ resolve(doc);
322
+ });
319
323
  });
320
- });
324
+ }, () => ({
325
+ operation: _this.query.op || 'find',
326
+ collection: _this.query.mongooseCollection.name,
327
+ database: _this.model.db?.name,
328
+ serverAddress: _this.model.db?.host,
329
+ serverPort: _this.model.db?.port,
330
+ batchSize: _this.options.batchSize || _this.query.options?.batchSize,
331
+ tailable: _this.options.tailable || _this.query.options?.tailable || false,
332
+ args: {
333
+ filter: _this.query.getFilter(),
334
+ options: _this.query._mongooseOptions
335
+ }
336
+ }));
321
337
  };
322
338
 
323
339
  /**
package/lib/document.js CHANGED
@@ -3264,6 +3264,9 @@ function _handlePathsToSkip(paths, pathsToSkip) {
3264
3264
  /**
3265
3265
  * Executes registered validation rules (skipping asynchronous validators) for this document.
3266
3266
  *
3267
+ * **Deprecated.** Use [`validate()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) instead.
3268
+ * `validateSync()` does not run validation middleware and will be removed in Mongoose 10.
3269
+ *
3267
3270
  * #### Note:
3268
3271
  *
3269
3272
  * This method is useful if you need synchronous validation.
@@ -3281,14 +3284,21 @@ function _handlePathsToSkip(paths, pathsToSkip) {
3281
3284
  * @param {object} [options] options for validation
3282
3285
  * @param {boolean} [options.validateModifiedOnly=false] If `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
3283
3286
  * @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
3284
- * @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware
3285
- * @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
3286
- * @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks
3287
3287
  * @return {ValidationError|undefined} ValidationError if there are errors during validation, or undefined if there is no error.
3288
+ * @deprecated Use `validate()` instead. `validateSync()` does not run middleware and will be removed in Mongoose 10.
3288
3289
  * @api public
3289
3290
  */
3290
3291
 
3291
- Document.prototype.validateSync = function(pathsToValidate, options) {
3292
+ Document.prototype.validateSync = function() {
3293
+ utils.warn('Mongoose: `Document.prototype.validateSync()` is deprecated and will be removed in Mongoose 10. Use `Document.prototype.validate()` instead.');
3294
+ return this.$__validateSync.apply(this, arguments);
3295
+ };
3296
+
3297
+ /*!
3298
+ * ignore
3299
+ */
3300
+
3301
+ Document.prototype.$__validateSync = function(pathsToValidate, options) {
3292
3302
  const _this = this;
3293
3303
 
3294
3304
  if (arguments.length === 1 && typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) {
@@ -5266,6 +5276,12 @@ function checkDivergentArray(doc, path, array) {
5266
5276
  // If any array was selected using an $elemMatch projection, we deny the update.
5267
5277
  // NOTE: MongoDB only supports projected $elemMatch on top level array.
5268
5278
  const top = path.split('.')[0];
5279
+ if (doc.$__.selected[top] && doc.$__.selected[top].$slice != null && utils.isMongooseArray(array)) {
5280
+ const atomics = array[arrayAtomicsSymbol];
5281
+ if (atomics.$set) {
5282
+ return top;
5283
+ }
5284
+ }
5269
5285
  if (doc.$__.selected[top + '.$']) {
5270
5286
  return top;
5271
5287
  }
@@ -17,7 +17,7 @@ class DivergentArrayError extends MongooseError {
17
17
 
18
18
  constructor(paths) {
19
19
  const msg = 'For your own good, using `document.save()` to update an array '
20
- + 'which was selected using an $elemMatch projection OR '
20
+ + 'which was selected using an $elemMatch or $slice projection OR '
21
21
  + 'populated using skip, limit, query conditions, or exclusion of '
22
22
  + 'the _id field when the operation results in a $pop or $set of '
23
23
  + 'the entire array is not supported. The following '
@@ -202,10 +202,15 @@ function cloneArray(arr, options) {
202
202
 
203
203
  let ret = null;
204
204
  if (options?.retainDocuments) {
205
+ // Use the cloned parent doc (options.parentDoc) when available so the new
206
+ // array is wired up to the clone, not the source. Falling back to the
207
+ // source's parent preserves the previous behavior for callers that don't
208
+ // pass parentDoc.
209
+ const newParent = options.parentDoc ?? (arr.isMongooseArray ? arr.$parent() : undefined);
205
210
  if (arr.isMongooseDocumentArray) {
206
- ret = new (arr.$schemaType().schema.base.Types.DocumentArray)([], arr.$path(), arr.$parent(), arr.$schemaType());
211
+ ret = new (arr.$schemaType().schema.base.Types.DocumentArray)([], arr.$path(), newParent, arr.$schemaType());
207
212
  } else if (arr.isMongooseArray) {
208
- ret = new (arr.$parent().schema.base.Types.Array)([], arr.$path(), arr.$parent(), arr.$schemaType());
213
+ ret = new (arr.$parent().schema.base.Types.Array)([], arr.$path(), newParent, arr.$schemaType());
209
214
  } else {
210
215
  ret = new Array(len);
211
216
  }
@@ -218,9 +223,21 @@ function cloneArray(arr, options) {
218
223
  // Create new options object to avoid mutating the shared options.
219
224
  // Subdocs need parentArray to point to their own cloned array.
220
225
  options = { ...options, parentArray: ret };
221
- }
222
- for (i = 0; i < len; ++i) {
223
- ret[i] = clone(arr[i], options, true);
226
+ // Skip change-tracking while populating the freshly cloned array: every
227
+ // index assignment would otherwise call markModified on the cloned parent
228
+ // doc and (before this guard) on the source parent doc.
229
+ for (i = 0; i < len; ++i) {
230
+ const doc = clone(arr[i], options, true);
231
+ // Document arrays can contain null entries, so only restamp actual subdocs.
232
+ if (typeof doc?.$setIndex === 'function') {
233
+ doc.$setIndex(i);
234
+ }
235
+ ret.set(i, doc, true);
236
+ }
237
+ } else {
238
+ for (i = 0; i < len; ++i) {
239
+ ret[i] = clone(arr[i], options, true);
240
+ }
224
241
  }
225
242
 
226
243
  return ret;
@@ -873,7 +873,11 @@ function _findRefPathForDiscriminators(doc, modelSchema, data, options, normaliz
873
873
  }
874
874
 
875
875
  /**
876
- * Throw an error if there are any $where keys
876
+ * Throw an error if there are any $where keys to defend against [CVE-2024-53900](https://nvd.nist.gov/vuln/detail/CVE-2024-53900)
877
+ *
878
+ * Note that this is ONLY for $where because sift executes $where in Node.js memory.
879
+ * Other forms of MongoDB server-side execution, like $expr, are NOT filtered out.
880
+ * This function is not meant to protect against server-side execution in MongoDB.
877
881
  */
878
882
 
879
883
  function throwOn$where(match) {
@@ -82,7 +82,9 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
82
82
 
83
83
  if (!skipCreatedAt && createdAt) {
84
84
  const overwriteImmutable = get(options, 'overwriteImmutable', false);
85
- const hasUserCreatedAt = currentUpdate[createdAt] != null || currentUpdate.$set?.[createdAt] != null;
85
+ const hasUserCreatedAt = currentUpdate[createdAt] != null
86
+ || currentUpdate.$set?.[createdAt] != null
87
+ || currentUpdate.$setOnInsert?.[createdAt] != null;
86
88
 
87
89
  // If overwriteImmutable is true and user provided createdAt, keep their value
88
90
  if (overwriteImmutable && hasUserCreatedAt) {
@@ -91,7 +93,7 @@ function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, optio
91
93
  updates.$set[createdAt] = currentUpdate[createdAt];
92
94
  delete currentUpdate[createdAt];
93
95
  }
94
- // User's value is already in $set, nothing more to do
96
+ // User's value is already in $set or $setOnInsert, nothing more to do
95
97
  } else {
96
98
  if (currentUpdate[createdAt]) {
97
99
  delete currentUpdate[createdAt];
package/lib/model.js CHANGED
@@ -7,6 +7,10 @@
7
7
  const Aggregate = require('./aggregate');
8
8
  const ChangeStream = require('./cursor/changeStream');
9
9
  const Document = require('./document');
10
+ const { createTracedChannel } = require('./tracing');
11
+ const { trace: traceSave } = createTracedChannel('mongoose:model:save');
12
+ const { trace: traceInsertMany } = createTracedChannel('mongoose:model:insertMany');
13
+ const { trace: traceBulkWrite } = createTracedChannel('mongoose:model:bulkWrite');
10
14
  const DocumentNotFoundError = require('./error/notFound');
11
15
  const EventEmitter = require('events').EventEmitter;
12
16
  const Kareem = require('kareem');
@@ -37,6 +41,7 @@ const applyVirtualsHelper = require('./helpers/document/applyVirtuals');
37
41
  const assignVals = require('./helpers/populate/assignVals');
38
42
  const castBulkWrite = require('./helpers/model/castBulkWrite');
39
43
  const clone = require('./helpers/clone');
44
+ const convertErrorToStandardSchemaIssues = require('./standardSchema/convertErrorToIssues');
40
45
  const createPopulateQueryFilter = require('./helpers/populate/createPopulateQueryFilter');
41
46
  const decorateUpdateWithVersionKey = require('./helpers/update/decorateUpdateWithVersionKey');
42
47
  const getDefaultBulkwriteResult = require('./helpers/getDefaultBulkwriteResult');
@@ -663,19 +668,29 @@ Model.prototype.save = async function save(options) {
663
668
 
664
669
  this.$__.saveOptions = options;
665
670
 
666
- try {
667
- await this.$__save(options);
668
- } catch (error) {
669
- this.$__handleReject(error);
670
- throw error;
671
- } finally {
672
- this.$__.saving = null;
673
- this.$__.saveOptions = null;
674
- this.$__.$versionError = null;
675
- this.$op = null;
676
- }
677
-
678
- return this;
671
+ const _this = this;
672
+ return traceSave(async function maybeTracedSave() {
673
+ try {
674
+ await _this.$__save(options);
675
+ } catch (error) {
676
+ _this.$__handleReject(error);
677
+ throw error;
678
+ } finally {
679
+ _this.$__.saving = null;
680
+ _this.$__.saveOptions = null;
681
+ _this.$__.$versionError = null;
682
+ _this.$op = null;
683
+ }
684
+
685
+ return _this;
686
+ }, () => ({
687
+ operation: 'save',
688
+ collection: _this.constructor.collection.name,
689
+ database: _this.constructor.db?.name,
690
+ serverAddress: _this.constructor.db?.host,
691
+ serverPort: _this.constructor.db?.port,
692
+ args: { options }
693
+ }));
679
694
  };
680
695
 
681
696
  Model.prototype.$save = Model.prototype.save;
@@ -3015,6 +3030,18 @@ Model.insertMany = async function insertMany(arr, options) {
3015
3030
  throw new MongooseError('Model.insertMany() no longer accepts a callback');
3016
3031
  }
3017
3032
 
3033
+ const ThisModel = this;
3034
+ return traceInsertMany(function maybeTracedInsertMany() { return _insertMany.call(ThisModel, arr, options); }, () => ({
3035
+ operation: 'insertMany',
3036
+ collection: ThisModel.collection.name,
3037
+ database: ThisModel.db?.name,
3038
+ serverAddress: ThisModel.db?.host,
3039
+ serverPort: ThisModel.db?.port,
3040
+ args: { docs: arr, options }
3041
+ }));
3042
+ };
3043
+
3044
+ async function _insertMany(arr, options) {
3018
3045
  options = options || {};
3019
3046
  const preFilter = buildMiddlewareFilter(options, 'pre');
3020
3047
  const postFilter = buildMiddlewareFilter(options, 'post');
@@ -3250,7 +3277,7 @@ Model.insertMany = async function insertMany(arr, options) {
3250
3277
 
3251
3278
  const [result] = await this._middleware.execPost('insertMany', this, [docAttributes], { filter: postFilter });
3252
3279
  return result;
3253
- };
3280
+ }
3254
3281
 
3255
3282
  /*!
3256
3283
  * ignore
@@ -3378,6 +3405,19 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3378
3405
  typeof arguments[2] === 'function') {
3379
3406
  throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
3380
3407
  }
3408
+
3409
+ const ThisModel = this;
3410
+ return traceBulkWrite(function maybeTracedBulkWrite() { return _bulkWrite.call(ThisModel, ops, options); }, () => ({
3411
+ operation: 'bulkWrite',
3412
+ collection: ThisModel.collection.name,
3413
+ database: ThisModel.db?.name,
3414
+ serverAddress: ThisModel.db?.host,
3415
+ serverPort: ThisModel.db?.port,
3416
+ args: { ops, options }
3417
+ }));
3418
+ };
3419
+
3420
+ async function _bulkWrite(ops, options) {
3381
3421
  options = options || {};
3382
3422
  const preFilter = buildMiddlewareFilter(options, 'pre');
3383
3423
  const postFilter = buildMiddlewareFilter(options, 'post');
@@ -3516,7 +3556,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
3516
3556
  await this.hooks.execPost('bulkWrite', this, [res], { filter: postFilter });
3517
3557
 
3518
3558
  return res;
3519
- };
3559
+ }
3520
3560
 
3521
3561
  /**
3522
3562
  * Takes an array of documents, gets the changes and inserts/updates documents in the database
@@ -3861,7 +3901,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
3861
3901
  throw new MongooseError(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`);
3862
3902
  }
3863
3903
  if (options.validateBeforeSave == null || options.validateBeforeSave) {
3864
- const err = document.validateSync();
3904
+ const err = document.$__validateSync();
3865
3905
  if (err != null) {
3866
3906
  throw err;
3867
3907
  }
@@ -4188,13 +4228,24 @@ Model.aggregate = function aggregate(pipeline, options) {
4188
4228
  * age: { type: Number, required: true }
4189
4229
  * });
4190
4230
  *
4231
+ * // Succeeds
4232
+ * await Model.validate({ name: 'John Smith', age: 31 });
4233
+ *
4191
4234
  * try {
4192
- * await Model.validate({ name: null }, ['name'])
4235
+ * await Model.validate({ name: null });
4193
4236
  * } catch (err) {
4194
4237
  * err instanceof mongoose.Error.ValidationError; // true
4195
4238
  * Object.keys(err.errors); // ['name']
4196
4239
  * }
4197
4240
  *
4241
+ * Note: the `pathsToSkip` and `pathsToValidate` options **only** apply to validation, not
4242
+ * casting. This function will still throw an error for values that cannot be casted to the
4243
+ * schema-specified type. Remove any paths you do not want to cast.
4244
+ *
4245
+ * // The following will still throw an error because the value of `age` cannot be
4246
+ * // casted to a number. Remove the `age` property before calling `validate()`.
4247
+ * await Model.validate({ name: 'Test', age: 'not a number' }, ['name']);
4248
+ *
4198
4249
  * @param {object} obj
4199
4250
  * @param {object|Array|string} pathsOrOptions
4200
4251
  * @param {object} [context]
@@ -4299,6 +4350,33 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
4299
4350
  return obj;
4300
4351
  };
4301
4352
 
4353
+ /**
4354
+ * Standard Schema adapter for this model.
4355
+ * Calls [`Model.validate()`](https://mongoosejs.com/docs/api/model.html#Model.validate()) internally with the provided `libraryOptions`
4356
+ * to cast and validate the given value.
4357
+ *
4358
+ * @api public
4359
+ * @property ~standard
4360
+ * @memberOf Model
4361
+ * @static
4362
+ */
4363
+
4364
+ Object.defineProperty(Model, '~standard', {
4365
+ configurable: true,
4366
+ get() {
4367
+ return {
4368
+ version: 1,
4369
+ vendor: 'mongoose',
4370
+ validate: (value, options) => {
4371
+ return this.validate(value, options?.libraryOptions).then(
4372
+ value => ({ value }),
4373
+ error => ({ issues: convertErrorToStandardSchemaIssues(error) })
4374
+ );
4375
+ }
4376
+ };
4377
+ }
4378
+ });
4379
+
4302
4380
  /**
4303
4381
  * Populates document references.
4304
4382
  *
package/lib/mongoose.js CHANGED
@@ -398,8 +398,8 @@ Mongoose.prototype.get = Mongoose.prototype.set;
398
398
  * @param {string} [options.user] username for authentication, equivalent to `options.auth.username`. Maintained for backwards compatibility.
399
399
  * @param {string} [options.pass] password for authentication, equivalent to `options.auth.password`. Maintained for backwards compatibility.
400
400
  * @param {boolean} [options.autoIndex=true] Mongoose-specific option. Set to false to disable automatic index creation for all models associated with this connection.
401
- * @param {number} [options.maxPoolSize=5] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
402
- * @param {number} [options.minPoolSize=1] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
401
+ * @param {number} [options.maxPoolSize=100] The maximum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
402
+ * @param {number} [options.minPoolSize=0] The minimum number of sockets the MongoDB driver will keep open for this connection. Keep in mind that MongoDB only allows one operation per socket at a time, so you may want to increase this if you find you have a few slow queries that are blocking faster queries from proceeding. See [Slow Trains in MongoDB and Node.js](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs).
403
403
  * @param {number} [options.socketTimeoutMS=0] How long the MongoDB driver will wait before killing a socket due to inactivity _after initial connection_. Defaults to 0, which means Node.js will not time out the socket due to inactivity. A socket may be inactive because of either no activity or a long-running operation. This option is passed to [Node.js `socket#setTimeout()` function](https://nodejs.org/api/net.html#net_socket_settimeout_timeout_callback) after the MongoDB driver successfully completes.
404
404
  * @param {number} [options.family=0] Passed transparently to [Node.js' `dns.lookup()`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) function. May be either `0`, `4`, or `6`. `4` means use IPv4 only, `6` means use IPv6 only, `0` means try both.
405
405
  * @return {Connection} the created Connection object. Connections are not thenable, so you can't do `await mongoose.createConnection()`. To await use `mongoose.createConnection(uri).asPromise()` instead.
package/lib/query.js CHANGED
@@ -42,6 +42,8 @@ const updateValidators = require('./helpers/updateValidators');
42
42
  const util = require('util');
43
43
  const utils = require('./utils');
44
44
  const queryMiddlewareFunctions = require('./constants').queryMiddlewareFunctions;
45
+ const { createTracedChannel } = require('./tracing');
46
+ const { trace: traceQuery } = createTracedChannel('mongoose:query');
45
47
 
46
48
  const queryOptionMethods = new Set([
47
49
  'allowDiskUse',
@@ -4778,42 +4780,56 @@ Query.prototype.exec = async function exec(op) {
4778
4780
  }
4779
4781
  this._execCount++;
4780
4782
 
4781
- let skipWrappedFunction = null;
4782
- try {
4783
- await this._hooks.execPre('exec', this, []);
4784
- } catch (err) {
4785
- if (err instanceof Kareem.skipWrappedFunction) {
4786
- skipWrappedFunction = err;
4787
- } else {
4788
- throw err;
4783
+ const _this = this;
4784
+ return traceQuery(async function maybeTracedQueryExec() {
4785
+ let skipWrappedFunction = null;
4786
+ try {
4787
+ await _this._hooks.execPre('exec', _this, []);
4788
+ } catch (err) {
4789
+ if (err instanceof Kareem.skipWrappedFunction) {
4790
+ skipWrappedFunction = err;
4791
+ } else {
4792
+ throw err;
4793
+ }
4789
4794
  }
4790
- }
4791
4795
 
4792
- let res;
4796
+ let res;
4793
4797
 
4794
- let error = null;
4795
- try {
4796
- await _executePreHooks(this);
4797
- res = skipWrappedFunction ? skipWrappedFunction.args[0] : await this[thunk]();
4798
+ let error = null;
4799
+ try {
4800
+ await _executePreHooks(_this);
4801
+ res = skipWrappedFunction ? skipWrappedFunction.args[0] : await _this[thunk]();
4798
4802
 
4799
- for (const fn of this._transforms) {
4800
- res = fn(res);
4801
- }
4802
- } catch (err) {
4803
- if (err instanceof Kareem.skipWrappedFunction) {
4804
- res = err.args[0];
4805
- } else {
4806
- error = err;
4807
- }
4803
+ for (const fn of _this._transforms) {
4804
+ res = fn(res);
4805
+ }
4806
+ } catch (err) {
4807
+ if (err instanceof Kareem.skipWrappedFunction) {
4808
+ res = err.args[0];
4809
+ } else {
4810
+ error = err;
4811
+ }
4808
4812
 
4809
- error = this.model.schema._transformDuplicateKeyError(error);
4810
- }
4813
+ error = _this.model.schema._transformDuplicateKeyError(error);
4814
+ }
4811
4815
 
4812
- res = await _executePostHooks(this, res, error);
4816
+ res = await _executePostHooks(_this, res, error);
4813
4817
 
4814
- await this._hooks.execPost('exec', this, []);
4818
+ await _this._hooks.execPost('exec', _this, []);
4815
4819
 
4816
- return res;
4820
+ return res;
4821
+ }, () => ({
4822
+ operation: _this.op,
4823
+ collection: _this.mongooseCollection.name,
4824
+ database: _this.model.db?.name,
4825
+ serverAddress: _this.model.db?.host,
4826
+ serverPort: _this.model.db?.port,
4827
+ args: {
4828
+ filter: _this.getFilter(),
4829
+ fields: _this._fields,
4830
+ options: _this._mongooseOptions
4831
+ }
4832
+ }));
4817
4833
  };
4818
4834
 
4819
4835
  /*!
@@ -188,16 +188,18 @@ SchemaDate.prototype.expires = function(when) {
188
188
  SchemaDate._checkRequired = v => v instanceof Date;
189
189
 
190
190
  /**
191
- * 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
192
192
  * passes the `required` check.
193
193
  *
194
194
  * #### Example:
195
195
  *
196
- * // Allow empty strings to pass `required` check
197
- * 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()));
198
198
  *
199
- * const M = mongoose.model({ str: { type: String, required: true } });
200
- * 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();
201
203
  *
202
204
  * @param {Function} fn
203
205
  * @return {Function}
@@ -314,7 +314,7 @@ SchemaDocumentArray.prototype.doValidateSync = function(array, scope, options) {
314
314
  continue;
315
315
  }
316
316
 
317
- const subdocValidateError = doc.validateSync(options);
317
+ const subdocValidateError = doc.$__validateSync(options);
318
318
 
319
319
  if (subdocValidateError && resultError == null) {
320
320
  resultError = subdocValidateError;
@@ -473,12 +473,14 @@ SchemaString.prototype.maxlength = function(value, message) {
473
473
 
474
474
  if (value != null) {
475
475
  let msg = message || MongooseError.messages.String.maxlength;
476
- msg = msg.replace(/{MAXLENGTH}/, value);
476
+ if (typeof msg !== 'function') {
477
+ msg = msg.replace(/{MAXLENGTH}/, value);
478
+ }
477
479
  this.validators.push({
478
480
  validator: this.maxlengthValidator = function(v) {
479
481
  return v === null || v.length <= value;
480
482
  },
481
- message: msg,
483
+ message: formatMaxLengthValidatorMessage(msg),
482
484
  type: 'maxlength',
483
485
  maxlength: value
484
486
  });
@@ -729,3 +731,18 @@ SchemaString.prototype.autoEncryptionType = function autoEncryptionType() {
729
731
  */
730
732
 
731
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
+ }
@@ -310,7 +310,7 @@ SchemaSubdocument.prototype.doValidateSync = function(value, scope, options) {
310
310
  if (!value) {
311
311
  return;
312
312
  }
313
- return value.validateSync();
313
+ return value.$__validateSync();
314
314
  };
315
315
 
316
316
  /**
@@ -112,8 +112,8 @@ class Union extends SchemaType {
112
112
  return schemaTypeError;
113
113
  }
114
114
  }
115
- if (value != null && typeof value.validateSync === 'function') {
116
- return value.validateSync();
115
+ if (value != null && typeof value.$__validateSync === 'function') {
116
+ return value.$__validateSync();
117
117
  }
118
118
  }
119
119
 
package/lib/schemaType.js CHANGED
@@ -1198,7 +1198,8 @@ SchemaType.prototype.required = function(required, message) {
1198
1198
  * name: { type: String, allowNull: false }
1199
1199
  * });
1200
1200
  *
1201
- * new Model({ name: undefined }).validateSync(); // OK
1201
+ * new Model({}).validateSync(); // OK
1202
+ * new Model({ name: undefined }).validateSync(); // OK, Mongoose strips out `name` when its value is `undefined`
1202
1203
  * new Model({ name: null }).validateSync(); // ValidationError
1203
1204
  *
1204
1205
  * @param {boolean} allowNull
@@ -1456,9 +1457,6 @@ SchemaType.prototype.doValidate = async function doValidate(value, scope, option
1456
1457
  validatorProperties.value = value;
1457
1458
  if (typeof value === 'string') {
1458
1459
  validatorProperties.length = value.length;
1459
- if (validatorProperties.value.length > 30) {
1460
- validatorProperties.value = validatorProperties.value.slice(0, 30) + '...';
1461
- }
1462
1460
  }
1463
1461
 
1464
1462
  if (value === undefined && validator !== this.requiredValidator) {
@@ -1582,9 +1580,6 @@ SchemaType.prototype.doValidateSync = function(value, scope, options) {
1582
1580
  validatorProperties.value = value;
1583
1581
  if (typeof value === 'string') {
1584
1582
  validatorProperties.length = value.length;
1585
- if (validatorProperties.value.length > 30) {
1586
- validatorProperties.value = validatorProperties.value.slice(0, 30) + '...';
1587
- }
1588
1583
  }
1589
1584
  let ok = false;
1590
1585
 
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const ValidationError = require('../error/validation');
4
+
5
+ module.exports = function convertErrorToIssues(error) {
6
+ if (error instanceof ValidationError) {
7
+ return Object.keys(error.errors).map(path => {
8
+ const err = error.errors[path];
9
+ return {
10
+ message: err.message,
11
+ path: path.split('.').map(part => {
12
+ const num = +part;
13
+ return Number.isInteger(num) && String(num) === part ? num : part;
14
+ })
15
+ };
16
+ });
17
+ }
18
+
19
+ return [{ message: error.message }];
20
+ };
package/lib/tracing.js ADDED
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ let dc;
4
+ try {
5
+ dc = (typeof process !== 'undefined' && 'getBuiltinModule' in process)
6
+ ? process.getBuiltinModule('node:diagnostics_channel')
7
+ : require('node:diagnostics_channel');
8
+ } catch {
9
+ // diagnostics_channel not available
10
+ }
11
+
12
+ const hasTracingChannel = !!dc && typeof dc.tracingChannel === 'function';
13
+
14
+ function shouldTrace(channel) {
15
+ // Node 18 and Bun don't expose hasSubscribers on TracingChannel
16
+ // so undefined means it may or may not have subscribers, so we'll trace anyway
17
+ return !!channel && channel.hasSubscribers !== false;
18
+ }
19
+
20
+ function createTracedChannel(name) {
21
+ const ch = hasTracingChannel ? dc.tracingChannel(name) : undefined;
22
+
23
+ function trace(fn, contextFactory) {
24
+ if (!shouldTrace(ch)) {
25
+ return fn();
26
+ }
27
+
28
+ const traced = ch.tracePromise(fn, contextFactory());
29
+ return traced;
30
+ }
31
+
32
+ return { channel: ch, trace };
33
+ }
34
+
35
+ module.exports = {
36
+ createTracedChannel,
37
+ cursorNextChannel: createTracedChannel('mongoose:cursor:next')
38
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "9.6.2",
4
+ "version": "9.7.0",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -39,9 +39,9 @@
39
39
  "c8": "11.0.0",
40
40
  "cheerio": "1.2.0",
41
41
  "dox": "1.0.0",
42
- "eslint": "10.2.1",
42
+ "eslint": "10.4.1",
43
43
  "eslint-plugin-mocha-no-only": "1.2.0",
44
- "express": "4.22.1",
44
+ "express": "5.2.1",
45
45
  "fs-extra": "~11.3.0",
46
46
  "globals": "^17.4.0",
47
47
  "glob": "^13.0.6",
@@ -50,16 +50,16 @@
50
50
  "lodash.isequal": "4.5.0",
51
51
  "lodash.isequalwith": "4.4.0",
52
52
  "markdownlint-cli2": "0.22.1",
53
- "marked": "18.0.2",
53
+ "marked": "18.0.4",
54
54
  "mkdirp": "^3.0.1",
55
55
  "mocha": "12.0.0-beta-10",
56
56
  "moment": "2.30.1",
57
57
  "mongodb-client-encryption": "~7.0",
58
- "mongodb-memory-server": "11.1.0",
58
+ "mongodb-memory-server": "11.2.0",
59
59
  "mongodb-runner": "^6.0.0",
60
60
  "ncp": "^2.0.0",
61
61
  "pug": "3.0.4",
62
- "sinon": "21.1.2",
62
+ "sinon": "22.0.0",
63
63
  "tstyche": "^7.0.0",
64
64
  "typescript": "5.9.3",
65
65
  "typescript-eslint": "^8.31.1",
@@ -11,6 +11,12 @@ declare module 'mongoose' {
11
11
  middleware?: boolean | SkipMiddlewareOptions;
12
12
  }
13
13
 
14
+ interface ValidateSyncOptions extends Omit<ValidateOptions, 'middleware'> {
15
+ /** `validateSync()` does not run middleware. Use `validate()` if you need middleware. */
16
+ middleware?: never;
17
+ [k: string]: any;
18
+ }
19
+
14
20
  interface DocumentSetOptions {
15
21
  merge?: boolean;
16
22
 
@@ -320,9 +326,20 @@ declare module 'mongoose' {
320
326
  validate(pathsToValidate?: pathsToValidate, options?: Omit<ValidateOptions, 'pathsToSkip'> & AnyObject): Promise<void>;
321
327
  validate(options: ValidateOptions): Promise<void>;
322
328
 
323
- /** Executes registered validation rules (skipping asynchronous validators) for this document. */
324
- validateSync(options: ValidateOptions & { [k: string]: any }): Error.ValidationError | null;
325
- validateSync<T extends keyof DocType>(pathsToValidate?: T | T[], options?: Omit<ValidateOptions, 'pathsToSkip'> & AnyObject): Error.ValidationError | null;
326
- validateSync(pathsToValidate?: pathsToValidate, options?: Omit<ValidateOptions, 'pathsToSkip'> & AnyObject): Error.ValidationError | null;
329
+ /**
330
+ * Executes registered validation rules (skipping asynchronous validators) for this document.
331
+ * @deprecated Use `validate()` instead. `validateSync()` does not run middleware and will be removed in Mongoose 10.
332
+ */
333
+ validateSync(options: ValidateSyncOptions): Error.ValidationError | null;
334
+ /**
335
+ * Executes registered validation rules (skipping asynchronous validators) for this document.
336
+ * @deprecated Use `validate()` instead. `validateSync()` does not run middleware and will be removed in Mongoose 10.
337
+ */
338
+ validateSync<T extends keyof DocType>(pathsToValidate?: T | T[], options?: Omit<ValidateSyncOptions, 'pathsToSkip'>): Error.ValidationError | null;
339
+ /**
340
+ * Executes registered validation rules (skipping asynchronous validators) for this document.
341
+ * @deprecated Use `validate()` instead. `validateSync()` does not run middleware and will be removed in Mongoose 10.
342
+ */
343
+ validateSync(pathsToValidate?: pathsToValidate, options?: Omit<ValidateSyncOptions, 'pathsToSkip'>): Error.ValidationError | null;
327
344
  }
328
345
  }
@@ -2313,6 +2313,19 @@ declare module 'mongoose' {
2313
2313
  }
2314
2314
  }
2315
2315
 
2316
+ export interface Percentile {
2317
+ /**
2318
+ * Returns an array of scalar values that correspond to specified percentile values.
2319
+ *
2320
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/
2321
+ */
2322
+ $percentile: {
2323
+ input: number | Expression,
2324
+ p: (number | Expression)[],
2325
+ method: 'approximate'
2326
+ }
2327
+ }
2328
+
2316
2329
  export interface StdDevPop {
2317
2330
  /**
2318
2331
  * Calculates the population standard deviation of the input values. Use if the values encompass the entire
@@ -2874,6 +2887,7 @@ declare module 'mongoose' {
2874
2887
  Expression.Median |
2875
2888
  Expression.Min |
2876
2889
  Expression.MinN |
2890
+ Expression.Percentile |
2877
2891
  Expression.Push |
2878
2892
  Expression.Rank |
2879
2893
  Expression.Shift |
@@ -2906,6 +2920,7 @@ declare module 'mongoose' {
2906
2920
  Expression.Max |
2907
2921
  Expression.Median |
2908
2922
  Expression.Min |
2923
+ Expression.Percentile |
2909
2924
  Expression.StdDevPop |
2910
2925
  Expression.StdDevSamp |
2911
2926
  Expression.Sum;
@@ -2981,6 +2996,7 @@ declare module 'mongoose' {
2981
2996
  Expression.MergeObjects |
2982
2997
  Expression.Min |
2983
2998
  Expression.MinN |
2999
+ Expression.Percentile |
2984
3000
  Expression.Push |
2985
3001
  Expression.StdDevPop |
2986
3002
  Expression.StdDevSamp |
package/types/index.d.ts CHANGED
@@ -24,6 +24,7 @@
24
24
  /// <reference path="./inferrawdoctype.d.ts" />
25
25
  /// <reference path="./inferschematype.d.ts" />
26
26
  /// <reference path="./virtuals.d.ts" />
27
+ /// <reference path="./tracing.d.ts" />
27
28
  /// <reference path="./augmentations.d.ts" />
28
29
 
29
30
  declare class NativeDate extends globalThis.Date { }
package/types/models.d.ts CHANGED
@@ -108,6 +108,41 @@ declare module 'mongoose' {
108
108
  */
109
109
  type pathsToValidate = PathsToValidate;
110
110
 
111
+ export namespace StandardSchemaV1 {
112
+ export interface Props<Output = unknown> {
113
+ readonly version: 1;
114
+ readonly vendor: 'mongoose';
115
+ readonly validate: (
116
+ value: unknown,
117
+ options?: Options | undefined
118
+ ) => Result<Output> | Promise<Result<Output>>;
119
+ }
120
+
121
+ export interface Options {
122
+ readonly libraryOptions?: Record<string, unknown> | undefined;
123
+ }
124
+
125
+ export type Result<Output> = SuccessResult<Output> | FailureResult;
126
+
127
+ export interface SuccessResult<Output> {
128
+ readonly value: Output;
129
+ readonly issues?: undefined;
130
+ }
131
+
132
+ export interface FailureResult {
133
+ readonly issues: ReadonlyArray<Issue>;
134
+ }
135
+
136
+ export interface Issue {
137
+ readonly message: string;
138
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
139
+ }
140
+
141
+ export interface PathSegment {
142
+ readonly key: PropertyKey;
143
+ }
144
+ }
145
+
111
146
  interface SaveOptions extends
112
147
  SessionOption {
113
148
  checkKeys?: boolean;
@@ -192,15 +227,15 @@ declare module 'mongoose' {
192
227
  type CreateObjectWithExtraKeys<T> = T & Record<string, unknown>;
193
228
  type ApplyBasicCreateCasting<T> = {
194
229
  [K in keyof T]: NonNullable<T[K]> extends Map<infer KeyType extends string, infer ValueType>
195
- ? (Record<KeyType, ValueType> | Array<[KeyType, ValueType]> | T[K])
230
+ ? (Record<KeyType, ValueType> | Array<[KeyType, ValueType]> | T[K] | QueryTypeCasting<Extract<T[K], TreatAsPrimitives>>)
196
231
  : NonNullable<T[K]> extends Types.DocumentArray<infer RawSubdocType>
197
- ? RawSubdocType[] | T[K]
232
+ ? RawSubdocType[] | T[K] | QueryTypeCasting<Extract<T[K], TreatAsPrimitives>>
198
233
  : NonNullable<T[K]> extends Document<any, any, infer RawSubdocType>
199
- ? CreateObjectWithExtraKeys<ApplyBasicCreateCasting<RawSubdocType>> | T[K]
234
+ ? CreateObjectWithExtraKeys<ApplyBasicCreateCasting<RawSubdocType>> | T[K] | QueryTypeCasting<Extract<T[K], TreatAsPrimitives>>
200
235
  : NonNullable<T[K]> extends TreatAsPrimitives
201
236
  ? QueryTypeCasting<T[K]>
202
237
  : NonNullable<T[K]> extends object
203
- ? CreateObjectWithExtraKeys<ApplyBasicCreateCasting<T[K]>> | T[K]
238
+ ? CreateObjectWithExtraKeys<ApplyBasicCreateCasting<T[K]>> | T[K] | QueryTypeCasting<Extract<T[K], TreatAsPrimitives>>
204
239
  : QueryTypeCasting<T[K]>;
205
240
  };
206
241
 
@@ -234,6 +269,9 @@ declare module 'mongoose' {
234
269
  /** Base Mongoose instance the model uses. */
235
270
  base: Mongoose;
236
271
 
272
+ /** Standard Schema adapter for validating input with this model's schema. */
273
+ readonly '~standard': StandardSchemaV1.Props<TRawDocType>;
274
+
237
275
  /**
238
276
  * If this is a discriminator model, `baseModelName` is the name of
239
277
  * the base model.
@@ -0,0 +1,10 @@
1
+ declare module 'mongoose' {
2
+ interface TracingContext {
3
+ operation: string;
4
+ collection: string;
5
+ database: string;
6
+ serverAddress: string;
7
+ serverPort: number;
8
+ args: Record<string, any>;
9
+ }
10
+ }