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 +4 -2
- package/lib/aggregate.js +51 -24
- package/lib/connection.js +1 -1
- package/lib/cursor/aggregationCursor.js +47 -16
- package/lib/cursor/queryCursor.js +23 -7
- package/lib/document.js +20 -4
- package/lib/error/divergentArray.js +1 -1
- package/lib/helpers/clone.js +22 -5
- package/lib/helpers/populate/getModelsMapForPopulate.js +5 -1
- package/lib/helpers/update/applyTimestampsToUpdate.js +4 -2
- package/lib/model.js +95 -17
- package/lib/mongoose.js +2 -2
- package/lib/query.js +44 -28
- package/lib/schema/date.js +7 -5
- package/lib/schema/documentArray.js +1 -1
- package/lib/schema/string.js +19 -2
- package/lib/schema/subdocument.js +1 -1
- package/lib/schema/union.js +2 -2
- package/lib/schemaType.js +2 -7
- package/lib/standardSchema/convertErrorToIssues.js +20 -0
- package/lib/tracing.js +38 -0
- package/package.json +6 -6
- package/types/document.d.ts +21 -4
- package/types/expressions.d.ts +16 -0
- package/types/index.d.ts +1 -0
- package/types/models.d.ts +42 -4
- package/types/tracing.d.ts +10 -0
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 {
|
|
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
|
|
1075
|
-
return
|
|
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
|
|
1094
|
-
|
|
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
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1118
|
+
if (!_this._pipeline.length) {
|
|
1119
|
+
throw new MongooseError('Aggregate has empty pipeline');
|
|
1120
|
+
}
|
|
1105
1121
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1122
|
+
const options = clone(_this.options || {});
|
|
1123
|
+
delete options.middleware;
|
|
1108
1124
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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
|
-
|
|
1118
|
-
|
|
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](
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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(
|
|
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 '
|
package/lib/helpers/clone.js
CHANGED
|
@@ -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(),
|
|
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(),
|
|
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
|
-
|
|
223
|
-
|
|
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
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
|
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 }
|
|
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=
|
|
402
|
-
* @param {number} [options.minPoolSize=
|
|
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
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
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
|
-
|
|
4796
|
+
let res;
|
|
4793
4797
|
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
+
let error = null;
|
|
4799
|
+
try {
|
|
4800
|
+
await _executePreHooks(_this);
|
|
4801
|
+
res = skipWrappedFunction ? skipWrappedFunction.args[0] : await _this[thunk]();
|
|
4798
4802
|
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
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
|
-
|
|
4810
|
-
|
|
4813
|
+
error = _this.model.schema._transformDuplicateKeyError(error);
|
|
4814
|
+
}
|
|
4811
4815
|
|
|
4812
|
-
|
|
4816
|
+
res = await _executePostHooks(_this, res, error);
|
|
4813
4817
|
|
|
4814
|
-
|
|
4818
|
+
await _this._hooks.execPost('exec', _this, []);
|
|
4815
4819
|
|
|
4816
|
-
|
|
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
|
/*!
|
package/lib/schema/date.js
CHANGED
|
@@ -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
|
|
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
|
-
* //
|
|
197
|
-
* mongoose.Schema.Types.
|
|
196
|
+
* // Disallow "invalid date"
|
|
197
|
+
* mongoose.Schema.Types.Date.checkRequired(v => v instanceof Date && !isNaN(v.valueOf()));
|
|
198
198
|
*
|
|
199
|
-
* const
|
|
200
|
-
*
|
|
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
|
|
317
|
+
const subdocValidateError = doc.$__validateSync(options);
|
|
318
318
|
|
|
319
319
|
if (subdocValidateError && resultError == null) {
|
|
320
320
|
resultError = subdocValidateError;
|
package/lib/schema/string.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/lib/schema/union.js
CHANGED
|
@@ -112,8 +112,8 @@ class Union extends SchemaType {
|
|
|
112
112
|
return schemaTypeError;
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
if (value != null && typeof value
|
|
116
|
-
return value
|
|
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({
|
|
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.
|
|
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.
|
|
42
|
+
"eslint": "10.4.1",
|
|
43
43
|
"eslint-plugin-mocha-no-only": "1.2.0",
|
|
44
|
-
"express": "
|
|
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.
|
|
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.
|
|
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": "
|
|
62
|
+
"sinon": "22.0.0",
|
|
63
63
|
"tstyche": "^7.0.0",
|
|
64
64
|
"typescript": "5.9.3",
|
|
65
65
|
"typescript-eslint": "^8.31.1",
|
package/types/document.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
}
|
package/types/expressions.d.ts
CHANGED
|
@@ -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.
|