mongoose 8.7.3 → 8.8.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/dist/browser.umd.js +1 -1
- package/lib/cast.js +2 -1
- package/lib/helpers/document/applyTimestamps.js +105 -0
- package/lib/helpers/isBsonType.js +1 -2
- package/lib/helpers/query/castUpdate.js +2 -2
- package/lib/helpers/query/handleImmutable.js +17 -1
- package/lib/helpers/schema/applyReadConcern.js +1 -3
- package/lib/helpers/schema/applyWriteConcern.js +1 -3
- package/lib/model.js +80 -9
- package/lib/mongoose.js +2 -2
- package/lib/query.js +68 -14
- package/lib/schema/array.js +35 -0
- package/lib/schema/documentArray.js +1 -1
- package/lib/schema.js +10 -0
- package/lib/types/array/index.js +5 -0
- package/lib/types/documentArray/index.js +6 -1
- package/package.json +11 -11
- package/types/document.d.ts +9 -2
- package/types/index.d.ts +5 -2
- package/types/indexes.d.ts +2 -1
- package/types/models.d.ts +6 -1
- package/types/query.d.ts +19 -1
package/lib/cast.js
CHANGED
|
@@ -28,6 +28,7 @@ const ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
|
|
|
28
28
|
* @param {Object} [options] the query options
|
|
29
29
|
* @param {Boolean|"throw"} [options.strict] Wheter to enable all strict options
|
|
30
30
|
* @param {Boolean|"throw"} [options.strictQuery] Enable strict Queries
|
|
31
|
+
* @param {Boolean} [options.sanitizeFilter] avoid adding implict query selectors ($in)
|
|
31
32
|
* @param {Boolean} [options.upsert]
|
|
32
33
|
* @param {Query} [context] passed to setters
|
|
33
34
|
* @api private
|
|
@@ -372,7 +373,7 @@ module.exports = function cast(schema, obj, options, context) {
|
|
|
372
373
|
|
|
373
374
|
}
|
|
374
375
|
}
|
|
375
|
-
} else if (Array.isArray(val) && ['Buffer', 'Array'].indexOf(schematype.instance) === -1) {
|
|
376
|
+
} else if (Array.isArray(val) && ['Buffer', 'Array'].indexOf(schematype.instance) === -1 && !options.sanitizeFilter) {
|
|
376
377
|
const casted = [];
|
|
377
378
|
const valuesArray = val;
|
|
378
379
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const handleTimestampOption = require('../schema/handleTimestampOption');
|
|
4
|
+
const mpath = require('mpath');
|
|
5
|
+
|
|
6
|
+
module.exports = applyTimestamps;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Apply a given schema's timestamps to the given POJO
|
|
10
|
+
*
|
|
11
|
+
* @param {Schema} schema
|
|
12
|
+
* @param {Object} obj
|
|
13
|
+
* @param {Object} [options]
|
|
14
|
+
* @param {Boolean} [options.isUpdate=false] if true, treat this as an update: just set updatedAt, skip setting createdAt. If false, set both createdAt and updatedAt
|
|
15
|
+
* @param {Function} [options.currentTime] if set, Mongoose will call this function to get the current time.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
function applyTimestamps(schema, obj, options) {
|
|
19
|
+
if (obj == null) {
|
|
20
|
+
return obj;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
applyTimestampsToChildren(schema, obj, options);
|
|
24
|
+
return applyTimestampsToDoc(schema, obj, options);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Apply timestamps to any subdocuments
|
|
29
|
+
*
|
|
30
|
+
* @param {Schema} schema subdocument schema
|
|
31
|
+
* @param {Object} res subdocument
|
|
32
|
+
* @param {Object} [options]
|
|
33
|
+
* @param {Boolean} [options.isUpdate=false] if true, treat this as an update: just set updatedAt, skip setting createdAt. If false, set both createdAt and updatedAt
|
|
34
|
+
* @param {Function} [options.currentTime] if set, Mongoose will call this function to get the current time.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
function applyTimestampsToChildren(schema, res, options) {
|
|
38
|
+
for (const childSchema of schema.childSchemas) {
|
|
39
|
+
const _path = childSchema.model.path;
|
|
40
|
+
const _schema = childSchema.schema;
|
|
41
|
+
if (!_path) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const _obj = mpath.get(_path, res);
|
|
45
|
+
if (_obj == null || (Array.isArray(_obj) && _obj.flat(Infinity).length === 0)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
applyTimestamps(_schema, _obj, options);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Apply timestamps to a given document. Does not apply timestamps to subdocuments: use `applyTimestampsToChildren` instead
|
|
55
|
+
*
|
|
56
|
+
* @param {Schema} schema
|
|
57
|
+
* @param {Object} obj
|
|
58
|
+
* @param {Object} [options]
|
|
59
|
+
* @param {Boolean} [options.isUpdate=false] if true, treat this as an update: just set updatedAt, skip setting createdAt. If false, set both createdAt and updatedAt
|
|
60
|
+
* @param {Function} [options.currentTime] if set, Mongoose will call this function to get the current time.
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
function applyTimestampsToDoc(schema, obj, options) {
|
|
64
|
+
if (obj == null || typeof obj !== 'object') {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(obj)) {
|
|
68
|
+
for (const el of obj) {
|
|
69
|
+
applyTimestampsToDoc(schema, el, options);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (schema.discriminators && Object.keys(schema.discriminators).length > 0) {
|
|
75
|
+
for (const discriminatorKey of Object.keys(schema.discriminators)) {
|
|
76
|
+
const discriminator = schema.discriminators[discriminatorKey];
|
|
77
|
+
const key = discriminator.discriminatorMapping.key;
|
|
78
|
+
const value = discriminator.discriminatorMapping.value;
|
|
79
|
+
if (obj[key] == value) {
|
|
80
|
+
schema = discriminator;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const createdAt = handleTimestampOption(schema.options.timestamps, 'createdAt');
|
|
87
|
+
const updatedAt = handleTimestampOption(schema.options.timestamps, 'updatedAt');
|
|
88
|
+
const currentTime = options?.currentTime;
|
|
89
|
+
|
|
90
|
+
let ts = null;
|
|
91
|
+
if (currentTime != null) {
|
|
92
|
+
ts = currentTime();
|
|
93
|
+
} else if (schema.base?.now) {
|
|
94
|
+
ts = schema.base.now();
|
|
95
|
+
} else {
|
|
96
|
+
ts = new Date();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (createdAt && obj[createdAt] == null && !options?.isUpdate) {
|
|
100
|
+
obj[createdAt] = ts;
|
|
101
|
+
}
|
|
102
|
+
if (updatedAt) {
|
|
103
|
+
obj[updatedAt] = ts;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -245,7 +245,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
if (op !== '$setOnInsert' &&
|
|
248
|
-
handleImmutable(schematype, strict, obj, key, prefix + key, context)) {
|
|
248
|
+
handleImmutable(schematype, strict, obj, key, prefix + key, options, context)) {
|
|
249
249
|
continue;
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -353,7 +353,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) {
|
|
|
353
353
|
|
|
354
354
|
// You can use `$setOnInsert` with immutable keys
|
|
355
355
|
if (op !== '$setOnInsert' &&
|
|
356
|
-
handleImmutable(schematype, strict, obj, key, prefix + key, context)) {
|
|
356
|
+
handleImmutable(schematype, strict, obj, key, prefix + key, options, context)) {
|
|
357
357
|
continue;
|
|
358
358
|
}
|
|
359
359
|
|
|
@@ -2,7 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
const StrictModeError = require('../../error/strict');
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Handle immutable option for a given path when casting updates based on options
|
|
7
|
+
*
|
|
8
|
+
* @param {SchemaType} schematype the resolved schematype for this path
|
|
9
|
+
* @param {Boolean | 'throw' | null} strict whether strict mode is set for this query
|
|
10
|
+
* @param {Object} obj the object containing the value being checked so we can delete
|
|
11
|
+
* @param {String} key the key in `obj` which we are checking for immutability
|
|
12
|
+
* @param {String} fullPath the full path being checked
|
|
13
|
+
* @param {Object} options the query options
|
|
14
|
+
* @param {Query} ctx the query. Passed as `this` and first param to the `immutable` option, if `immutable` is a function
|
|
15
|
+
* @returns true if field was removed, false otherwise
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
module.exports = function handleImmutable(schematype, strict, obj, key, fullPath, options, ctx) {
|
|
6
19
|
if (schematype == null || !schematype.options || !schematype.options.immutable) {
|
|
7
20
|
return false;
|
|
8
21
|
}
|
|
@@ -15,6 +28,9 @@ module.exports = function handleImmutable(schematype, strict, obj, key, fullPath
|
|
|
15
28
|
return false;
|
|
16
29
|
}
|
|
17
30
|
|
|
31
|
+
if (options && options.overwriteImmutable) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
18
34
|
if (strict === false) {
|
|
19
35
|
return false;
|
|
20
36
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const get = require('../get');
|
|
4
|
-
|
|
5
3
|
module.exports = function applyReadConcern(schema, options) {
|
|
6
4
|
if (options.readConcern !== undefined) {
|
|
7
5
|
return;
|
|
@@ -15,7 +13,7 @@ module.exports = function applyReadConcern(schema, options) {
|
|
|
15
13
|
return;
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
const level =
|
|
16
|
+
const level = schema.options?.readConcern?.level;
|
|
19
17
|
if (level != null) {
|
|
20
18
|
options.readConcern = { level };
|
|
21
19
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const get = require('../get');
|
|
4
|
-
|
|
5
3
|
module.exports = function applyWriteConcern(schema, options) {
|
|
6
4
|
if (options.writeConcern != null) {
|
|
7
5
|
return;
|
|
@@ -12,7 +10,7 @@ module.exports = function applyWriteConcern(schema, options) {
|
|
|
12
10
|
if (options && options.session && options.session.transaction) {
|
|
13
11
|
return;
|
|
14
12
|
}
|
|
15
|
-
const writeConcern =
|
|
13
|
+
const writeConcern = schema.options.writeConcern ?? {};
|
|
16
14
|
if (Object.keys(writeConcern).length != 0) {
|
|
17
15
|
options.writeConcern = {};
|
|
18
16
|
if (!('w' in options) && writeConcern.w != null) {
|
package/lib/model.js
CHANGED
|
@@ -10,6 +10,7 @@ const Document = require('./document');
|
|
|
10
10
|
const DocumentNotFoundError = require('./error/notFound');
|
|
11
11
|
const EventEmitter = require('events').EventEmitter;
|
|
12
12
|
const Kareem = require('kareem');
|
|
13
|
+
const { MongoBulkWriteError } = require('mongodb');
|
|
13
14
|
const MongooseBulkWriteError = require('./error/bulkWriteError');
|
|
14
15
|
const MongooseError = require('./error/index');
|
|
15
16
|
const ObjectParameterError = require('./error/objectParameter');
|
|
@@ -30,6 +31,7 @@ const applyReadConcern = require('./helpers/schema/applyReadConcern');
|
|
|
30
31
|
const applySchemaCollation = require('./helpers/indexes/applySchemaCollation');
|
|
31
32
|
const applyStaticHooks = require('./helpers/model/applyStaticHooks');
|
|
32
33
|
const applyStatics = require('./helpers/model/applyStatics');
|
|
34
|
+
const applyTimestampsHelper = require('./helpers/document/applyTimestamps');
|
|
33
35
|
const applyWriteConcern = require('./helpers/schema/applyWriteConcern');
|
|
34
36
|
const applyVirtualsHelper = require('./helpers/document/applyVirtuals');
|
|
35
37
|
const assignVals = require('./helpers/populate/assignVals');
|
|
@@ -1219,6 +1221,7 @@ Model.createCollection = async function createCollection(options) {
|
|
|
1219
1221
|
*
|
|
1220
1222
|
* @param {Object} [options] options to pass to `ensureIndexes()`
|
|
1221
1223
|
* @param {Boolean} [options.background=null] if specified, overrides each index's `background` property
|
|
1224
|
+
* @param {Boolean} [options.hideIndexes=false] set to `true` to hide indexes instead of dropping. Requires MongoDB server 4.4 or higher
|
|
1222
1225
|
* @return {Promise}
|
|
1223
1226
|
* @api public
|
|
1224
1227
|
*/
|
|
@@ -1439,8 +1442,10 @@ function getIndexesToDrop(schema, schemaIndexes, dbIndexes) {
|
|
|
1439
1442
|
*
|
|
1440
1443
|
* The returned promise resolves to a list of the dropped indexes' names as an array
|
|
1441
1444
|
*
|
|
1442
|
-
* @param {
|
|
1443
|
-
* @
|
|
1445
|
+
* @param {Object} [options]
|
|
1446
|
+
* @param {Array<String>} [options.toDrop] if specified, contains a list of index names to drop
|
|
1447
|
+
* @param {Boolean} [options.hideIndexes=false] set to `true` to hide indexes instead of dropping. Requires MongoDB server 4.4 or higher
|
|
1448
|
+
* @return {Promise<String>} list of dropped or hidden index names
|
|
1444
1449
|
* @api public
|
|
1445
1450
|
*/
|
|
1446
1451
|
|
|
@@ -1451,23 +1456,32 @@ Model.cleanIndexes = async function cleanIndexes(options) {
|
|
|
1451
1456
|
}
|
|
1452
1457
|
const model = this;
|
|
1453
1458
|
|
|
1454
|
-
const collection = model.$__collection;
|
|
1455
|
-
|
|
1456
1459
|
if (Array.isArray(options && options.toDrop)) {
|
|
1457
|
-
const res = await _dropIndexes(options.toDrop,
|
|
1460
|
+
const res = await _dropIndexes(options.toDrop, model, options);
|
|
1458
1461
|
return res;
|
|
1459
1462
|
}
|
|
1460
1463
|
|
|
1461
1464
|
const res = await model.diffIndexes();
|
|
1462
|
-
return await _dropIndexes(res.toDrop,
|
|
1465
|
+
return await _dropIndexes(res.toDrop, model, options);
|
|
1463
1466
|
};
|
|
1464
1467
|
|
|
1465
|
-
async function _dropIndexes(toDrop,
|
|
1468
|
+
async function _dropIndexes(toDrop, model, options) {
|
|
1466
1469
|
if (toDrop.length === 0) {
|
|
1467
1470
|
return [];
|
|
1468
1471
|
}
|
|
1469
1472
|
|
|
1470
|
-
|
|
1473
|
+
const collection = model.$__collection;
|
|
1474
|
+
if (options && options.hideIndexes) {
|
|
1475
|
+
await Promise.all(toDrop.map(indexName => {
|
|
1476
|
+
return model.db.db.command({
|
|
1477
|
+
collMod: collection.collectionName,
|
|
1478
|
+
index: { name: indexName, hidden: true }
|
|
1479
|
+
});
|
|
1480
|
+
}));
|
|
1481
|
+
} else {
|
|
1482
|
+
await Promise.all(toDrop.map(indexName => collection.dropIndex(indexName)));
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1471
1485
|
return toDrop;
|
|
1472
1486
|
}
|
|
1473
1487
|
|
|
@@ -1653,7 +1667,24 @@ function _ensureIndexes(model, options, callback) {
|
|
|
1653
1667
|
}
|
|
1654
1668
|
}
|
|
1655
1669
|
|
|
1656
|
-
|
|
1670
|
+
// Just in case `createIndex()` throws a sync error
|
|
1671
|
+
let promise = null;
|
|
1672
|
+
try {
|
|
1673
|
+
promise = model.collection.createIndex(indexFields, indexOptions);
|
|
1674
|
+
} catch (err) {
|
|
1675
|
+
if (!indexError) {
|
|
1676
|
+
indexError = err;
|
|
1677
|
+
}
|
|
1678
|
+
if (!model.$caught) {
|
|
1679
|
+
model.emit('error', err);
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
indexSingleDone(err, indexFields, indexOptions);
|
|
1683
|
+
create();
|
|
1684
|
+
return;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
promise.then(
|
|
1657
1688
|
name => {
|
|
1658
1689
|
indexSingleDone(null, indexFields, indexOptions, name);
|
|
1659
1690
|
create();
|
|
@@ -3417,6 +3448,11 @@ Model.bulkSave = async function bulkSave(documents, options) {
|
|
|
3417
3448
|
(err) => ({ bulkWriteResult: null, bulkWriteError: err })
|
|
3418
3449
|
);
|
|
3419
3450
|
|
|
3451
|
+
// If not a MongoBulkWriteError, treat this as all documents failed to save.
|
|
3452
|
+
if (bulkWriteError != null && !(bulkWriteError instanceof MongoBulkWriteError)) {
|
|
3453
|
+
throw bulkWriteError;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3420
3456
|
const matchedCount = bulkWriteResult?.matchedCount ?? 0;
|
|
3421
3457
|
const insertedCount = bulkWriteResult?.insertedCount ?? 0;
|
|
3422
3458
|
if (writeOperations.length > 0 && matchedCount + insertedCount < writeOperations.length && !bulkWriteError) {
|
|
@@ -3540,6 +3576,41 @@ Model.applyVirtuals = function applyVirtuals(obj, virtualsToApply) {
|
|
|
3540
3576
|
return obj;
|
|
3541
3577
|
};
|
|
3542
3578
|
|
|
3579
|
+
/**
|
|
3580
|
+
* Apply this model's timestamps to a given POJO, including subdocument timestamps
|
|
3581
|
+
*
|
|
3582
|
+
* #### Example:
|
|
3583
|
+
*
|
|
3584
|
+
* const userSchema = new Schema({ name: String }, { timestamps: true });
|
|
3585
|
+
* const User = mongoose.model('User', userSchema);
|
|
3586
|
+
*
|
|
3587
|
+
* const obj = { name: 'John' };
|
|
3588
|
+
* User.applyTimestamps(obj);
|
|
3589
|
+
* obj.createdAt; // 2024-06-01T18:00:00.000Z
|
|
3590
|
+
* obj.updatedAt; // 2024-06-01T18:00:00.000Z
|
|
3591
|
+
*
|
|
3592
|
+
* @param {Object} obj object or document to apply virtuals on
|
|
3593
|
+
* @param {Object} [options]
|
|
3594
|
+
* @param {Boolean} [options.isUpdate=false] if true, treat this as an update: just set updatedAt, skip setting createdAt. If false, set both createdAt and updatedAt
|
|
3595
|
+
* @param {Function} [options.currentTime] if set, Mongoose will call this function to get the current time.
|
|
3596
|
+
* @returns {Object} obj
|
|
3597
|
+
* @api public
|
|
3598
|
+
*/
|
|
3599
|
+
|
|
3600
|
+
Model.applyTimestamps = function applyTimestamps(obj, options) {
|
|
3601
|
+
if (obj == null) {
|
|
3602
|
+
return obj;
|
|
3603
|
+
}
|
|
3604
|
+
// Nothing to do if this is already a hydrated document - it should already have timestamps
|
|
3605
|
+
if (obj.$__ != null) {
|
|
3606
|
+
return obj;
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
applyTimestampsHelper(this.schema, obj, options);
|
|
3610
|
+
|
|
3611
|
+
return obj;
|
|
3612
|
+
};
|
|
3613
|
+
|
|
3543
3614
|
/**
|
|
3544
3615
|
* Cast the given POJO to the model's schema
|
|
3545
3616
|
*
|
package/lib/mongoose.js
CHANGED
|
@@ -661,6 +661,8 @@ Mongoose.prototype._model = function(name, schema, collection, options) {
|
|
|
661
661
|
utils.toCollectionName(name, _mongoose.pluralize());
|
|
662
662
|
}
|
|
663
663
|
|
|
664
|
+
applyEmbeddedDiscriminators(schema);
|
|
665
|
+
|
|
664
666
|
const connection = options.connection || _mongoose.connection;
|
|
665
667
|
model = _mongoose.Model.compile(model || name, schema, collection, connection, _mongoose);
|
|
666
668
|
// Errors handled internally, so safe to ignore error
|
|
@@ -678,8 +680,6 @@ Mongoose.prototype._model = function(name, schema, collection, options) {
|
|
|
678
680
|
}
|
|
679
681
|
}
|
|
680
682
|
|
|
681
|
-
applyEmbeddedDiscriminators(schema);
|
|
682
|
-
|
|
683
683
|
return model;
|
|
684
684
|
};
|
|
685
685
|
|
package/lib/query.js
CHANGED
|
@@ -22,7 +22,6 @@ const castUpdate = require('./helpers/query/castUpdate');
|
|
|
22
22
|
const clone = require('./helpers/clone');
|
|
23
23
|
const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue');
|
|
24
24
|
const helpers = require('./queryHelpers');
|
|
25
|
-
const immediate = require('./helpers/immediate');
|
|
26
25
|
const internalToObjectOptions = require('./options').internalToObjectOptions;
|
|
27
26
|
const isExclusive = require('./helpers/projection/isExclusive');
|
|
28
27
|
const isInclusive = require('./helpers/projection/isInclusive');
|
|
@@ -1142,6 +1141,38 @@ Query.prototype.select = function select() {
|
|
|
1142
1141
|
throw new TypeError('Invalid select() argument. Must be string or object.');
|
|
1143
1142
|
};
|
|
1144
1143
|
|
|
1144
|
+
/**
|
|
1145
|
+
* Enable or disable schema level projections for this query. Enabled by default.
|
|
1146
|
+
* Set to `false` to include fields with `select: false` in the query result by default.
|
|
1147
|
+
*
|
|
1148
|
+
* #### Example:
|
|
1149
|
+
*
|
|
1150
|
+
* const userSchema = new Schema({
|
|
1151
|
+
* email: { type: String, required: true },
|
|
1152
|
+
* passwordHash: { type: String, select: false, required: true }
|
|
1153
|
+
* });
|
|
1154
|
+
* const UserModel = mongoose.model('User', userSchema);
|
|
1155
|
+
*
|
|
1156
|
+
* const doc = await UserModel.findOne().orFail().schemaLevelProjections(false);
|
|
1157
|
+
*
|
|
1158
|
+
* // Contains password hash, because `schemaLevelProjections()` overrides `select: false`
|
|
1159
|
+
* doc.passwordHash;
|
|
1160
|
+
*
|
|
1161
|
+
* @method schemaLevelProjections
|
|
1162
|
+
* @memberOf Query
|
|
1163
|
+
* @instance
|
|
1164
|
+
* @param {Boolean} value
|
|
1165
|
+
* @return {Query} this
|
|
1166
|
+
* @see SchemaTypeOptions https://mongoosejs.com/docs/schematypes.html#all-schema-types
|
|
1167
|
+
* @api public
|
|
1168
|
+
*/
|
|
1169
|
+
|
|
1170
|
+
Query.prototype.schemaLevelProjections = function schemaLevelProjections(value) {
|
|
1171
|
+
this._mongooseOptions.schemaLevelProjections = value;
|
|
1172
|
+
|
|
1173
|
+
return this;
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1145
1176
|
/**
|
|
1146
1177
|
* Sets this query's `sanitizeProjection` option. If set, `sanitizeProjection` does
|
|
1147
1178
|
* two things:
|
|
@@ -1594,6 +1625,7 @@ Query.prototype.getOptions = function() {
|
|
|
1594
1625
|
* - [writeConcern](https://www.mongodb.com/docs/manual/reference/method/db.collection.update/)
|
|
1595
1626
|
* - [timestamps](https://mongoosejs.com/docs/guide.html#timestamps): If `timestamps` is set in the schema, set this option to `false` to skip timestamps for that particular update. Has no effect if `timestamps` is not enabled in the schema options.
|
|
1596
1627
|
* - overwriteDiscriminatorKey: allow setting the discriminator key in the update. Will use the correct discriminator schema if the update changes the discriminator key.
|
|
1628
|
+
* - overwriteImmutable: allow overwriting properties that are set to `immutable` in the schema. Defaults to false.
|
|
1597
1629
|
*
|
|
1598
1630
|
* The following options are only for `find()`, `findOne()`, `findById()`, `findOneAndUpdate()`, `findOneAndReplace()`, `findOneAndDelete()`, and `findByIdAndUpdate()`:
|
|
1599
1631
|
*
|
|
@@ -1665,6 +1697,10 @@ Query.prototype.setOptions = function(options, overwrite) {
|
|
|
1665
1697
|
this._mongooseOptions.overwriteDiscriminatorKey = options.overwriteDiscriminatorKey;
|
|
1666
1698
|
delete options.overwriteDiscriminatorKey;
|
|
1667
1699
|
}
|
|
1700
|
+
if ('overwriteImmutable' in options) {
|
|
1701
|
+
this._mongooseOptions.overwriteImmutable = options.overwriteImmutable;
|
|
1702
|
+
delete options.overwriteImmutable;
|
|
1703
|
+
}
|
|
1668
1704
|
if ('sanitizeProjection' in options) {
|
|
1669
1705
|
if (options.sanitizeProjection && !this._mongooseOptions.sanitizeProjection) {
|
|
1670
1706
|
sanitizeProjection(this._fields);
|
|
@@ -1689,6 +1725,10 @@ Query.prototype.setOptions = function(options, overwrite) {
|
|
|
1689
1725
|
this._mongooseOptions.translateAliases = options.translateAliases;
|
|
1690
1726
|
delete options.translateAliases;
|
|
1691
1727
|
}
|
|
1728
|
+
if ('schemaLevelProjections' in options) {
|
|
1729
|
+
this._mongooseOptions.schemaLevelProjections = options.schemaLevelProjections;
|
|
1730
|
+
delete options.schemaLevelProjections;
|
|
1731
|
+
}
|
|
1692
1732
|
|
|
1693
1733
|
if (options.lean == null && this.schema && 'lean' in this.schema.options) {
|
|
1694
1734
|
this._mongooseOptions.lean = this.schema.options.lean;
|
|
@@ -2207,6 +2247,9 @@ Query.prototype.error = function error(err) {
|
|
|
2207
2247
|
*/
|
|
2208
2248
|
|
|
2209
2249
|
Query.prototype._unsetCastError = function _unsetCastError() {
|
|
2250
|
+
if (this._error == null) {
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2210
2253
|
if (this._error != null && !(this._error instanceof CastError)) {
|
|
2211
2254
|
return;
|
|
2212
2255
|
}
|
|
@@ -2222,6 +2265,7 @@ Query.prototype._unsetCastError = function _unsetCastError() {
|
|
|
2222
2265
|
* - `strict`: controls how Mongoose handles keys that aren't in the schema for updates. This option is `true` by default, which means Mongoose will silently strip any paths in the update that aren't in the schema. See the [`strict` mode docs](https://mongoosejs.com/docs/guide.html#strict) for more information.
|
|
2223
2266
|
* - `strictQuery`: controls how Mongoose handles keys that aren't in the schema for the query `filter`. This option is `false` by default, which means Mongoose will allow `Model.find({ foo: 'bar' })` even if `foo` is not in the schema. See the [`strictQuery` docs](https://mongoosejs.com/docs/guide.html#strictQuery) for more information.
|
|
2224
2267
|
* - `nearSphere`: use `$nearSphere` instead of `near()`. See the [`Query.prototype.nearSphere()` docs](https://mongoosejs.com/docs/api/query.html#Query.prototype.nearSphere())
|
|
2268
|
+
* - `schemaLevelProjections`: if `false`, Mongoose will not apply schema-level `select: false` or `select: true` for this query
|
|
2225
2269
|
*
|
|
2226
2270
|
* Mongoose maintains a separate object for internal options because
|
|
2227
2271
|
* Mongoose sends `Query.prototype.options` to the MongoDB server, and the
|
|
@@ -2249,9 +2293,9 @@ Query.prototype.mongooseOptions = function(v) {
|
|
|
2249
2293
|
|
|
2250
2294
|
Query.prototype._castConditions = function() {
|
|
2251
2295
|
let sanitizeFilterOpt = undefined;
|
|
2252
|
-
if (this.model != null
|
|
2296
|
+
if (this.model?.db.options?.sanitizeFilter != null) {
|
|
2253
2297
|
sanitizeFilterOpt = this.model.db.options.sanitizeFilter;
|
|
2254
|
-
} else if (this.model != null
|
|
2298
|
+
} else if (this.model?.base.options?.sanitizeFilter != null) {
|
|
2255
2299
|
sanitizeFilterOpt = this.model.base.options.sanitizeFilter;
|
|
2256
2300
|
} else {
|
|
2257
2301
|
sanitizeFilterOpt = this._mongooseOptions.sanitizeFilter;
|
|
@@ -2494,13 +2538,12 @@ Query.prototype.collation = function(value) {
|
|
|
2494
2538
|
* @api private
|
|
2495
2539
|
*/
|
|
2496
2540
|
|
|
2497
|
-
Query.prototype._completeOne = function(doc, res, callback) {
|
|
2541
|
+
Query.prototype._completeOne = function(doc, res, projection, callback) {
|
|
2498
2542
|
if (!doc && !this.options.includeResultMetadata) {
|
|
2499
2543
|
return callback(null, null);
|
|
2500
2544
|
}
|
|
2501
2545
|
|
|
2502
2546
|
const model = this.model;
|
|
2503
|
-
const projection = clone(this._fields);
|
|
2504
2547
|
const userProvidedFields = this._userProvidedFields || {};
|
|
2505
2548
|
// `populate`, `lean`
|
|
2506
2549
|
const mongooseOptions = this._mongooseOptions;
|
|
@@ -2601,7 +2644,7 @@ Query.prototype._findOne = async function _findOne() {
|
|
|
2601
2644
|
// don't pass in the conditions because we already merged them in
|
|
2602
2645
|
const doc = await this.mongooseCollection.findOne(this._conditions, options);
|
|
2603
2646
|
return new Promise((resolve, reject) => {
|
|
2604
|
-
this._completeOne(doc, null, (err, res) => {
|
|
2647
|
+
this._completeOne(doc, null, options.projection, (err, res) => {
|
|
2605
2648
|
if (err) {
|
|
2606
2649
|
return reject(err);
|
|
2607
2650
|
}
|
|
@@ -3196,7 +3239,7 @@ function completeOne(model, doc, res, options, fields, userProvidedFields, pop,
|
|
|
3196
3239
|
|
|
3197
3240
|
function _init(err, casted) {
|
|
3198
3241
|
if (err) {
|
|
3199
|
-
return
|
|
3242
|
+
return callback(err);
|
|
3200
3243
|
}
|
|
3201
3244
|
|
|
3202
3245
|
|
|
@@ -3209,12 +3252,12 @@ function completeOne(model, doc, res, options, fields, userProvidedFields, pop,
|
|
|
3209
3252
|
} else {
|
|
3210
3253
|
res.value = null;
|
|
3211
3254
|
}
|
|
3212
|
-
return
|
|
3255
|
+
return callback(null, res);
|
|
3213
3256
|
}
|
|
3214
3257
|
if (options.session != null) {
|
|
3215
3258
|
casted.$session(options.session);
|
|
3216
3259
|
}
|
|
3217
|
-
|
|
3260
|
+
callback(null, casted);
|
|
3218
3261
|
}
|
|
3219
3262
|
}
|
|
3220
3263
|
|
|
@@ -3281,6 +3324,7 @@ function prepareDiscriminatorCriteria(query) {
|
|
|
3281
3324
|
* @param {Boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`.
|
|
3282
3325
|
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
|
3283
3326
|
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
|
|
3327
|
+
* @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
|
|
3284
3328
|
* @see Tutorial https://mongoosejs.com/docs/tutorials/findoneandupdate.html
|
|
3285
3329
|
* @see findAndModify command https://www.mongodb.com/docs/manual/reference/command/findAndModify/
|
|
3286
3330
|
* @see ModifyResult https://mongodb.github.io/node-mongodb-native/4.9/interfaces/ModifyResult.html
|
|
@@ -3422,7 +3466,7 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
|
|
|
3422
3466
|
const doc = !options.includeResultMetadata ? res : res.value;
|
|
3423
3467
|
|
|
3424
3468
|
return new Promise((resolve, reject) => {
|
|
3425
|
-
this._completeOne(doc, res, (err, res) => {
|
|
3469
|
+
this._completeOne(doc, res, options.projection, (err, res) => {
|
|
3426
3470
|
if (err) {
|
|
3427
3471
|
return reject(err);
|
|
3428
3472
|
}
|
|
@@ -3518,7 +3562,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
|
|
|
3518
3562
|
const doc = !includeResultMetadata ? res : res.value;
|
|
3519
3563
|
|
|
3520
3564
|
return new Promise((resolve, reject) => {
|
|
3521
|
-
this._completeOne(doc, res, (err, res) => {
|
|
3565
|
+
this._completeOne(doc, res, options.projection, (err, res) => {
|
|
3522
3566
|
if (err) {
|
|
3523
3567
|
return reject(err);
|
|
3524
3568
|
}
|
|
@@ -3672,7 +3716,7 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
|
|
|
3672
3716
|
|
|
3673
3717
|
const doc = !includeResultMetadata ? res : res.value;
|
|
3674
3718
|
return new Promise((resolve, reject) => {
|
|
3675
|
-
this._completeOne(doc, res, (err, res) => {
|
|
3719
|
+
this._completeOne(doc, res, options.projection, (err, res) => {
|
|
3676
3720
|
if (err) {
|
|
3677
3721
|
return reject(err);
|
|
3678
3722
|
}
|
|
@@ -3979,6 +4023,7 @@ Query.prototype._replaceOne = async function _replaceOne() {
|
|
|
3979
4023
|
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Does nothing if schema-level timestamps are not set.
|
|
3980
4024
|
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
|
3981
4025
|
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
|
|
4026
|
+
* @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
|
|
3982
4027
|
* @param {Function} [callback] params are (error, writeOpResult)
|
|
3983
4028
|
* @return {Query} this
|
|
3984
4029
|
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
|
|
@@ -4049,6 +4094,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
|
|
|
4049
4094
|
* @param {Boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
|
|
4050
4095
|
* @param {Boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
|
|
4051
4096
|
* @param {Boolean} [options.overwriteDiscriminatorKey=false] Mongoose removes discriminator key updates from `update` by default, set `overwriteDiscriminatorKey` to `true` to allow updating the discriminator key
|
|
4097
|
+
* @param {Boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
|
|
4052
4098
|
* @param {Function} [callback] params are (error, writeOpResult)
|
|
4053
4099
|
* @return {Query} this
|
|
4054
4100
|
* @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
|
|
@@ -4670,7 +4716,8 @@ Query.prototype._castUpdate = function _castUpdate(obj) {
|
|
|
4670
4716
|
strict: this._mongooseOptions.strict,
|
|
4671
4717
|
upsert: upsert,
|
|
4672
4718
|
arrayFilters: this.options.arrayFilters,
|
|
4673
|
-
overwriteDiscriminatorKey: this._mongooseOptions.overwriteDiscriminatorKey
|
|
4719
|
+
overwriteDiscriminatorKey: this._mongooseOptions.overwriteDiscriminatorKey,
|
|
4720
|
+
overwriteImmutable: this._mongooseOptions.overwriteImmutable
|
|
4674
4721
|
}, this, this._conditions);
|
|
4675
4722
|
};
|
|
4676
4723
|
|
|
@@ -4863,6 +4910,9 @@ Query.prototype.cast = function(model, obj) {
|
|
|
4863
4910
|
opts.strictQuery = this.options.strictQuery;
|
|
4864
4911
|
}
|
|
4865
4912
|
}
|
|
4913
|
+
if ('sanitizeFilter' in this._mongooseOptions) {
|
|
4914
|
+
opts.sanitizeFilter = this._mongooseOptions.sanitizeFilter;
|
|
4915
|
+
}
|
|
4866
4916
|
|
|
4867
4917
|
try {
|
|
4868
4918
|
return cast(model.schema, obj, opts, this);
|
|
@@ -4946,7 +4996,11 @@ Query.prototype._applyPaths = function applyPaths() {
|
|
|
4946
4996
|
sanitizeProjection = this._mongooseOptions.sanitizeProjection;
|
|
4947
4997
|
}
|
|
4948
4998
|
|
|
4949
|
-
|
|
4999
|
+
const schemaLevelProjections = this._mongooseOptions.schemaLevelProjections ?? true;
|
|
5000
|
+
|
|
5001
|
+
if (schemaLevelProjections) {
|
|
5002
|
+
helpers.applyPaths(this._fields, this.model.schema, sanitizeProjection);
|
|
5003
|
+
}
|
|
4950
5004
|
|
|
4951
5005
|
let _selectPopulatedPaths = true;
|
|
4952
5006
|
|