@depup/mongoose 9.4.1-depup.0 → 9.8.0-depup.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 +3 -9
- package/changes.json +3 -8
- package/eslint.config.mjs +4 -1
- package/lib/aggregate.js +54 -24
- package/lib/cast.js +1 -1
- package/lib/connection.js +1 -1
- package/lib/cursor/aggregationCursor.js +47 -16
- package/lib/cursor/queryCursor.js +23 -7
- package/lib/document.js +133 -58
- package/lib/drivers/node-mongodb-native/collection.js +10 -4
- package/lib/error/cast.js +18 -9
- package/lib/error/divergentArray.js +1 -1
- package/lib/error/messages.js +1 -0
- package/lib/helpers/clone.js +22 -5
- 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/query/castUpdate.js +4 -0
- package/lib/helpers/schema/getKeysInSchemaOrder.js +13 -16
- package/lib/helpers/update/applyTimestampsToUpdate.js +4 -2
- package/lib/model.js +153 -38
- package/lib/mongoose.js +3 -3
- package/lib/options/schemaTypeOptions.js +14 -0
- package/lib/plugins/saveSubdocs.js +16 -13
- package/lib/query.js +208 -102
- package/lib/queryHelpers.js +13 -12
- package/lib/schema/array.js +3 -3
- package/lib/schema/date.js +7 -5
- package/lib/schema/documentArray.js +1 -1
- package/lib/schema/objectId.js +7 -1
- package/lib/schema/string.js +19 -2
- package/lib/schema/subdocument.js +1 -1
- package/lib/schema/union.js +2 -2
- package/lib/schema.js +38 -12
- package/lib/schemaType.js +58 -10
- package/lib/standardSchema/convertErrorToIssues.js +20 -0
- package/lib/stateMachine.js +11 -6
- package/lib/tracing.js +38 -0
- package/lib/types/documentArray/methods/index.js +117 -3
- package/lib/types/subdocument.js +4 -2
- package/lib/validOptions.js +1 -0
- package/package.json +27 -30
- package/tstyche.json +2 -1
- package/types/augmentations.d.ts +2 -2
- package/types/document.d.ts +21 -4
- package/types/expressions.d.ts +16 -0
- package/types/index.d.ts +13 -10
- package/types/inferhydrateddoctype.d.ts +1 -1
- package/types/inferrawdoctype.d.ts +6 -3
- package/types/inferschematype.d.ts +10 -2
- package/types/models.d.ts +28 -11
- package/types/mongooseoptions.d.ts +9 -1
- package/types/populate.d.ts +8 -0
- package/types/query.d.ts +13 -3
- package/types/schemaoptions.d.ts +5 -0
- package/types/schematypes.d.ts +10 -0
- package/types/tracing.d.ts +10 -0
- package/types/utility.d.ts +9 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const createPopulateQueryFilter = require('./createPopulateQueryFilter');
|
|
4
|
+
const get = require('../get');
|
|
5
|
+
const utils = require('../../utils');
|
|
6
|
+
|
|
7
|
+
module.exports = splitPopulateQuery;
|
|
8
|
+
|
|
9
|
+
/*!
|
|
10
|
+
* If a single populate query would have more than this many elements in its `$in` filter,
|
|
11
|
+
* Mongoose splits the populate into a separate query per document to avoid going over
|
|
12
|
+
* MongoDB's 16 MB BSON size limit on queries. Overwritable for testing purposes. See gh-5890.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
splitPopulateQuery.maxInFilterLength = 50000;
|
|
16
|
+
|
|
17
|
+
/*!
|
|
18
|
+
* Split a populate models-map entry into a separate query per document if either:
|
|
19
|
+
*
|
|
20
|
+
* 1. The `perDocumentLimit` option is set, so each document needs its own query with its
|
|
21
|
+
* own `limit` (gh-7318), or
|
|
22
|
+
* 2. A single populate query for `mod` would have too many elements in its `$in` filter
|
|
23
|
+
* (gh-5890). With multiple foreign fields, `createPopulateQueryFilter()` repeats the ids
|
|
24
|
+
* under `$or` once per foreign field, so the threshold counts one copy of `ids` per
|
|
25
|
+
* foreign field.
|
|
26
|
+
*
|
|
27
|
+
* Returns a list of `[mod, match, select, assignmentOpts]` params, one per document, for
|
|
28
|
+
* `_execPopulateQuery()`. Returns `null` if the populate query doesn't need to be split.
|
|
29
|
+
* A `null` `match` means the document has no ids to query: `_execPopulateQuery()` skips
|
|
30
|
+
* executing a query, and `_assign()` just sets the document's populated path to the
|
|
31
|
+
* default value.
|
|
32
|
+
*
|
|
33
|
+
* Splitting on document boundaries means each document's populated value is the result of
|
|
34
|
+
* exactly one query, so split entries can typically be assigned from only their own query's
|
|
35
|
+
* results (`_assignFromOwnResults`) rather than scanning every populate query's results.
|
|
36
|
+
* refPath is the exception: a single document's array can contain ids for multiple models,
|
|
37
|
+
* so assigning refPath populate results relies on every query's results being available for
|
|
38
|
+
* every document. refPath entries are therefore only split when `perDocumentLimit` requires
|
|
39
|
+
* it, not to keep the `$in` filter small. A single document whose ids alone overflow the
|
|
40
|
+
* BSON size limit cannot be split.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
function splitPopulateQuery(mod, ids, select, assignmentOpts) {
|
|
44
|
+
if (mod.docs.length <= 1) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const perDocumentLimit = mod.options.perDocumentLimit == null ?
|
|
48
|
+
get(mod.options, 'options.perDocumentLimit', null) :
|
|
49
|
+
mod.options.perDocumentLimit;
|
|
50
|
+
const numInFilterElements = ids.length * mod.foreignField.size;
|
|
51
|
+
if (perDocumentLimit == null && (numInFilterElements <= splitPopulateQuery.maxInFilterLength || mod.isRefPath)) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return mod.docs.map((doc, i) => {
|
|
56
|
+
const subMod = {
|
|
57
|
+
...mod,
|
|
58
|
+
docs: [doc],
|
|
59
|
+
ids: [mod.ids[i]],
|
|
60
|
+
allIds: [mod.allIds[i]],
|
|
61
|
+
unpopulatedValues: [mod.unpopulatedValues[i]],
|
|
62
|
+
match: Array.isArray(mod.match) ? [mod.match[i]] : mod.match,
|
|
63
|
+
_assignFromOwnResults: !mod.isRefPath
|
|
64
|
+
};
|
|
65
|
+
let subIds = utils.array.flatten(subMod.ids, flatten);
|
|
66
|
+
subIds = utils.array.unique(subIds);
|
|
67
|
+
const match = subIds.length === 0 || subIds.every(utils.isNullOrUndefined) ?
|
|
68
|
+
null :
|
|
69
|
+
createPopulateQueryFilter(subIds, subMod.match, subMod.foreignField, subMod.model, subMod.options.skipInvalidIds);
|
|
70
|
+
return [subMod, match, select, assignmentOpts];
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/*!
|
|
75
|
+
* ignore
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
function flatten(item) {
|
|
79
|
+
// no need to include undefined values in our query
|
|
80
|
+
return undefined !== item;
|
|
81
|
+
}
|
|
@@ -98,6 +98,10 @@ module.exports = function castUpdate(schema, obj, options, context, filter) {
|
|
|
98
98
|
moveImmutableProperties(schema, obj, context);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
if (obj?.$__ && typeof obj.toObject === 'function') {
|
|
102
|
+
obj = obj.toObject(internalToObjectOptions);
|
|
103
|
+
}
|
|
104
|
+
|
|
101
105
|
const ops = Object.keys(obj);
|
|
102
106
|
let i = ops.length;
|
|
103
107
|
const ret = {};
|
|
@@ -3,25 +3,22 @@
|
|
|
3
3
|
const get = require('../get');
|
|
4
4
|
|
|
5
5
|
module.exports = function getKeysInSchemaOrder(schema, val, path) {
|
|
6
|
+
const valKeys = Object.keys(val);
|
|
7
|
+
if (valKeys.length <= 1) {
|
|
8
|
+
return valKeys;
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
const schemaKeys = path != null ? Object.keys(get(schema.tree, path, {})) : Object.keys(schema.tree);
|
|
7
|
-
const
|
|
12
|
+
const remaining = new Set(valKeys);
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (valKeys.has(key)) {
|
|
14
|
-
keys.add(key);
|
|
15
|
-
}
|
|
14
|
+
const keys = [];
|
|
15
|
+
for (const key of schemaKeys) {
|
|
16
|
+
if (remaining.delete(key)) {
|
|
17
|
+
keys.push(key);
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
keys = Array.from(keys);
|
|
23
|
-
} else {
|
|
24
|
-
keys = Array.from(valKeys);
|
|
19
|
+
}
|
|
20
|
+
for (const key of remaining) {
|
|
21
|
+
keys.push(key);
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
return keys;
|
|
@@ -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');
|
|
@@ -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;
|
|
@@ -2325,7 +2341,7 @@ Model.where = function where(path, val) {
|
|
|
2325
2341
|
*
|
|
2326
2342
|
* Sometimes you need to query for things in mongodb using a JavaScript expression. You can do so via `find({ $where: javascript })`, or you can use the mongoose shortcut method $where via a Query chain or from your mongoose Model.
|
|
2327
2343
|
*
|
|
2328
|
-
* Blog.$where('this.username.indexOf("val") !== -1').exec(
|
|
2344
|
+
* const result = await Blog.$where('this.username.indexOf("val") !== -1').exec();
|
|
2329
2345
|
*
|
|
2330
2346
|
* @param {string|Function} argument is a javascript string or anonymous function
|
|
2331
2347
|
* @method $where
|
|
@@ -2418,13 +2434,6 @@ Model.findOneAndUpdate = function(conditions, update, options) {
|
|
|
2418
2434
|
fields = options.fields || options.projection;
|
|
2419
2435
|
}
|
|
2420
2436
|
|
|
2421
|
-
update = clone(update, {
|
|
2422
|
-
depopulate: true,
|
|
2423
|
-
_isNested: true
|
|
2424
|
-
});
|
|
2425
|
-
|
|
2426
|
-
decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey);
|
|
2427
|
-
|
|
2428
2437
|
const mq = new this.Query({}, {}, this, this.$__collection);
|
|
2429
2438
|
mq.select(fields);
|
|
2430
2439
|
|
|
@@ -2864,6 +2873,10 @@ Model.create = async function create(doc, options) {
|
|
|
2864
2873
|
Model.insertOne = async function insertOne(doc, options) {
|
|
2865
2874
|
_checkContext(this, 'insertOne');
|
|
2866
2875
|
|
|
2876
|
+
if (doc == null || typeof doc !== 'object') {
|
|
2877
|
+
throw new ObjectParameterError(doc, 'doc', 'insertOne');
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2867
2880
|
const discriminatorKey = this.schema.options.discriminatorKey;
|
|
2868
2881
|
const Model = this.discriminators && doc[discriminatorKey] != null ?
|
|
2869
2882
|
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
|
|
@@ -3018,6 +3031,18 @@ Model.insertMany = async function insertMany(arr, options) {
|
|
|
3018
3031
|
throw new MongooseError('Model.insertMany() no longer accepts a callback');
|
|
3019
3032
|
}
|
|
3020
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) {
|
|
3021
3046
|
options = options || {};
|
|
3022
3047
|
const preFilter = buildMiddlewareFilter(options, 'pre');
|
|
3023
3048
|
const postFilter = buildMiddlewareFilter(options, 'post');
|
|
@@ -3253,7 +3278,7 @@ Model.insertMany = async function insertMany(arr, options) {
|
|
|
3253
3278
|
|
|
3254
3279
|
const [result] = await this._middleware.execPost('insertMany', this, [docAttributes], { filter: postFilter });
|
|
3255
3280
|
return result;
|
|
3256
|
-
}
|
|
3281
|
+
}
|
|
3257
3282
|
|
|
3258
3283
|
/*!
|
|
3259
3284
|
* ignore
|
|
@@ -3381,6 +3406,19 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
|
|
|
3381
3406
|
typeof arguments[2] === 'function') {
|
|
3382
3407
|
throw new MongooseError('Model.bulkWrite() no longer accepts a callback');
|
|
3383
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) {
|
|
3384
3422
|
options = options || {};
|
|
3385
3423
|
const preFilter = buildMiddlewareFilter(options, 'pre');
|
|
3386
3424
|
const postFilter = buildMiddlewareFilter(options, 'post');
|
|
@@ -3519,7 +3557,7 @@ Model.bulkWrite = async function bulkWrite(ops, options) {
|
|
|
3519
3557
|
await this.hooks.execPost('bulkWrite', this, [res], { filter: postFilter });
|
|
3520
3558
|
|
|
3521
3559
|
return res;
|
|
3522
|
-
}
|
|
3560
|
+
}
|
|
3523
3561
|
|
|
3524
3562
|
/**
|
|
3525
3563
|
* Takes an array of documents, gets the changes and inserts/updates documents in the database
|
|
@@ -3618,12 +3656,16 @@ async function buildPreSavePromise(document, options) {
|
|
|
3618
3656
|
}
|
|
3619
3657
|
|
|
3620
3658
|
async function handleSuccessfulWrite(document, options) {
|
|
3621
|
-
|
|
3659
|
+
const wasNew = document.$isNew;
|
|
3660
|
+
if (wasNew) {
|
|
3622
3661
|
_setIsNew(document, false);
|
|
3623
3662
|
}
|
|
3624
3663
|
|
|
3625
3664
|
document.$__reset();
|
|
3626
|
-
document
|
|
3665
|
+
// version key gets initialized to 0 when inserting a new document so avoid incrementing version key here
|
|
3666
|
+
if (!wasNew) {
|
|
3667
|
+
document._applyVersionIncrement();
|
|
3668
|
+
}
|
|
3627
3669
|
const postFilter = buildMiddlewareFilter(options, 'post');
|
|
3628
3670
|
return document.schema.s.hooks.execPost('save', document, [document], { filter: postFilter });
|
|
3629
3671
|
}
|
|
@@ -3864,7 +3906,7 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
|
|
|
3864
3906
|
throw new MongooseError(`documents.${i} was not a mongoose document, documents must be an array of mongoose documents (instanceof mongoose.Document).`);
|
|
3865
3907
|
}
|
|
3866
3908
|
if (options.validateBeforeSave == null || options.validateBeforeSave) {
|
|
3867
|
-
const err = document
|
|
3909
|
+
const err = document.$__validateSync();
|
|
3868
3910
|
if (err != null) {
|
|
3869
3911
|
throw err;
|
|
3870
3912
|
}
|
|
@@ -3873,6 +3915,12 @@ Model.buildBulkWriteOperations = function buildBulkWriteOperations(documents, op
|
|
|
3873
3915
|
|
|
3874
3916
|
const isANewDocument = document.isNew;
|
|
3875
3917
|
if (isANewDocument) {
|
|
3918
|
+
// Like `$__version()` on insert, initialize the version key so
|
|
3919
|
+
// inserted documents get the same shape as `save()` gives them
|
|
3920
|
+
const versionKey = document.$__schema.options.versionKey;
|
|
3921
|
+
if (versionKey) {
|
|
3922
|
+
document.$__setValue(versionKey, 0);
|
|
3923
|
+
}
|
|
3876
3924
|
const writeOperation = { insertOne: { document } };
|
|
3877
3925
|
utils.injectTimestampsOption(writeOperation.insertOne, options.timestamps);
|
|
3878
3926
|
return writeOperation;
|
|
@@ -4191,13 +4239,24 @@ Model.aggregate = function aggregate(pipeline, options) {
|
|
|
4191
4239
|
* age: { type: Number, required: true }
|
|
4192
4240
|
* });
|
|
4193
4241
|
*
|
|
4242
|
+
* // Succeeds
|
|
4243
|
+
* await Model.validate({ name: 'John Smith', age: 31 });
|
|
4244
|
+
*
|
|
4194
4245
|
* try {
|
|
4195
|
-
* await Model.validate({ name: null }
|
|
4246
|
+
* await Model.validate({ name: null });
|
|
4196
4247
|
* } catch (err) {
|
|
4197
4248
|
* err instanceof mongoose.Error.ValidationError; // true
|
|
4198
4249
|
* Object.keys(err.errors); // ['name']
|
|
4199
4250
|
* }
|
|
4200
4251
|
*
|
|
4252
|
+
* Note: the `pathsToSkip` and `pathsToValidate` options **only** apply to validation, not
|
|
4253
|
+
* casting. This function will still throw an error for values that cannot be casted to the
|
|
4254
|
+
* schema-specified type. Remove any paths you do not want to cast.
|
|
4255
|
+
*
|
|
4256
|
+
* // The following will still throw an error because the value of `age` cannot be
|
|
4257
|
+
* // casted to a number. Remove the `age` property before calling `validate()`.
|
|
4258
|
+
* await Model.validate({ name: 'Test', age: 'not a number' }, ['name']);
|
|
4259
|
+
*
|
|
4201
4260
|
* @param {object} obj
|
|
4202
4261
|
* @param {object|Array|string} pathsOrOptions
|
|
4203
4262
|
* @param {object} [context]
|
|
@@ -4302,6 +4361,33 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
|
|
|
4302
4361
|
return obj;
|
|
4303
4362
|
};
|
|
4304
4363
|
|
|
4364
|
+
/**
|
|
4365
|
+
* Standard Schema adapter for this model.
|
|
4366
|
+
* Calls [`Model.validate()`](https://mongoosejs.com/docs/api/model.html#Model.validate()) internally with the provided `libraryOptions`
|
|
4367
|
+
* to cast and validate the given value.
|
|
4368
|
+
*
|
|
4369
|
+
* @api public
|
|
4370
|
+
* @property ~standard
|
|
4371
|
+
* @memberOf Model
|
|
4372
|
+
* @static
|
|
4373
|
+
*/
|
|
4374
|
+
|
|
4375
|
+
Object.defineProperty(Model, '~standard', {
|
|
4376
|
+
configurable: true,
|
|
4377
|
+
get() {
|
|
4378
|
+
return {
|
|
4379
|
+
version: 1,
|
|
4380
|
+
vendor: 'mongoose',
|
|
4381
|
+
validate: (value, options) => {
|
|
4382
|
+
return this.validate(value, options?.libraryOptions).then(
|
|
4383
|
+
value => ({ value }),
|
|
4384
|
+
error => ({ issues: convertErrorToStandardSchemaIssues(error) })
|
|
4385
|
+
);
|
|
4386
|
+
}
|
|
4387
|
+
};
|
|
4388
|
+
}
|
|
4389
|
+
});
|
|
4390
|
+
|
|
4305
4391
|
/**
|
|
4306
4392
|
* Populates document references.
|
|
4307
4393
|
*
|
|
@@ -4468,7 +4554,6 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4468
4554
|
mod.foreignField.clear();
|
|
4469
4555
|
mod.foreignField.add(populateOptions.foreignField);
|
|
4470
4556
|
}
|
|
4471
|
-
const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
|
|
4472
4557
|
if (assignmentOpts.excludeId) {
|
|
4473
4558
|
// override the exclusion from the query so we can use the _id
|
|
4474
4559
|
// for document matching during assignment. we'll delete the
|
|
@@ -4489,6 +4574,17 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4489
4574
|
} else if (mod.options.limit != null) {
|
|
4490
4575
|
assignmentOpts.originalLimit = mod.options.limit;
|
|
4491
4576
|
}
|
|
4577
|
+
|
|
4578
|
+
// Execute a separate query per document if `perDocumentLimit` is set (gh-7318), or if
|
|
4579
|
+
// there are so many ids that the `$in` filter may overflow MongoDB's 16 MB BSON size
|
|
4580
|
+
// limit on queries (gh-5890).
|
|
4581
|
+
const splitParams = splitPopulateQuery(mod, ids, select, assignmentOpts);
|
|
4582
|
+
if (splitParams != null) {
|
|
4583
|
+
params.push(...splitParams);
|
|
4584
|
+
continue;
|
|
4585
|
+
}
|
|
4586
|
+
|
|
4587
|
+
const match = createPopulateQueryFilter(ids, mod.match, mod.foreignField, mod.model, mod.options.skipInvalidIds);
|
|
4492
4588
|
params.push([mod, match, select, assignmentOpts]);
|
|
4493
4589
|
}
|
|
4494
4590
|
if (!hasOne) {
|
|
@@ -4509,6 +4605,9 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4509
4605
|
|
|
4510
4606
|
// Track deferred populates per-param (per model) to avoid mixing them
|
|
4511
4607
|
const deferredPopulatesPerParam = new Map();
|
|
4608
|
+
// Query results per param. Batches that were split off of a large populate query (gh-5890)
|
|
4609
|
+
// are assigned from only their own query's results rather than the combined `vals`.
|
|
4610
|
+
const valsByParam = [];
|
|
4512
4611
|
|
|
4513
4612
|
if (populateOptions.ordered) {
|
|
4514
4613
|
// Populate in series, primarily for transactions because MongoDB doesn't support multiple operations on
|
|
@@ -4516,7 +4615,10 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4516
4615
|
for (let i = 0; i < params.length; i++) {
|
|
4517
4616
|
const arr = params[i];
|
|
4518
4617
|
const { docs, deferredPopulates } = await _execPopulateQuery.apply(null, arr);
|
|
4519
|
-
|
|
4618
|
+
valsByParam.push(docs);
|
|
4619
|
+
if (!arr[0]._assignFromOwnResults) {
|
|
4620
|
+
vals = vals.concat(docs);
|
|
4621
|
+
}
|
|
4520
4622
|
if (deferredPopulates.length > 0) {
|
|
4521
4623
|
deferredPopulatesPerParam.set(i, deferredPopulates);
|
|
4522
4624
|
}
|
|
@@ -4531,7 +4633,10 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4531
4633
|
const results = await Promise.all(promises);
|
|
4532
4634
|
for (let i = 0; i < results.length; i++) {
|
|
4533
4635
|
const { docs, deferredPopulates } = results[i];
|
|
4534
|
-
|
|
4636
|
+
valsByParam.push(docs);
|
|
4637
|
+
if (!params[i][0]._assignFromOwnResults) {
|
|
4638
|
+
vals = vals.concat(docs);
|
|
4639
|
+
}
|
|
4535
4640
|
if (deferredPopulates.length > 0) {
|
|
4536
4641
|
deferredPopulatesPerParam.set(i, deferredPopulates);
|
|
4537
4642
|
}
|
|
@@ -4539,13 +4644,15 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4539
4644
|
}
|
|
4540
4645
|
|
|
4541
4646
|
|
|
4542
|
-
for (
|
|
4647
|
+
for (let i = 0; i < params.length; i++) {
|
|
4648
|
+
const arr = params[i];
|
|
4543
4649
|
const mod = arr[0];
|
|
4544
4650
|
const assignmentOpts = arr[3];
|
|
4545
|
-
|
|
4651
|
+
const valsForMod = mod._assignFromOwnResults ? valsByParam[i] : vals;
|
|
4652
|
+
for (const val of valsForMod) {
|
|
4546
4653
|
mod.options._childDocs.push(val);
|
|
4547
4654
|
}
|
|
4548
|
-
_assign(model,
|
|
4655
|
+
_assign(model, valsForMod, mod, assignmentOpts);
|
|
4549
4656
|
}
|
|
4550
4657
|
|
|
4551
4658
|
// Handle deferred populate for cases with per-document match functions.
|
|
@@ -4585,13 +4692,15 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4585
4692
|
}
|
|
4586
4693
|
}
|
|
4587
4694
|
|
|
4588
|
-
for (
|
|
4589
|
-
|
|
4695
|
+
for (let i = 0; i < params.length; i++) {
|
|
4696
|
+
const mod = params[i][0];
|
|
4697
|
+
removeDeselectedForeignField(mod.foreignField, mod.options, mod._assignFromOwnResults ? valsByParam[i] : vals);
|
|
4590
4698
|
}
|
|
4591
|
-
for (
|
|
4592
|
-
const mod =
|
|
4699
|
+
for (let i = 0; i < params.length; i++) {
|
|
4700
|
+
const mod = params[i][0];
|
|
4593
4701
|
if (mod.options?.options?._leanTransform) {
|
|
4594
|
-
|
|
4702
|
+
const valsForMod = mod._assignFromOwnResults ? valsByParam[i] : vals;
|
|
4703
|
+
for (const doc of valsForMod) {
|
|
4595
4704
|
mod.options.options._leanTransform(doc);
|
|
4596
4705
|
}
|
|
4597
4706
|
}
|
|
@@ -4603,6 +4712,12 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4603
4712
|
*/
|
|
4604
4713
|
|
|
4605
4714
|
function _execPopulateQuery(mod, match, select) {
|
|
4715
|
+
// `null` match means `mod`'s documents have no ids to query, possible if a large populate
|
|
4716
|
+
// was split into a query per document (gh-5890). Skip executing a query: `_assign()` still
|
|
4717
|
+
// sets the documents' populated paths to the appropriate default value.
|
|
4718
|
+
if (match == null) {
|
|
4719
|
+
return Promise.resolve({ docs: [], deferredPopulates: [] });
|
|
4720
|
+
}
|
|
4606
4721
|
let subPopulate = clone(mod.options.populate);
|
|
4607
4722
|
const queryOptions = {};
|
|
4608
4723
|
if (mod.options.skip !== undefined) {
|
package/lib/mongoose.js
CHANGED
|
@@ -228,7 +228,7 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
|
|
|
228
228
|
* - `bufferCommands`: enable/disable mongoose's buffering mechanism for all connections and models
|
|
229
229
|
* - `bufferTimeoutMS`: If bufferCommands is on, this option sets the maximum amount of time Mongoose buffering will wait before throwing an error. If not specified, Mongoose will use 10000 (10 seconds).
|
|
230
230
|
* - `cloneSchemas`: `false` by default. Set to `true` to `clone()` all schemas before compiling into a model.
|
|
231
|
-
* - `debug`: If `true`, prints the operations mongoose sends to MongoDB to the console. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
|
|
231
|
+
* - `debug`: If `true`, prints the operations mongoose sends to MongoDB to the console. If an object is passed, you can set `color`, `shell`, and `timestamp` options. If `timestamp` is `true`, Mongoose prefixes console debug output with an ISO timestamp in brackets. If a writable stream is passed, it will log to that stream, without colorization. If a callback function is passed, it will receive the collection name, the method name, then all arguments passed to the method. For example, if you wanted to replicate the default logging, you could output from the callback `Mongoose: ${collectionName}.${methodName}(${methodArgs.join(', ')})`.
|
|
232
232
|
* - `id`: If `true`, adds a `id` virtual to all schemas unless overwritten on a per-schema basis.
|
|
233
233
|
* - `maxTimeMS`: If set, attaches [maxTimeMS](https://www.mongodb.com/docs/manual/reference/operator/meta/maxTimeMS/) to every query
|
|
234
234
|
* - `objectIdGetter`: `true` by default. Mongoose adds a getter to MongoDB ObjectId's called `_id` that returns `this` for convenience with populate. Set this to false to remove the getter.
|
|
@@ -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.
|
|
@@ -94,6 +94,20 @@ Object.defineProperty(SchemaTypeOptions.prototype, 'cast', opts);
|
|
|
94
94
|
|
|
95
95
|
Object.defineProperty(SchemaTypeOptions.prototype, 'required', opts);
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Controls whether this path may be set to `null`. By default, Mongoose allows
|
|
99
|
+
* `null` for non-required paths. Set `allowNull: false` to allow `undefined`
|
|
100
|
+
* but disallow `null`.
|
|
101
|
+
*
|
|
102
|
+
* @api public
|
|
103
|
+
* @property allowNull
|
|
104
|
+
* @memberOf SchemaTypeOptions
|
|
105
|
+
* @type {boolean}
|
|
106
|
+
* @instance
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
Object.defineProperty(SchemaTypeOptions.prototype, 'allowNull', opts);
|
|
110
|
+
|
|
97
111
|
/**
|
|
98
112
|
* The default value for this path. If a function, Mongoose executes the function
|
|
99
113
|
* and uses the return value as the default.
|
|
@@ -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
|
|