mongoose 9.6.3 → 9.7.1
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/lib/aggregate.js +50 -23
- package/lib/connection.js +1 -1
- package/lib/cursor/aggregationCursor.js +47 -16
- package/lib/cursor/queryCursor.js +23 -7
- package/lib/document.js +49 -34
- package/lib/error/divergentArray.js +1 -1
- package/lib/helpers/document/applyDefaults.js +5 -0
- package/lib/helpers/populate/createPopulateQueryFilter.js +9 -1
- package/lib/helpers/populate/getModelsMapForPopulate.js +8 -6
- package/lib/helpers/populate/splitPopulateQuery.js +81 -0
- package/lib/helpers/schema/getKeysInSchemaOrder.js +13 -16
- package/lib/model.js +136 -28
- package/lib/plugins/saveSubdocs.js +16 -13
- package/lib/query.js +44 -28
- package/lib/schema/date.js +7 -5
- package/lib/schema/documentArray.js +1 -1
- package/lib/schema/objectId.js +7 -1
- package/lib/schema/subdocument.js +1 -1
- package/lib/schema/union.js +2 -2
- package/lib/schemaType.js +2 -1
- package/lib/standardSchema/convertErrorToIssues.js +20 -0
- package/lib/stateMachine.js +11 -6
- package/lib/tracing.js +38 -0
- package/package.json +8 -7
- package/types/document.d.ts +21 -4
- package/types/index.d.ts +1 -0
- package/types/models.d.ts +42 -4
- package/types/tracing.d.ts +10 -0
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');
|
|
@@ -66,6 +71,7 @@ const prepareDiscriminatorPipeline = require('./helpers/aggregate/prepareDiscrim
|
|
|
66
71
|
const pushNestedArrayPaths = require('./helpers/model/pushNestedArrayPaths');
|
|
67
72
|
const removeDeselectedForeignField = require('./helpers/populate/removeDeselectedForeignField');
|
|
68
73
|
const setDottedPath = require('./helpers/path/setDottedPath');
|
|
74
|
+
const splitPopulateQuery = require('./helpers/populate/splitPopulateQuery');
|
|
69
75
|
const { buildMiddlewareFilter } = require('./helpers/buildMiddlewareFilter');
|
|
70
76
|
const util = require('util');
|
|
71
77
|
const utils = require('./utils');
|
|
@@ -663,19 +669,29 @@ Model.prototype.save = async function save(options) {
|
|
|
663
669
|
|
|
664
670
|
this.$__.saveOptions = options;
|
|
665
671
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
672
|
+
const _this = this;
|
|
673
|
+
return traceSave(async function maybeTracedSave() {
|
|
674
|
+
try {
|
|
675
|
+
await _this.$__save(options);
|
|
676
|
+
} catch (error) {
|
|
677
|
+
_this.$__handleReject(error);
|
|
678
|
+
throw error;
|
|
679
|
+
} finally {
|
|
680
|
+
_this.$__.saving = null;
|
|
681
|
+
_this.$__.saveOptions = null;
|
|
682
|
+
_this.$__.$versionError = null;
|
|
683
|
+
_this.$op = null;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return _this;
|
|
687
|
+
}, () => ({
|
|
688
|
+
operation: 'save',
|
|
689
|
+
collection: _this.constructor.collection.name,
|
|
690
|
+
database: _this.constructor.db?.name,
|
|
691
|
+
serverAddress: _this.constructor.db?.host,
|
|
692
|
+
serverPort: _this.constructor.db?.port,
|
|
693
|
+
args: { options }
|
|
694
|
+
}));
|
|
679
695
|
};
|
|
680
696
|
|
|
681
697
|
Model.prototype.$save = Model.prototype.save;
|
|
@@ -3015,6 +3031,18 @@ Model.insertMany = async function insertMany(arr, options) {
|
|
|
3015
3031
|
throw new MongooseError('Model.insertMany() no longer accepts a callback');
|
|
3016
3032
|
}
|
|
3017
3033
|
|
|
3034
|
+
const ThisModel = this;
|
|
3035
|
+
return traceInsertMany(function maybeTracedInsertMany() { return _insertMany.call(ThisModel, arr, options); }, () => ({
|
|
3036
|
+
operation: 'insertMany',
|
|
3037
|
+
collection: ThisModel.collection.name,
|
|
3038
|
+
database: ThisModel.db?.name,
|
|
3039
|
+
serverAddress: ThisModel.db?.host,
|
|
3040
|
+
serverPort: ThisModel.db?.port,
|
|
3041
|
+
args: { docs: arr, options }
|
|
3042
|
+
}));
|
|
3043
|
+
};
|
|
3044
|
+
|
|
3045
|
+
async function _insertMany(arr, options) {
|
|
3018
3046
|
options = options || {};
|
|
3019
3047
|
const preFilter = buildMiddlewareFilter(options, 'pre');
|
|
3020
3048
|
const postFilter = buildMiddlewareFilter(options, 'post');
|
|
@@ -3250,7 +3278,7 @@ Model.insertMany = async function insertMany(arr, options) {
|
|
|
3250
3278
|
|
|
3251
3279
|
const [result] = await this._middleware.execPost('insertMany', this, [docAttributes], { filter: postFilter });
|
|
3252
3280
|
return result;
|
|
3253
|
-
}
|
|
3281
|
+
}
|
|
3254
3282
|
|
|
3255
3283
|
/*!
|
|
3256
3284
|
* ignore
|
|
@@ -3378,6 +3406,19 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
|
|
|
3378
3406
|
typeof arguments[2] === 'function') {
|
|
3379
3407
|
throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
|
|
3380
3408
|
}
|
|
3409
|
+
|
|
3410
|
+
const ThisModel = this;
|
|
3411
|
+
return traceBulkWrite(function maybeTracedBulkWrite() { return _bulkWrite.call(ThisModel, ops, options); }, () => ({
|
|
3412
|
+
operation: 'bulkWrite',
|
|
3413
|
+
collection: ThisModel.collection.name,
|
|
3414
|
+
database: ThisModel.db?.name,
|
|
3415
|
+
serverAddress: ThisModel.db?.host,
|
|
3416
|
+
serverPort: ThisModel.db?.port,
|
|
3417
|
+
args: { ops, options }
|
|
3418
|
+
}));
|
|
3419
|
+
};
|
|
3420
|
+
|
|
3421
|
+
async function _bulkWrite(ops, options) {
|
|
3381
3422
|
options = options || {};
|
|
3382
3423
|
const preFilter = buildMiddlewareFilter(options, 'pre');
|
|
3383
3424
|
const postFilter = buildMiddlewareFilter(options, 'post');
|
|
@@ -3516,7 +3557,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
|
|
|
3516
3557
|
await this.hooks.execPost('bulkWrite', this, [res], { filter: postFilter });
|
|
3517
3558
|
|
|
3518
3559
|
return res;
|
|
3519
|
-
}
|
|
3560
|
+
}
|
|
3520
3561
|
|
|
3521
3562
|
/**
|
|
3522
3563
|
* Takes an array of documents, gets the changes and inserts/updates documents in the database
|
|
@@ -3861,7 +3902,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
|
|
|
3861
3902
|
throw new MongooseError(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`);
|
|
3862
3903
|
}
|
|
3863
3904
|
if (options.validateBeforeSave == null || options.validateBeforeSave) {
|
|
3864
|
-
const err = document
|
|
3905
|
+
const err = document.$__validateSync();
|
|
3865
3906
|
if (err != null) {
|
|
3866
3907
|
throw err;
|
|
3867
3908
|
}
|
|
@@ -4188,13 +4229,24 @@ Model.aggregate = function aggregate(pipeline, options) {
|
|
|
4188
4229
|
* age: { type: Number, required: true }
|
|
4189
4230
|
* });
|
|
4190
4231
|
*
|
|
4232
|
+
* // Succeeds
|
|
4233
|
+
* await Model.validate({ name: 'John Smith', age: 31 });
|
|
4234
|
+
*
|
|
4191
4235
|
* try {
|
|
4192
|
-
* await Model.validate({ name: null }
|
|
4236
|
+
* await Model.validate({ name: null });
|
|
4193
4237
|
* } catch (err) {
|
|
4194
4238
|
* err instanceof mongoose.Error.ValidationError; // true
|
|
4195
4239
|
* Object.keys(err.errors); // ['name']
|
|
4196
4240
|
* }
|
|
4197
4241
|
*
|
|
4242
|
+
* Note: the `pathsToSkip` and `pathsToValidate` options **only** apply to validation, not
|
|
4243
|
+
* casting. This function will still throw an error for values that cannot be casted to the
|
|
4244
|
+
* schema-specified type. Remove any paths you do not want to cast.
|
|
4245
|
+
*
|
|
4246
|
+
* // The following will still throw an error because the value of `age` cannot be
|
|
4247
|
+
* // casted to a number. Remove the `age` property before calling `validate()`.
|
|
4248
|
+
* await Model.validate({ name: 'Test', age: 'not a number' }, ['name']);
|
|
4249
|
+
*
|
|
4198
4250
|
* @param {object} obj
|
|
4199
4251
|
* @param {object|Array|string} pathsOrOptions
|
|
4200
4252
|
* @param {object} [context]
|
|
@@ -4299,6 +4351,33 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
|
|
|
4299
4351
|
return obj;
|
|
4300
4352
|
};
|
|
4301
4353
|
|
|
4354
|
+
/**
|
|
4355
|
+
* Standard Schema adapter for this model.
|
|
4356
|
+
* Calls [`Model.validate()`](https://mongoosejs.com/docs/api/model.html#Model.validate()) internally with the provided `libraryOptions`
|
|
4357
|
+
* to cast and validate the given value.
|
|
4358
|
+
*
|
|
4359
|
+
* @api public
|
|
4360
|
+
* @property ~standard
|
|
4361
|
+
* @memberOf Model
|
|
4362
|
+
* @static
|
|
4363
|
+
*/
|
|
4364
|
+
|
|
4365
|
+
Object.defineProperty(Model, '~standard', {
|
|
4366
|
+
configurable: true,
|
|
4367
|
+
get() {
|
|
4368
|
+
return {
|
|
4369
|
+
version: 1,
|
|
4370
|
+
vendor: 'mongoose',
|
|
4371
|
+
validate: (value, options) => {
|
|
4372
|
+
return this.validate(value, options?.libraryOptions).then(
|
|
4373
|
+
value => ({ value }),
|
|
4374
|
+
error => ({ issues: convertErrorToStandardSchemaIssues(error) })
|
|
4375
|
+
);
|
|
4376
|
+
}
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
});
|
|
4380
|
+
|
|
4302
4381
|
/**
|
|
4303
4382
|
* Populates document references.
|
|
4304
4383
|
*
|
|
@@ -4465,7 +4544,6 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4465
4544
|
mod.foreignField.clear();
|
|
4466
4545
|
mod.foreignField.add(populateOptions.foreignField);
|
|
4467
4546
|
}
|
|
4468
|
-
const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
|
|
4469
4547
|
if (assignmentOpts.excludeId) {
|
|
4470
4548
|
// override the exclusion from the query so we can use the _id
|
|
4471
4549
|
// for document matching during assignment. we'll delete the
|
|
@@ -4486,6 +4564,17 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4486
4564
|
} else if (mod.options.limit != null) {
|
|
4487
4565
|
assignmentOpts.originalLimit = mod.options.limit;
|
|
4488
4566
|
}
|
|
4567
|
+
|
|
4568
|
+
// Execute a separate query per document if `perDocumentLimit` is set (gh-7318), or if
|
|
4569
|
+
// there are so many ids that the `$in` filter may overflow MongoDB's 16 MB BSON size
|
|
4570
|
+
// limit on queries (gh-5890).
|
|
4571
|
+
const splitParams = splitPopulateQuery(mod, ids, select, assignmentOpts);
|
|
4572
|
+
if (splitParams != null) {
|
|
4573
|
+
params.push(...splitParams);
|
|
4574
|
+
continue;
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
|
|
4489
4578
|
params.push([mod, match, select, assignmentOpts]);
|
|
4490
4579
|
}
|
|
4491
4580
|
if (!hasOne) {
|
|
@@ -4506,6 +4595,9 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4506
4595
|
|
|
4507
4596
|
// Track deferred populates per-param (per model) to avoid mixing them
|
|
4508
4597
|
const deferredPopulatesPerParam = new Map();
|
|
4598
|
+
// Query results per param. Batches that were split off of a large populate query (gh-5890)
|
|
4599
|
+
// are assigned from only their own query's results rather than the combined `vals`.
|
|
4600
|
+
const valsByParam = [];
|
|
4509
4601
|
|
|
4510
4602
|
if (populateOptions.ordered) {
|
|
4511
4603
|
// Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
|
|
@@ -4513,7 +4605,10 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4513
4605
|
for (let i = 0; i < params.length; i++) {
|
|
4514
4606
|
const arr = params[i];
|
|
4515
4607
|
const { docs, deferredPopulates } = await _execPopulateQuery.apply(null, arr);
|
|
4516
|
-
|
|
4608
|
+
valsByParam.push(docs);
|
|
4609
|
+
if (!arr[0]._assignFromOwnResults) {
|
|
4610
|
+
vals = vals.concat(docs);
|
|
4611
|
+
}
|
|
4517
4612
|
if (deferredPopulates.length > 0) {
|
|
4518
4613
|
deferredPopulatesPerParam.set(i, deferredPopulates);
|
|
4519
4614
|
}
|
|
@@ -4528,7 +4623,10 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4528
4623
|
const results = await Promise.all(promises);
|
|
4529
4624
|
for (let i = 0; i < results.length; i++) {
|
|
4530
4625
|
const { docs, deferredPopulates } = results[i];
|
|
4531
|
-
|
|
4626
|
+
valsByParam.push(docs);
|
|
4627
|
+
if (!params[i][0]._assignFromOwnResults) {
|
|
4628
|
+
vals = vals.concat(docs);
|
|
4629
|
+
}
|
|
4532
4630
|
if (deferredPopulates.length > 0) {
|
|
4533
4631
|
deferredPopulatesPerParam.set(i, deferredPopulates);
|
|
4534
4632
|
}
|
|
@@ -4536,13 +4634,15 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4536
4634
|
}
|
|
4537
4635
|
|
|
4538
4636
|
|
|
4539
|
-
for (
|
|
4637
|
+
for (let i = 0; i < params.length; i++) {
|
|
4638
|
+
const arr = params[i];
|
|
4540
4639
|
const mod = arr[0];
|
|
4541
4640
|
const assignmentOpts = arr[3];
|
|
4542
|
-
|
|
4641
|
+
const valsForMod = mod._assignFromOwnResults ? valsByParam[i] : vals;
|
|
4642
|
+
for (const val of valsForMod) {
|
|
4543
4643
|
mod.options._childDocs.push(val);
|
|
4544
4644
|
}
|
|
4545
|
-
_assign(model,
|
|
4645
|
+
_assign(model, valsForMod, mod, assignmentOpts);
|
|
4546
4646
|
}
|
|
4547
4647
|
|
|
4548
4648
|
// Handle deferred populate for cases with per-document match functions.
|
|
@@ -4582,13 +4682,15 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4582
4682
|
}
|
|
4583
4683
|
}
|
|
4584
4684
|
|
|
4585
|
-
for (
|
|
4586
|
-
|
|
4685
|
+
for (let i = 0; i < params.length; i++) {
|
|
4686
|
+
const mod = params[i][0];
|
|
4687
|
+
removeDeselectedForeignField(mod.foreignField, mod.options, mod._assignFromOwnResults ? valsByParam[i] : vals);
|
|
4587
4688
|
}
|
|
4588
|
-
for (
|
|
4589
|
-
const mod =
|
|
4689
|
+
for (let i = 0; i < params.length; i++) {
|
|
4690
|
+
const mod = params[i][0];
|
|
4590
4691
|
if (mod.options?.options?._leanTransform) {
|
|
4591
|
-
|
|
4692
|
+
const valsForMod = mod._assignFromOwnResults ? valsByParam[i] : vals;
|
|
4693
|
+
for (const doc of valsForMod) {
|
|
4592
4694
|
mod.options.options._leanTransform(doc);
|
|
4593
4695
|
}
|
|
4594
4696
|
}
|
|
@@ -4600,6 +4702,12 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4600
4702
|
*/
|
|
4601
4703
|
|
|
4602
4704
|
function _execPopulateQuery(mod, match, select) {
|
|
4705
|
+
// `null` match means `mod`'s documents have no ids to query, possible if a large populate
|
|
4706
|
+
// was split into a query per document (gh-5890). Skip executing a query: `_assign()` still
|
|
4707
|
+
// sets the documents' populated paths to the appropriate default value.
|
|
4708
|
+
if (match == null) {
|
|
4709
|
+
return Promise.resolve({ docs: [], deferredPopulates: [] });
|
|
4710
|
+
}
|
|
4603
4711
|
let subPopulate = clone(mod.options.populate);
|
|
4604
4712
|
const queryOptions = {};
|
|
4605
4713
|
if (mod.options.skip !== undefined) {
|
|
@@ -16,7 +16,10 @@ module.exports = function saveSubdocs(schema) {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
async
|
|
19
|
+
// These hooks are deliberately not `async` so that they don't allocate a
|
|
20
|
+
// promise and force an extra microtask hop on every `save()` when there are
|
|
21
|
+
// no subdocuments. kareem only awaits hooks that return a promise.
|
|
22
|
+
function saveSubdocsPreSave() {
|
|
20
23
|
if (this.$isSubdocument) {
|
|
21
24
|
return;
|
|
22
25
|
}
|
|
@@ -28,15 +31,15 @@ async function saveSubdocsPreSave() {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
const options = this.$__.saveOptions;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
34
|
+
return Promise.all(subdocs.map(subdoc => subdoc._execDocumentPreHooks('save', options, [options]))).then(() => {
|
|
35
|
+
// Invalidate subdocs cache because subdoc pre hooks can add new subdocuments
|
|
36
|
+
if (this.$__.saveOptions) {
|
|
37
|
+
this.$__.saveOptions.__subdocs = null;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
function saveSubdocsPostSave() {
|
|
40
43
|
if (this.$isSubdocument) {
|
|
41
44
|
return;
|
|
42
45
|
}
|
|
@@ -53,10 +56,10 @@ async function saveSubdocsPostSave() {
|
|
|
53
56
|
promises.push(subdoc._execDocumentPostHooks('save', options));
|
|
54
57
|
}
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
return Promise.all(promises);
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
function saveSubdocsPreDeleteOne() {
|
|
60
63
|
const removedSubdocs = this.$__.removedSubdocs;
|
|
61
64
|
if (!removedSubdocs?.length) {
|
|
62
65
|
return;
|
|
@@ -68,10 +71,10 @@ async function saveSubdocsPreDeleteOne() {
|
|
|
68
71
|
promises.push(subdoc._execDocumentPreHooks('deleteOne', options));
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
return Promise.all(promises);
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
function saveSubdocsPostDeleteOne() {
|
|
75
78
|
const removedSubdocs = this.$__.removedSubdocs;
|
|
76
79
|
if (!removedSubdocs?.length) {
|
|
77
80
|
return;
|
|
@@ -84,7 +87,7 @@ async function saveSubdocsPostDeleteOne() {
|
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
this.$__.removedSubdocs = null;
|
|
87
|
-
|
|
90
|
+
return Promise.all(promises);
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
|
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/objectId.js
CHANGED
|
@@ -317,7 +317,13 @@ function resetId(v) {
|
|
|
317
317
|
*/
|
|
318
318
|
|
|
319
319
|
SchemaObjectId.prototype.toJSONSchema = function toJSONSchema(options) {
|
|
320
|
-
|
|
320
|
+
const jsonSchema = this._createJSONSchemaTypeDefinition('string', 'objectId', options);
|
|
321
|
+
// `bsonType: 'objectId'` already validates ObjectIds, so the regex is only useful
|
|
322
|
+
// for the portable `type: 'string'` representation (e.g. for ajv, zod, etc.)
|
|
323
|
+
if (!options?.useBsonType) {
|
|
324
|
+
jsonSchema.pattern = '^[A-Fa-f0-9]{24}$';
|
|
325
|
+
}
|
|
326
|
+
return jsonSchema;
|
|
321
327
|
};
|
|
322
328
|
|
|
323
329
|
SchemaObjectId.prototype.autoEncryptionType = function autoEncryptionType() {
|
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
|
|
@@ -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/stateMachine.js
CHANGED
|
@@ -69,8 +69,10 @@ StateMachine.prototype._changeState = function _changeState(path, nextState) {
|
|
|
69
69
|
if (prevState === nextState) {
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
if (prevState !== undefined) {
|
|
73
|
+
const prevBucket = this.states[prevState];
|
|
74
|
+
if (prevBucket) delete prevBucket[path];
|
|
75
|
+
}
|
|
74
76
|
|
|
75
77
|
this.paths[path] = nextState;
|
|
76
78
|
this.states[nextState] = this.states[nextState] || {};
|
|
@@ -86,13 +88,16 @@ StateMachine.prototype.clear = function clear(state) {
|
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
90
|
const keys = Object.keys(this.states[state]);
|
|
91
|
+
if (keys.length === 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Replace the bucket rather than deleting each key: repeated `delete` puts
|
|
95
|
+
// the object in dictionary mode, which is significantly slower.
|
|
96
|
+
this.states[state] = {};
|
|
89
97
|
let i = keys.length;
|
|
90
|
-
let path;
|
|
91
98
|
|
|
92
99
|
while (i--) {
|
|
93
|
-
|
|
94
|
-
delete this.states[state][path];
|
|
95
|
-
delete this.paths[path];
|
|
100
|
+
delete this.paths[keys[i]];
|
|
96
101
|
}
|
|
97
102
|
};
|
|
98
103
|
|
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
|
+
};
|