mongoose 9.4.0 → 9.5.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/lib/document.js +40 -8
- package/lib/drivers/node-mongodb-native/collection.js +10 -4
- package/lib/error/cast.js +18 -9
- package/lib/helpers/query/castUpdate.js +4 -0
- package/lib/helpers/setDefaultsOnInsert.js +1 -1
- package/lib/model.js +4 -7
- package/lib/mongoose.js +1 -1
- package/lib/query.js +159 -74
- package/lib/queryHelpers.js +13 -12
- package/lib/schema.js +19 -0
- package/package.json +7 -7
- package/types/mongooseoptions.d.ts +1 -1
- package/types/query.d.ts +11 -1
package/lib/document.js
CHANGED
|
@@ -5112,20 +5112,26 @@ Document.prototype.$__delta = function $__delta(pathsToSave, pathsToSaveSet) {
|
|
|
5112
5112
|
const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
|
|
5113
5113
|
if (optimisticConcurrency) {
|
|
5114
5114
|
if (Array.isArray(optimisticConcurrency)) {
|
|
5115
|
-
|
|
5116
|
-
|
|
5115
|
+
if (!this.$__schema.options._optimisticConcurrencySet) {
|
|
5116
|
+
this.$__schema.options._optimisticConcurrencySet = new Set(optimisticConcurrency);
|
|
5117
|
+
}
|
|
5118
|
+
const optimisticConcurrencySet = this.$__schema.options._optimisticConcurrencySet;
|
|
5119
|
+
const modPaths = this.directModifiedPaths();
|
|
5117
5120
|
const hasRelevantModPaths = pathsToSave == null ?
|
|
5118
|
-
modPaths.find(path =>
|
|
5119
|
-
modPaths.find(path =>
|
|
5121
|
+
modPaths.find(path => _pathOverlapsSet(path, optimisticConcurrencySet)) :
|
|
5122
|
+
modPaths.find(path => _pathOverlapsSet(path, optimisticConcurrencySet) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5120
5123
|
if (hasRelevantModPaths) {
|
|
5121
5124
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5122
5125
|
}
|
|
5123
5126
|
} else if (Array.isArray(optimisticConcurrency?.exclude)) {
|
|
5124
|
-
|
|
5125
|
-
|
|
5127
|
+
if (!this.$__schema.options._optimisticConcurrencyExcludeSet) {
|
|
5128
|
+
this.$__schema.options._optimisticConcurrencyExcludeSet = new Set(optimisticConcurrency.exclude);
|
|
5129
|
+
}
|
|
5130
|
+
const optimisticConcurrencyExcludeSet = this.$__schema.options._optimisticConcurrencyExcludeSet;
|
|
5131
|
+
const modPaths = this.directModifiedPaths();
|
|
5126
5132
|
const hasRelevantModPaths = pathsToSave == null ?
|
|
5127
|
-
modPaths.find(path => !
|
|
5128
|
-
modPaths.find(path => !
|
|
5133
|
+
modPaths.find(path => !_pathOverlapsSet(path, optimisticConcurrencyExcludeSet)) :
|
|
5134
|
+
modPaths.find(path => !_pathOverlapsSet(path, optimisticConcurrencyExcludeSet) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5129
5135
|
if (hasRelevantModPaths) {
|
|
5130
5136
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5131
5137
|
}
|
|
@@ -5652,6 +5658,32 @@ Document.prototype._applyVersionIncrement = function _applyVersionIncrement() {
|
|
|
5652
5658
|
* Module exports.
|
|
5653
5659
|
*/
|
|
5654
5660
|
|
|
5661
|
+
/*!
|
|
5662
|
+
* Check if `path`, any of its ancestor paths, or any of its descendant paths
|
|
5663
|
+
* exist in `pathSet`.
|
|
5664
|
+
* For example:
|
|
5665
|
+
* _pathOverlapsSet('profile.firstName', Set(['profile'])) === true
|
|
5666
|
+
* _pathOverlapsSet('profile', Set(['profile.firstName'])) === true
|
|
5667
|
+
*/
|
|
5668
|
+
function _pathOverlapsSet(path, pathSet) {
|
|
5669
|
+
if (pathSet.has(path)) {
|
|
5670
|
+
return true;
|
|
5671
|
+
}
|
|
5672
|
+
let idx = path.indexOf('.');
|
|
5673
|
+
while (idx !== -1) {
|
|
5674
|
+
if (pathSet.has(path.substring(0, idx))) {
|
|
5675
|
+
return true;
|
|
5676
|
+
}
|
|
5677
|
+
idx = path.indexOf('.', idx + 1);
|
|
5678
|
+
}
|
|
5679
|
+
for (const p of pathSet) {
|
|
5680
|
+
if (p.length > path.length + 1 && p[path.length] === '.' && p.slice(0, path.length) === path) {
|
|
5681
|
+
return true;
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
return false;
|
|
5685
|
+
}
|
|
5686
|
+
|
|
5655
5687
|
Document.VERSION_WHERE = VERSION_WHERE;
|
|
5656
5688
|
Document.VERSION_INC = VERSION_INC;
|
|
5657
5689
|
Document.VERSION_ALL = VERSION_ALL;
|
|
@@ -172,7 +172,8 @@ function iter(i) {
|
|
|
172
172
|
} else {
|
|
173
173
|
const color = debug.color == null ? true : debug.color;
|
|
174
174
|
const shell = debug.shell == null ? false : debug.shell;
|
|
175
|
-
|
|
175
|
+
const timestamp = debug.timestamp == null ? false : debug.timestamp;
|
|
176
|
+
this.$print(_this.name, i, args, color, shell, timestamp);
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
179
|
|
|
@@ -256,7 +257,12 @@ for (const key of Object.getOwnPropertyNames(Collection.prototype)) {
|
|
|
256
257
|
* @method $print
|
|
257
258
|
*/
|
|
258
259
|
|
|
259
|
-
NativeCollection.prototype.$print = function(name, i, args, color, shell) {
|
|
260
|
+
NativeCollection.prototype.$print = function(name, i, args, color, shell, timestamp) {
|
|
261
|
+
let prefix = '';
|
|
262
|
+
if (timestamp) {
|
|
263
|
+
const ts = new Date().toISOString();
|
|
264
|
+
prefix = color ? `\x1B[0;90m[${ts}]\x1B[0m ` : `[${ts}] `;
|
|
265
|
+
}
|
|
260
266
|
const moduleName = color ? '\x1B[0;36mMongoose:\x1B[0m ' : 'Mongoose: ';
|
|
261
267
|
const functionCall = [name, i].join('.');
|
|
262
268
|
const _args = [];
|
|
@@ -267,14 +273,14 @@ NativeCollection.prototype.$print = function(name, i, args, color, shell) {
|
|
|
267
273
|
}
|
|
268
274
|
const params = '(' + _args.join(', ') + ')';
|
|
269
275
|
|
|
270
|
-
console.info(moduleName + functionCall + params);
|
|
276
|
+
console.info(prefix + moduleName + functionCall + params);
|
|
271
277
|
};
|
|
272
278
|
|
|
273
279
|
/**
|
|
274
280
|
* Debug print helper
|
|
275
281
|
*
|
|
276
282
|
* @api public
|
|
277
|
-
* @method $
|
|
283
|
+
* @method $printToStream
|
|
278
284
|
*/
|
|
279
285
|
|
|
280
286
|
NativeCollection.prototype.$printToStream = function(name, i, args, stream) {
|
package/lib/error/cast.js
CHANGED
|
@@ -42,6 +42,7 @@ class CastError extends MongooseError {
|
|
|
42
42
|
message: this.message
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
|
|
45
46
|
/*!
|
|
46
47
|
* ignore
|
|
47
48
|
*/
|
|
@@ -76,7 +77,7 @@ class CastError extends MongooseError {
|
|
|
76
77
|
*/
|
|
77
78
|
setModel(model) {
|
|
78
79
|
this.message = formatMessage(model, this.kind, this.value, this.path,
|
|
79
|
-
this.messageFormat, this.valueType);
|
|
80
|
+
this.messageFormat, this.valueType, this.reason);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -122,10 +123,11 @@ function getMessageFormat(schemaType) {
|
|
|
122
123
|
function formatMessage(model, kind, value, path, messageFormat, valueType, reason) {
|
|
123
124
|
if (typeof messageFormat === 'string') {
|
|
124
125
|
const stringValue = getStringValue(value);
|
|
125
|
-
let ret = messageFormat
|
|
126
|
-
replace('{KIND}', kind)
|
|
127
|
-
replace('{VALUE}', stringValue)
|
|
128
|
-
replace('{PATH}', path);
|
|
126
|
+
let ret = messageFormat
|
|
127
|
+
.replace('{KIND}', kind)
|
|
128
|
+
.replace('{VALUE}', stringValue)
|
|
129
|
+
.replace('{PATH}', path);
|
|
130
|
+
|
|
129
131
|
if (model != null) {
|
|
130
132
|
ret = ret.replace('{MODEL}', model.modelName);
|
|
131
133
|
}
|
|
@@ -136,17 +138,24 @@ function formatMessage(model, kind, value, path, messageFormat, valueType, reaso
|
|
|
136
138
|
} else {
|
|
137
139
|
const stringValue = getStringValue(value);
|
|
138
140
|
const valueTypeMsg = valueType ? ' (type ' + valueType + ')' : '';
|
|
141
|
+
|
|
139
142
|
let ret = 'Cast to ' + kind + ' failed for value ' +
|
|
140
143
|
stringValue + valueTypeMsg + ' at path "' + path + '"';
|
|
144
|
+
|
|
141
145
|
if (model != null) {
|
|
142
146
|
ret += ' for model "' + model.modelName + '"';
|
|
143
147
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
reason != null &&
|
|
151
|
+
typeof reason.constructor === 'function' &&
|
|
152
|
+
reason.constructor.name !== 'AssertionError' &&
|
|
153
|
+
reason.constructor.name !== 'Error' &&
|
|
154
|
+
reason.constructor.name !== 'BSONError'
|
|
155
|
+
) {
|
|
148
156
|
ret += ' because of "' + reason.constructor.name + '"';
|
|
149
157
|
}
|
|
158
|
+
|
|
150
159
|
return ret;
|
|
151
160
|
}
|
|
152
161
|
}
|
|
@@ -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 = {};
|
|
@@ -84,7 +84,7 @@ module.exports = function(filter, schema, castedDoc, options, queryMongooseOptio
|
|
|
84
84
|
if (schemaType.path === '_id' && schemaType.options.auto) {
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
|
-
const def = schemaType.getDefault(null,
|
|
87
|
+
const def = schemaType.getDefault(null, true, { context });
|
|
88
88
|
if (typeof def === 'undefined') {
|
|
89
89
|
return;
|
|
90
90
|
}
|
package/lib/model.js
CHANGED
|
@@ -2418,13 +2418,6 @@ Model.findOneAndUpdate = function(conditions, update, options) {
|
|
|
2418
2418
|
fields = options.fields || options.projection;
|
|
2419
2419
|
}
|
|
2420
2420
|
|
|
2421
|
-
update = clone(update, {
|
|
2422
|
-
depopulate: true,
|
|
2423
|
-
_isNested: true
|
|
2424
|
-
});
|
|
2425
|
-
|
|
2426
|
-
decorateUpdateWithVersionKey(update, options, this.schema.options.versionKey);
|
|
2427
|
-
|
|
2428
2421
|
const mq = new this.Query({}, {}, this, this.$__collection);
|
|
2429
2422
|
mq.select(fields);
|
|
2430
2423
|
|
|
@@ -2864,6 +2857,10 @@ Model.create = async function create(doc, options) {
|
|
|
2864
2857
|
Model.insertOne = async function insertOne(doc, options) {
|
|
2865
2858
|
_checkContext(this, 'insertOne');
|
|
2866
2859
|
|
|
2860
|
+
if (doc == null || typeof doc !== 'object') {
|
|
2861
|
+
throw new ObjectParameterError(doc, 'doc', 'insertOne');
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2867
2864
|
const discriminatorKey = this.schema.options.discriminatorKey;
|
|
2868
2865
|
const Model = this.discriminators && doc[discriminatorKey] != null ?
|
|
2869
2866
|
this.discriminators[doc[discriminatorKey]] || getDiscriminatorByValue(this.discriminators, doc[discriminatorKey]) :
|
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.
|
package/lib/query.js
CHANGED
|
@@ -20,6 +20,7 @@ const castArrayFilters = require('./helpers/update/castArrayFilters');
|
|
|
20
20
|
const castNumber = require('./cast/number');
|
|
21
21
|
const castUpdate = require('./helpers/query/castUpdate');
|
|
22
22
|
const clone = require('./helpers/clone');
|
|
23
|
+
const decorateUpdateWithVersionKey = require('./helpers/update/decorateUpdateWithVersionKey');
|
|
23
24
|
const getDiscriminatorByValue = require('./helpers/discriminator/getDiscriminatorByValue');
|
|
24
25
|
const helpers = require('./queryHelpers');
|
|
25
26
|
const internalToObjectOptions = require('./options').internalToObjectOptions;
|
|
@@ -85,6 +86,8 @@ const opToThunk = new Map([
|
|
|
85
86
|
['findOneAndDelete', '_findOneAndDelete']
|
|
86
87
|
]);
|
|
87
88
|
|
|
89
|
+
const queryUpdateSymbol = Symbol('mongoose#Query#update');
|
|
90
|
+
|
|
88
91
|
/**
|
|
89
92
|
* Query constructor used for building queries. You do not need
|
|
90
93
|
* to instantiate a `Query` directly. Instead use Model functions like
|
|
@@ -196,6 +199,18 @@ function checkRequireFilter(filter, options) {
|
|
|
196
199
|
Query.prototype = new mquery();
|
|
197
200
|
Query.prototype.constructor = Query;
|
|
198
201
|
|
|
202
|
+
Object.defineProperty(Query.prototype, '_update', {
|
|
203
|
+
configurable: true,
|
|
204
|
+
enumerable: true,
|
|
205
|
+
get: function() {
|
|
206
|
+
_cloneUpdateIfShared(this);
|
|
207
|
+
return this[queryUpdateSymbol];
|
|
208
|
+
},
|
|
209
|
+
set: function(v) {
|
|
210
|
+
this[queryUpdateSymbol] = v;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
199
214
|
// Remove some legacy methods that we removed in Mongoose 8, but
|
|
200
215
|
// are still in mquery 5.
|
|
201
216
|
Query.prototype.count = undefined;
|
|
@@ -300,7 +315,7 @@ Query.prototype.toConstructor = function toConstructor() {
|
|
|
300
315
|
p.op = this.op;
|
|
301
316
|
p._conditions = clone(this._conditions);
|
|
302
317
|
p._fields = clone(this._fields);
|
|
303
|
-
p
|
|
318
|
+
p[queryUpdateSymbol] = clone(this[queryUpdateSymbol], {
|
|
304
319
|
flattenDecimals: false
|
|
305
320
|
});
|
|
306
321
|
p._path = this._path;
|
|
@@ -348,7 +363,7 @@ Query.prototype.clone = function() {
|
|
|
348
363
|
q.op = this.op;
|
|
349
364
|
q._conditions = clone(this._conditions);
|
|
350
365
|
q._fields = clone(this._fields);
|
|
351
|
-
q
|
|
366
|
+
q[queryUpdateSymbol] = clone(this[queryUpdateSymbol], {
|
|
352
367
|
flattenDecimals: false
|
|
353
368
|
});
|
|
354
369
|
q._path = this._path;
|
|
@@ -1365,7 +1380,7 @@ Query.prototype.toString = function toString() {
|
|
|
1365
1380
|
this.op === 'update' ||
|
|
1366
1381
|
this.op === 'updateMany' ||
|
|
1367
1382
|
this.op === 'updateOne') {
|
|
1368
|
-
return `${this.model.modelName}.${this.op}(${util.inspect(this._conditions)}, ${util.inspect(this
|
|
1383
|
+
return `${this.model.modelName}.${this.op}(${util.inspect(this._conditions)}, ${util.inspect(this[queryUpdateSymbol])})`;
|
|
1369
1384
|
}
|
|
1370
1385
|
|
|
1371
1386
|
// 'estimatedDocumentCount' or any others
|
|
@@ -1669,6 +1684,7 @@ Query.prototype.getOptions = function() {
|
|
|
1669
1684
|
* - [upsert](https://www.mongodb.com/docs/manual/reference/method/db.collection.update/)
|
|
1670
1685
|
* - [writeConcern](https://www.mongodb.com/docs/manual/reference/method/db.collection.update/)
|
|
1671
1686
|
* - [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.
|
|
1687
|
+
* - cloneUpdate: set to `false` to skip cloning the update before executing the query.
|
|
1672
1688
|
* - overwriteDiscriminatorKey: allow setting the discriminator key in the update. Will use the correct discriminator schema if the update changes the discriminator key.
|
|
1673
1689
|
* - overwriteImmutable: allow overwriting properties that are set to `immutable` in the schema. Defaults to false.
|
|
1674
1690
|
*
|
|
@@ -1679,6 +1695,7 @@ Query.prototype.getOptions = function() {
|
|
|
1679
1695
|
* - [projection](https://mongoosejs.com/docs/api/query.html#Query.prototype.projection())
|
|
1680
1696
|
* - sanitizeProjection
|
|
1681
1697
|
* - useBigInt64
|
|
1698
|
+
* - defaults: if `false`, skip applying default values to the returned document(s). Defaults to true.
|
|
1682
1699
|
*
|
|
1683
1700
|
* The following options are only for all operations **except** `updateOne()`, `updateMany()`, `deleteOne()`, and `deleteMany()`:
|
|
1684
1701
|
*
|
|
@@ -1751,6 +1768,10 @@ Query.prototype.setOptions = function(options, overwrite) {
|
|
|
1751
1768
|
this._mongooseOptions.updatePipeline = options.updatePipeline;
|
|
1752
1769
|
delete options.updatePipeline;
|
|
1753
1770
|
}
|
|
1771
|
+
if ('cloneUpdate' in options) {
|
|
1772
|
+
this._mongooseOptions.cloneUpdate = options.cloneUpdate;
|
|
1773
|
+
delete options.cloneUpdate;
|
|
1774
|
+
}
|
|
1754
1775
|
if ('sanitizeProjection' in options) {
|
|
1755
1776
|
if (options.sanitizeProjection && !this._mongooseOptions.sanitizeProjection) {
|
|
1756
1777
|
sanitizeProjection(this._fields);
|
|
@@ -1984,12 +2005,17 @@ Query.prototype.getUpdate = function() {
|
|
|
1984
2005
|
* query.getUpdate(); // { $set: { b: 6 } }
|
|
1985
2006
|
*
|
|
1986
2007
|
* @param {object} new update operation
|
|
2008
|
+
* @param {boolean} [cloneUpdate=true] if `false`, Mongoose will not clone the update
|
|
1987
2009
|
* @return {undefined}
|
|
1988
2010
|
* @api public
|
|
1989
2011
|
*/
|
|
1990
2012
|
|
|
1991
|
-
Query.prototype.setUpdate = function(val) {
|
|
1992
|
-
this
|
|
2013
|
+
Query.prototype.setUpdate = function(val, cloneUpdate) {
|
|
2014
|
+
this[queryUpdateSymbol] = cloneUpdate === false ? val : clone(val);
|
|
2015
|
+
if (cloneUpdate != null) {
|
|
2016
|
+
this._mongooseOptions.cloneUpdate = cloneUpdate;
|
|
2017
|
+
}
|
|
2018
|
+
this._updateIsShared = false;
|
|
1993
2019
|
};
|
|
1994
2020
|
|
|
1995
2021
|
/**
|
|
@@ -2022,7 +2048,7 @@ Query.prototype._fieldsForExec = function() {
|
|
|
2022
2048
|
*/
|
|
2023
2049
|
|
|
2024
2050
|
Query.prototype._updateForExec = function() {
|
|
2025
|
-
const update = clone(this
|
|
2051
|
+
const update = clone(this[queryUpdateSymbol], {
|
|
2026
2052
|
transform: false,
|
|
2027
2053
|
depopulate: true
|
|
2028
2054
|
});
|
|
@@ -2216,6 +2242,8 @@ Query.prototype.lean = function(v) {
|
|
|
2216
2242
|
*/
|
|
2217
2243
|
|
|
2218
2244
|
Query.prototype.set = function(path, val) {
|
|
2245
|
+
_cloneUpdateIfShared(this);
|
|
2246
|
+
|
|
2219
2247
|
if (typeof path === 'object') {
|
|
2220
2248
|
const keys = Object.keys(path);
|
|
2221
2249
|
for (const key of keys) {
|
|
@@ -2224,12 +2252,16 @@ Query.prototype.set = function(path, val) {
|
|
|
2224
2252
|
return this;
|
|
2225
2253
|
}
|
|
2226
2254
|
|
|
2227
|
-
|
|
2228
|
-
if (
|
|
2229
|
-
|
|
2255
|
+
let update = this[queryUpdateSymbol];
|
|
2256
|
+
if (update == null) {
|
|
2257
|
+
update = {};
|
|
2258
|
+
this[queryUpdateSymbol] = update;
|
|
2259
|
+
}
|
|
2260
|
+
if (path in update) {
|
|
2261
|
+
delete update[path];
|
|
2230
2262
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2263
|
+
update.$set = update.$set || {};
|
|
2264
|
+
update.$set[path] = val;
|
|
2233
2265
|
return this;
|
|
2234
2266
|
};
|
|
2235
2267
|
|
|
@@ -2249,7 +2281,7 @@ Query.prototype.set = function(path, val) {
|
|
|
2249
2281
|
*/
|
|
2250
2282
|
|
|
2251
2283
|
Query.prototype.get = function get(path) {
|
|
2252
|
-
const update = this
|
|
2284
|
+
const update = this[queryUpdateSymbol];
|
|
2253
2285
|
if (update == null) {
|
|
2254
2286
|
return void 0;
|
|
2255
2287
|
}
|
|
@@ -2333,6 +2365,7 @@ Query.prototype._unsetCastError = function _unsetCastError() {
|
|
|
2333
2365
|
* - `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.
|
|
2334
2366
|
* - `nearSphere`: use `$nearSphere` instead of `near()`. See the [`Query.prototype.nearSphere()` docs](https://mongoosejs.com/docs/api/query.html#Query.prototype.nearSphere())
|
|
2335
2367
|
* - `schemaLevelProjections`: if `false`, Mongoose will not apply schema-level `select: false` or `select: true` for this query
|
|
2368
|
+
* - `cloneUpdate`: if `false`, Mongoose will not clone updates before executing the query
|
|
2336
2369
|
*
|
|
2337
2370
|
* Mongoose maintains a separate object for internal options because
|
|
2338
2371
|
* Mongoose sends `Query.prototype.options` to the MongoDB server, and the
|
|
@@ -2421,6 +2454,12 @@ Query.prototype._find = async function _find() {
|
|
|
2421
2454
|
lean: mongooseOptions.lean || null
|
|
2422
2455
|
};
|
|
2423
2456
|
|
|
2457
|
+
// Only pass `defaults` through when it is non-nullish; `null` and
|
|
2458
|
+
// `undefined` are treated as "not set" and omitted from `createModel()`.
|
|
2459
|
+
if (mongooseOptions.defaults != null) {
|
|
2460
|
+
completeManyOptions.defaults = mongooseOptions.defaults;
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2424
2463
|
const options = this._optionsForExec();
|
|
2425
2464
|
|
|
2426
2465
|
const filter = this._conditions;
|
|
@@ -2553,9 +2592,10 @@ Query.prototype.merge = function(source) {
|
|
|
2553
2592
|
utils.merge(this.options, source.options, opts);
|
|
2554
2593
|
}
|
|
2555
2594
|
|
|
2556
|
-
if (source
|
|
2557
|
-
|
|
2558
|
-
|
|
2595
|
+
if (source[queryUpdateSymbol] != null) {
|
|
2596
|
+
_cloneUpdateIfShared(this);
|
|
2597
|
+
this[queryUpdateSymbol] || (this[queryUpdateSymbol] = {});
|
|
2598
|
+
utils.mergeClone(this[queryUpdateSymbol], source[queryUpdateSymbol]);
|
|
2559
2599
|
}
|
|
2560
2600
|
|
|
2561
2601
|
if (source._distinct) {
|
|
@@ -2691,7 +2731,7 @@ Query.prototype._completeMany = async function _completeMany(docs, fields, userP
|
|
|
2691
2731
|
const model = this.model;
|
|
2692
2732
|
return Promise.all(docs.map(doc => new Promise((resolve, reject) => {
|
|
2693
2733
|
const rawDoc = doc;
|
|
2694
|
-
doc = helpers.createModel(model, doc, fields, userProvidedFields);
|
|
2734
|
+
doc = helpers.createModel(model, doc, fields, userProvidedFields, opts);
|
|
2695
2735
|
if (opts.session != null) {
|
|
2696
2736
|
doc.$session(opts.session);
|
|
2697
2737
|
}
|
|
@@ -2849,9 +2889,10 @@ Query.prototype._applyTranslateAliases = function _applyTranslateAliases() {
|
|
|
2849
2889
|
}
|
|
2850
2890
|
|
|
2851
2891
|
if (this.model?.schema?.aliases && utils.hasOwnKeys(this.model.schema.aliases)) {
|
|
2892
|
+
_cloneUpdateIfShared(this);
|
|
2852
2893
|
this.model.translateAliases(this._conditions, true);
|
|
2853
2894
|
this.model.translateAliases(this._fields, true);
|
|
2854
|
-
this.model.translateAliases(this
|
|
2895
|
+
this.model.translateAliases(this[queryUpdateSymbol], true);
|
|
2855
2896
|
if (this._distinct != null && this.model.schema.aliases[this._distinct] != null) {
|
|
2856
2897
|
this._distinct = this.model.schema.aliases[this._distinct];
|
|
2857
2898
|
}
|
|
@@ -3405,6 +3446,7 @@ function prepareDiscriminatorCriteria(query) {
|
|
|
3405
3446
|
* @param {object|Query} [filter]
|
|
3406
3447
|
* @param {object} [update]
|
|
3407
3448
|
* @param {object} [options]
|
|
3449
|
+
* @param {boolean} [options.cloneUpdate=true] if `false`, Mongoose will not clone the update before executing the query
|
|
3408
3450
|
* @param {boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/7.0/interfaces/ModifyResult.html) rather than just the document
|
|
3409
3451
|
* @param {boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
|
3410
3452
|
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
|
@@ -3481,11 +3523,20 @@ Query.prototype.findOneAndUpdate = function(filter, update, options) {
|
|
|
3481
3523
|
options.updatePipeline = updatePipeline;
|
|
3482
3524
|
}
|
|
3483
3525
|
|
|
3526
|
+
if (!options.updatePipeline && Array.isArray(update)) {
|
|
3527
|
+
throw new MongooseError('Cannot pass an array to query updates unless the `updatePipeline` option is set.');
|
|
3528
|
+
}
|
|
3529
|
+
|
|
3484
3530
|
this.setOptions(options);
|
|
3485
3531
|
|
|
3486
3532
|
// apply doc
|
|
3487
3533
|
if (update) {
|
|
3488
|
-
this.
|
|
3534
|
+
if (this[queryUpdateSymbol] == null || utils.isEmptyObject(this[queryUpdateSymbol])) {
|
|
3535
|
+
this[queryUpdateSymbol] = update;
|
|
3536
|
+
this._updateIsShared = true;
|
|
3537
|
+
} else {
|
|
3538
|
+
this._mergeUpdate(update);
|
|
3539
|
+
}
|
|
3489
3540
|
}
|
|
3490
3541
|
|
|
3491
3542
|
return this;
|
|
@@ -3523,49 +3574,51 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
|
|
|
3523
3574
|
const options = this._optionsForExec(this.model);
|
|
3524
3575
|
convertNewToReturnDocument(options);
|
|
3525
3576
|
|
|
3526
|
-
this
|
|
3577
|
+
this[queryUpdateSymbol] = this._castUpdate(this[queryUpdateSymbol]);
|
|
3578
|
+
this._updateIsShared = false;
|
|
3579
|
+
decorateUpdateWithVersionKey(this[queryUpdateSymbol], options, this.schema.options.versionKey);
|
|
3527
3580
|
|
|
3528
|
-
this
|
|
3581
|
+
this[queryUpdateSymbol] = setDefaultsOnInsert(
|
|
3529
3582
|
this._conditions,
|
|
3530
3583
|
this.model.schema,
|
|
3531
|
-
this
|
|
3584
|
+
this[queryUpdateSymbol],
|
|
3532
3585
|
options,
|
|
3533
3586
|
this._mongooseOptions,
|
|
3534
3587
|
this
|
|
3535
3588
|
);
|
|
3536
3589
|
|
|
3537
|
-
if (!this
|
|
3590
|
+
if (!this[queryUpdateSymbol] || utils.hasOwnKeys(this[queryUpdateSymbol]) === false) {
|
|
3538
3591
|
if (options.upsert) {
|
|
3539
3592
|
// still need to do the upsert to empty doc
|
|
3540
|
-
const $set = clone(this
|
|
3593
|
+
const $set = clone(this[queryUpdateSymbol]);
|
|
3541
3594
|
delete $set._id;
|
|
3542
|
-
this
|
|
3595
|
+
this[queryUpdateSymbol] = { $set };
|
|
3543
3596
|
} else {
|
|
3544
3597
|
this._execCount = 0;
|
|
3545
3598
|
const res = await this._findOne();
|
|
3546
3599
|
return res;
|
|
3547
3600
|
}
|
|
3548
|
-
} else if (this
|
|
3549
|
-
throw this
|
|
3601
|
+
} else if (this[queryUpdateSymbol] instanceof Error) {
|
|
3602
|
+
throw this[queryUpdateSymbol];
|
|
3550
3603
|
} else {
|
|
3551
3604
|
// In order to make MongoDB 2.6 happy (see
|
|
3552
3605
|
// https://jira.mongodb.org/browse/SERVER-12266 and related issues)
|
|
3553
3606
|
// if we have an actual update document but $set is empty, junk the $set.
|
|
3554
|
-
if (this
|
|
3555
|
-
delete this
|
|
3607
|
+
if (this[queryUpdateSymbol].$set && utils.hasOwnKeys(this[queryUpdateSymbol].$set) === false) {
|
|
3608
|
+
delete this[queryUpdateSymbol].$set;
|
|
3556
3609
|
}
|
|
3557
3610
|
}
|
|
3558
3611
|
|
|
3559
3612
|
const runValidators = _getOption(this, 'runValidators', false);
|
|
3560
3613
|
if (runValidators) {
|
|
3561
|
-
await this.validate(this
|
|
3614
|
+
await this.validate(this[queryUpdateSymbol], options, false);
|
|
3562
3615
|
}
|
|
3563
3616
|
|
|
3564
|
-
if (typeof this.
|
|
3565
|
-
this
|
|
3617
|
+
if (typeof this[queryUpdateSymbol].toBSON === 'function') {
|
|
3618
|
+
this[queryUpdateSymbol] = this[queryUpdateSymbol].toBSON();
|
|
3566
3619
|
}
|
|
3567
3620
|
|
|
3568
|
-
let res = await this.mongooseCollection.findOneAndUpdate(this._conditions, this
|
|
3621
|
+
let res = await this.mongooseCollection.findOneAndUpdate(this._conditions, this[queryUpdateSymbol], options);
|
|
3569
3622
|
for (const fn of this._transforms) {
|
|
3570
3623
|
res = fn(res);
|
|
3571
3624
|
}
|
|
@@ -3701,6 +3754,7 @@ Query.prototype._findOneAndDelete = async function _findOneAndDelete() {
|
|
|
3701
3754
|
* @param {object} [filter]
|
|
3702
3755
|
* @param {object} [replacement]
|
|
3703
3756
|
* @param {object} [options]
|
|
3757
|
+
* @param {boolean} [options.cloneUpdate=true] if `false`, Mongoose will not clone the update before executing the query
|
|
3704
3758
|
* @param {boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/7.0/interfaces/ModifyResult.html) rather than just the document
|
|
3705
3759
|
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
|
3706
3760
|
* @param {boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
|
@@ -3796,13 +3850,13 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
|
|
|
3796
3850
|
const runValidators = _getOption(this, 'runValidators', false);
|
|
3797
3851
|
|
|
3798
3852
|
try {
|
|
3799
|
-
const update = new this.model(this
|
|
3853
|
+
const update = new this.model(this[queryUpdateSymbol], null, modelOpts);
|
|
3800
3854
|
if (runValidators) {
|
|
3801
3855
|
await update.validate();
|
|
3802
3856
|
} else if (update.$__.validationError) {
|
|
3803
3857
|
throw update.$__.validationError;
|
|
3804
3858
|
}
|
|
3805
|
-
this
|
|
3859
|
+
this[queryUpdateSymbol] = update.toBSON();
|
|
3806
3860
|
} catch (err) {
|
|
3807
3861
|
if (err instanceof ValidationError) {
|
|
3808
3862
|
throw err;
|
|
@@ -3812,7 +3866,7 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
|
|
|
3812
3866
|
throw validationError;
|
|
3813
3867
|
}
|
|
3814
3868
|
|
|
3815
|
-
let res = await this.mongooseCollection.findOneAndReplace(filter, this
|
|
3869
|
+
let res = await this.mongooseCollection.findOneAndReplace(filter, this[queryUpdateSymbol], options);
|
|
3816
3870
|
|
|
3817
3871
|
for (const fn of this._transforms) {
|
|
3818
3872
|
res = fn(res);
|
|
@@ -3874,6 +3928,7 @@ Query.prototype.findById = function(id, projection, options) {
|
|
|
3874
3928
|
* @param {any} id value of `_id` to query by
|
|
3875
3929
|
* @param {object} [doc]
|
|
3876
3930
|
* @param {object} [options]
|
|
3931
|
+
* @param {boolean} [options.cloneUpdate=true] if `false`, Mongoose will not clone the update before executing the query
|
|
3877
3932
|
* @param {boolean} [options.includeResultMetadata] if true, returns the full [ModifyResult from the MongoDB driver](https://mongodb.github.io/node-mongodb-native/7.0/interfaces/ModifyResult.html) rather than just the document
|
|
3878
3933
|
* @param {boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
|
3879
3934
|
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
|
|
@@ -4049,12 +4104,14 @@ function _completeManyLean(schema, docs, path, opts) {
|
|
|
4049
4104
|
*/
|
|
4050
4105
|
|
|
4051
4106
|
Query.prototype._mergeUpdate = function(update) {
|
|
4107
|
+
_cloneUpdateIfShared(this);
|
|
4108
|
+
|
|
4052
4109
|
const updatePipeline = this._mongooseOptions.updatePipeline;
|
|
4053
4110
|
if (!updatePipeline && Array.isArray(update)) {
|
|
4054
4111
|
throw new MongooseError('Cannot pass an array to query updates unless the `updatePipeline` option is set.');
|
|
4055
4112
|
}
|
|
4056
|
-
if (!this
|
|
4057
|
-
this
|
|
4113
|
+
if (!this[queryUpdateSymbol]) {
|
|
4114
|
+
this[queryUpdateSymbol] = Array.isArray(update) ? [] : {};
|
|
4058
4115
|
}
|
|
4059
4116
|
|
|
4060
4117
|
if (update == null || (typeof update === 'object' && utils.hasOwnKeys(update) === false)) {
|
|
@@ -4062,28 +4119,28 @@ Query.prototype._mergeUpdate = function(update) {
|
|
|
4062
4119
|
}
|
|
4063
4120
|
|
|
4064
4121
|
if (update instanceof Query) {
|
|
4065
|
-
if (Array.isArray(this
|
|
4066
|
-
throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this
|
|
4122
|
+
if (Array.isArray(this[queryUpdateSymbol])) {
|
|
4123
|
+
throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update[queryUpdateSymbol])})`);
|
|
4067
4124
|
}
|
|
4068
|
-
if (update
|
|
4069
|
-
utils.mergeClone(this
|
|
4125
|
+
if (update[queryUpdateSymbol]) {
|
|
4126
|
+
utils.mergeClone(this[queryUpdateSymbol], update[queryUpdateSymbol]);
|
|
4070
4127
|
}
|
|
4071
4128
|
} else if (Array.isArray(update)) {
|
|
4072
|
-
if (!Array.isArray(this
|
|
4129
|
+
if (!Array.isArray(this[queryUpdateSymbol])) {
|
|
4073
4130
|
// `_update` may be empty object by default, like in `doc.updateOne()`
|
|
4074
4131
|
// because we create the query first, then run hooks, then apply the update.
|
|
4075
|
-
if (this
|
|
4076
|
-
this
|
|
4132
|
+
if (this[queryUpdateSymbol] == null || utils.isEmptyObject(this[queryUpdateSymbol])) {
|
|
4133
|
+
this[queryUpdateSymbol] = [];
|
|
4077
4134
|
} else {
|
|
4078
|
-
throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this
|
|
4135
|
+
throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update)})`);
|
|
4079
4136
|
}
|
|
4080
4137
|
}
|
|
4081
|
-
this
|
|
4138
|
+
this[queryUpdateSymbol] = this[queryUpdateSymbol].concat(update);
|
|
4082
4139
|
} else {
|
|
4083
|
-
if (Array.isArray(this
|
|
4084
|
-
throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this
|
|
4140
|
+
if (Array.isArray(this[queryUpdateSymbol])) {
|
|
4141
|
+
throw new MongooseError(`Cannot mix array and object updates (current: ${_previewUpdate(this[queryUpdateSymbol])}, incoming: ${_previewUpdate(update)})`);
|
|
4085
4142
|
}
|
|
4086
|
-
utils.mergeClone(this
|
|
4143
|
+
utils.mergeClone(this[queryUpdateSymbol], update);
|
|
4087
4144
|
}
|
|
4088
4145
|
};
|
|
4089
4146
|
|
|
@@ -4157,30 +4214,31 @@ Query.prototype._updateMany = async function _updateMany() {
|
|
|
4157
4214
|
}
|
|
4158
4215
|
|
|
4159
4216
|
const options = this._optionsForExec(this.model);
|
|
4160
|
-
|
|
4161
|
-
|
|
4217
|
+
_cloneUpdateIfShared(this);
|
|
4218
|
+
this[queryUpdateSymbol] = this._castUpdate(this[queryUpdateSymbol]);
|
|
4219
|
+
if (this[queryUpdateSymbol] == null || utils.hasOwnKeys(this[queryUpdateSymbol]) === false) {
|
|
4162
4220
|
return { acknowledged: false };
|
|
4163
4221
|
}
|
|
4164
|
-
removeUnusedArrayFilters(this
|
|
4222
|
+
removeUnusedArrayFilters(this[queryUpdateSymbol], options);
|
|
4165
4223
|
|
|
4166
|
-
this
|
|
4224
|
+
this[queryUpdateSymbol] = setDefaultsOnInsert(
|
|
4167
4225
|
this._conditions,
|
|
4168
4226
|
this.model.schema,
|
|
4169
|
-
this
|
|
4227
|
+
this[queryUpdateSymbol],
|
|
4170
4228
|
options,
|
|
4171
4229
|
this._mongooseOptions,
|
|
4172
4230
|
this
|
|
4173
4231
|
);
|
|
4174
4232
|
|
|
4175
4233
|
if (_getOption(this, 'runValidators', false)) {
|
|
4176
|
-
await this.validate(this
|
|
4234
|
+
await this.validate(this[queryUpdateSymbol], options, false);
|
|
4177
4235
|
}
|
|
4178
4236
|
|
|
4179
|
-
if (typeof this.
|
|
4180
|
-
this
|
|
4237
|
+
if (typeof this[queryUpdateSymbol].toBSON === 'function') {
|
|
4238
|
+
this[queryUpdateSymbol] = this[queryUpdateSymbol].toBSON();
|
|
4181
4239
|
}
|
|
4182
4240
|
|
|
4183
|
-
return this.mongooseCollection.updateMany(this._conditions, this
|
|
4241
|
+
return this.mongooseCollection.updateMany(this._conditions, this[queryUpdateSymbol], options);
|
|
4184
4242
|
};
|
|
4185
4243
|
|
|
4186
4244
|
/**
|
|
@@ -4202,30 +4260,31 @@ Query.prototype._updateOne = async function _updateOne() {
|
|
|
4202
4260
|
}
|
|
4203
4261
|
|
|
4204
4262
|
const options = this._optionsForExec(this.model);
|
|
4205
|
-
|
|
4206
|
-
|
|
4263
|
+
_cloneUpdateIfShared(this);
|
|
4264
|
+
this[queryUpdateSymbol] = this._castUpdate(this[queryUpdateSymbol]);
|
|
4265
|
+
if (this[queryUpdateSymbol] == null || utils.hasOwnKeys(this[queryUpdateSymbol]) === false) {
|
|
4207
4266
|
return { acknowledged: false };
|
|
4208
4267
|
}
|
|
4209
|
-
removeUnusedArrayFilters(this
|
|
4268
|
+
removeUnusedArrayFilters(this[queryUpdateSymbol], options);
|
|
4210
4269
|
|
|
4211
|
-
this
|
|
4270
|
+
this[queryUpdateSymbol] = setDefaultsOnInsert(
|
|
4212
4271
|
this._conditions,
|
|
4213
4272
|
this.model.schema,
|
|
4214
|
-
this
|
|
4273
|
+
this[queryUpdateSymbol],
|
|
4215
4274
|
options,
|
|
4216
4275
|
this._mongooseOptions,
|
|
4217
4276
|
this
|
|
4218
4277
|
);
|
|
4219
4278
|
|
|
4220
4279
|
if (_getOption(this, 'runValidators', false)) {
|
|
4221
|
-
await this.validate(this
|
|
4280
|
+
await this.validate(this[queryUpdateSymbol], options, false);
|
|
4222
4281
|
}
|
|
4223
4282
|
|
|
4224
|
-
if (typeof this.
|
|
4225
|
-
this
|
|
4283
|
+
if (typeof this[queryUpdateSymbol].toBSON === 'function') {
|
|
4284
|
+
this[queryUpdateSymbol] = this[queryUpdateSymbol].toBSON();
|
|
4226
4285
|
}
|
|
4227
4286
|
|
|
4228
|
-
return this.mongooseCollection.updateOne(this._conditions, this
|
|
4287
|
+
return this.mongooseCollection.updateOne(this._conditions, this[queryUpdateSymbol], options);
|
|
4229
4288
|
};
|
|
4230
4289
|
|
|
4231
4290
|
/**
|
|
@@ -4247,18 +4306,18 @@ Query.prototype._replaceOne = async function _replaceOne() {
|
|
|
4247
4306
|
}
|
|
4248
4307
|
|
|
4249
4308
|
const options = this._optionsForExec(this.model);
|
|
4250
|
-
this
|
|
4251
|
-
removeUnusedArrayFilters(this
|
|
4309
|
+
this[queryUpdateSymbol] = new this.model(this[queryUpdateSymbol], null, { skipId: true });
|
|
4310
|
+
removeUnusedArrayFilters(this[queryUpdateSymbol], options);
|
|
4252
4311
|
|
|
4253
4312
|
if (_getOption(this, 'runValidators', false)) {
|
|
4254
|
-
await this.validate(this
|
|
4313
|
+
await this.validate(this[queryUpdateSymbol], options, true);
|
|
4255
4314
|
}
|
|
4256
4315
|
|
|
4257
|
-
if (typeof this.
|
|
4258
|
-
this
|
|
4316
|
+
if (typeof this[queryUpdateSymbol].toBSON === 'function') {
|
|
4317
|
+
this[queryUpdateSymbol] = this[queryUpdateSymbol].toBSON();
|
|
4259
4318
|
}
|
|
4260
4319
|
|
|
4261
|
-
return this.mongooseCollection.replaceOne(this._conditions, this
|
|
4320
|
+
return this.mongooseCollection.replaceOne(this._conditions, this[queryUpdateSymbol], options);
|
|
4262
4321
|
};
|
|
4263
4322
|
|
|
4264
4323
|
/**
|
|
@@ -4285,6 +4344,7 @@ Query.prototype._replaceOne = async function _replaceOne() {
|
|
|
4285
4344
|
* @param {object} [filter]
|
|
4286
4345
|
* @param {object|Array} [update] the update command. If array, this update will be treated as an update pipeline and not casted.
|
|
4287
4346
|
* @param {object} [options]
|
|
4347
|
+
* @param {boolean} [options.cloneUpdate=true] if `false`, Mongoose will not clone the update before executing the query
|
|
4288
4348
|
* @param {boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
|
|
4289
4349
|
* @param {boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
|
4290
4350
|
* @param {boolean} [options.upsert=false] if true, and no documents found, insert a new document
|
|
@@ -4360,6 +4420,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
|
|
|
4360
4420
|
* @param {object} [filter]
|
|
4361
4421
|
* @param {object|Array} [update] the update command. If array, this update will be treated as an update pipeline and not casted.
|
|
4362
4422
|
* @param {object} [options]
|
|
4423
|
+
* @param {boolean} [options.cloneUpdate=true] if `false`, Mongoose will not clone the update before executing the query
|
|
4363
4424
|
* @param {boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
|
|
4364
4425
|
* @param {boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
|
4365
4426
|
* @param {boolean} [options.upsert=false] if true, and no documents found, insert a new document
|
|
@@ -4429,6 +4490,7 @@ Query.prototype.updateOne = function(conditions, doc, options, callback) {
|
|
|
4429
4490
|
* @param {object} [filter]
|
|
4430
4491
|
* @param {object} [doc] the update command
|
|
4431
4492
|
* @param {object} [options]
|
|
4493
|
+
* @param {boolean} [options.cloneUpdate=true] if `false`, Mongoose will not clone the update before executing the query
|
|
4432
4494
|
* @param {boolean} [options.multipleCastError] by default, mongoose only returns the first error that occurred in casting the query. Turn on this option to aggregate all the cast errors.
|
|
4433
4495
|
* @param {boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
|
|
4434
4496
|
* @param {boolean} [options.upsert=false] if true, and no documents found, insert a new document
|
|
@@ -4510,11 +4572,20 @@ function _update(query, op, filter, doc, options, callback) {
|
|
|
4510
4572
|
options.updatePipeline = updatePipeline;
|
|
4511
4573
|
}
|
|
4512
4574
|
|
|
4575
|
+
if (!options?.updatePipeline && Array.isArray(doc)) {
|
|
4576
|
+
throw new MongooseError('Cannot pass an array to query updates unless the `updatePipeline` option is set.');
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4513
4579
|
if (utils.isObject(options)) {
|
|
4514
4580
|
query.setOptions(options);
|
|
4515
4581
|
}
|
|
4516
4582
|
|
|
4517
|
-
query.
|
|
4583
|
+
if (query[queryUpdateSymbol] == null || utils.isEmptyObject(query[queryUpdateSymbol])) {
|
|
4584
|
+
query[queryUpdateSymbol] = doc;
|
|
4585
|
+
query._updateIsShared = true;
|
|
4586
|
+
} else {
|
|
4587
|
+
query._mergeUpdate(doc);
|
|
4588
|
+
}
|
|
4518
4589
|
|
|
4519
4590
|
// Hooks
|
|
4520
4591
|
if (callback) {
|
|
@@ -4789,6 +4860,20 @@ function _executePreHooks(query, op) {
|
|
|
4789
4860
|
);
|
|
4790
4861
|
}
|
|
4791
4862
|
|
|
4863
|
+
function _cloneUpdateIfShared(query) {
|
|
4864
|
+
if (!query._updateIsShared) {
|
|
4865
|
+
return;
|
|
4866
|
+
}
|
|
4867
|
+
if (query.mongooseOptions().cloneUpdate === false) {
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
|
|
4871
|
+
query[queryUpdateSymbol] = clone(query[queryUpdateSymbol], {
|
|
4872
|
+
flattenDecimals: false
|
|
4873
|
+
});
|
|
4874
|
+
query._updateIsShared = false;
|
|
4875
|
+
}
|
|
4876
|
+
|
|
4792
4877
|
/**
|
|
4793
4878
|
* Executes the query returning a `Promise` which will be
|
|
4794
4879
|
* resolved with either the doc(s) or rejected with the error.
|
package/lib/queryHelpers.js
CHANGED
|
@@ -84,28 +84,29 @@ exports.createModel = function createModel(model, doc, fields, userProvidedField
|
|
|
84
84
|
discriminatorMapping.key :
|
|
85
85
|
null;
|
|
86
86
|
|
|
87
|
+
const _opts = {
|
|
88
|
+
skipId: true,
|
|
89
|
+
isNew: false,
|
|
90
|
+
willInit: true
|
|
91
|
+
};
|
|
92
|
+
if (options?.defaults != null) {
|
|
93
|
+
_opts.defaults = options.defaults;
|
|
94
|
+
}
|
|
95
|
+
if (options?.strict != null) {
|
|
96
|
+
_opts.strict = options.strict;
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
const value = doc[key];
|
|
88
100
|
if (key && value && model.discriminators) {
|
|
89
101
|
const discriminator = model.discriminators[value] || getDiscriminatorByValue(model.discriminators, value);
|
|
90
102
|
if (discriminator) {
|
|
91
103
|
const _fields = clone(userProvidedFields);
|
|
92
104
|
exports.applyPaths(_fields, discriminator.schema);
|
|
93
|
-
|
|
105
|
+
|
|
94
106
|
return new discriminator(undefined, _fields, _opts);
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
const _opts = {
|
|
99
|
-
skipId: true,
|
|
100
|
-
isNew: false,
|
|
101
|
-
willInit: true
|
|
102
|
-
};
|
|
103
|
-
if (options != null && 'defaults' in options) {
|
|
104
|
-
_opts.defaults = options.defaults;
|
|
105
|
-
}
|
|
106
|
-
if (options != null && 'strict' in options) {
|
|
107
|
-
_opts.strict = options.strict;
|
|
108
|
-
}
|
|
109
110
|
return new model(undefined, fields, _opts);
|
|
110
111
|
};
|
|
111
112
|
|
package/lib/schema.js
CHANGED
|
@@ -3089,8 +3089,27 @@ function isArrayFilter(piece) {
|
|
|
3089
3089
|
|
|
3090
3090
|
Schema.prototype._preCompile = function _preCompile() {
|
|
3091
3091
|
this.plugin(idGetter, { deduplicate: true });
|
|
3092
|
+
_precomputeOptimisticConcurrency(this);
|
|
3092
3093
|
};
|
|
3093
3094
|
|
|
3095
|
+
/*!
|
|
3096
|
+
* Build precomputed sets for optimisticConcurrency include/exclude,
|
|
3097
|
+
* expanding user-specified paths to include all schema subpaths so that
|
|
3098
|
+
* lookups at save time are a simple `Set.has()`.
|
|
3099
|
+
*/
|
|
3100
|
+
|
|
3101
|
+
function _precomputeOptimisticConcurrency(schema) {
|
|
3102
|
+
const opt = schema.options.optimisticConcurrency;
|
|
3103
|
+
if (!opt || opt === true) {
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
if (Array.isArray(opt)) {
|
|
3107
|
+
schema.options._optimisticConcurrencySet = new Set(opt);
|
|
3108
|
+
} else if (Array.isArray(opt.exclude)) {
|
|
3109
|
+
schema.options._optimisticConcurrencyExcludeSet = new Set(opt.exclude);
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
|
|
3094
3113
|
/**
|
|
3095
3114
|
* Returns a JSON schema representation of this Schema.
|
|
3096
3115
|
*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongoose",
|
|
3
3
|
"description": "Mongoose MongoDB ODM",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.5.0",
|
|
5
5
|
"author": "Guillermo Rauch <guillermo@learnboost.com>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongodb",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"type": "commonjs",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"kareem": "3.
|
|
23
|
+
"kareem": "3.3.0",
|
|
24
24
|
"mongodb": "~7.1",
|
|
25
25
|
"mpath": "0.9.0",
|
|
26
26
|
"mquery": "6.0.0",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"linkinator": "7.x",
|
|
50
50
|
"lodash.isequal": "4.5.0",
|
|
51
51
|
"lodash.isequalwith": "4.4.0",
|
|
52
|
-
"markdownlint-cli2": "
|
|
53
|
-
"marked": "
|
|
52
|
+
"markdownlint-cli2": "0.22.0",
|
|
53
|
+
"marked": "17.0.5",
|
|
54
54
|
"mkdirp": "^3.0.1",
|
|
55
55
|
"mocha": "11.7.5",
|
|
56
56
|
"moment": "2.30.1",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"mongodb-memory-server": "11.0.1",
|
|
59
59
|
"mongodb-runner": "^6.0.0",
|
|
60
60
|
"ncp": "^2.0.0",
|
|
61
|
-
"pug": "3.0.
|
|
62
|
-
"sinon": "21.0.
|
|
63
|
-
"tstyche": "^7.0.0
|
|
61
|
+
"pug": "3.0.4",
|
|
62
|
+
"sinon": "21.0.3",
|
|
63
|
+
"tstyche": "^7.0.0",
|
|
64
64
|
"typescript": "5.9.3",
|
|
65
65
|
"typescript-eslint": "^8.31.1",
|
|
66
66
|
"uuid": "11.1.0"
|
|
@@ -82,7 +82,7 @@ declare module 'mongoose' {
|
|
|
82
82
|
*/
|
|
83
83
|
debug?:
|
|
84
84
|
| boolean
|
|
85
|
-
| { color?: boolean; shell?: boolean; }
|
|
85
|
+
| { color?: boolean; shell?: boolean; timestamp?: boolean; }
|
|
86
86
|
| stream.Writable
|
|
87
87
|
| ((collectionName: string, methodName: string, ...methodArgs: any[]) => void);
|
|
88
88
|
|
package/types/query.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ declare module 'mongoose' {
|
|
|
51
51
|
type QueryFilter<T> = IsItRecordAndNotAny<T> extends true ? _QueryFilter<WithLevel1NestedPaths<T>> : _QueryFilterLooseId<Record<string, any>>;
|
|
52
52
|
|
|
53
53
|
type MongooseBaseQueryOptionKeys =
|
|
54
|
+
| 'cloneUpdate'
|
|
54
55
|
| 'context'
|
|
55
56
|
| 'middleware'
|
|
56
57
|
| 'multipleCastError'
|
|
@@ -108,6 +109,10 @@ declare module 'mongoose' {
|
|
|
108
109
|
collation?: mongodb.CollationOptions;
|
|
109
110
|
comment?: any;
|
|
110
111
|
context?: string;
|
|
112
|
+
/**
|
|
113
|
+
* If `false`, Mongoose will not clone the update before executing the query.
|
|
114
|
+
*/
|
|
115
|
+
cloneUpdate?: boolean;
|
|
111
116
|
explain?: mongodb.ExplainVerbosityLike;
|
|
112
117
|
fields?: any | string;
|
|
113
118
|
hint?: mongodb.Hint;
|
|
@@ -129,6 +134,11 @@ declare module 'mongoose' {
|
|
|
129
134
|
*/
|
|
130
135
|
new?: boolean;
|
|
131
136
|
|
|
137
|
+
/**
|
|
138
|
+
* If `false`, skip applying default schema values to the returned document(s).
|
|
139
|
+
* @default true
|
|
140
|
+
*/
|
|
141
|
+
defaults?: boolean;
|
|
132
142
|
overwriteDiscriminatorKey?: boolean;
|
|
133
143
|
/**
|
|
134
144
|
* Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert).
|
|
@@ -884,7 +894,7 @@ declare module 'mongoose' {
|
|
|
884
894
|
setQuery(val: QueryFilter<RawDocType> | null): void;
|
|
885
895
|
setQuery(val: Query<any, any> | null): void;
|
|
886
896
|
|
|
887
|
-
setUpdate(update: UpdateQuery<RawDocType> | UpdateWithAggregationPipeline): void;
|
|
897
|
+
setUpdate(update: UpdateQuery<RawDocType> | UpdateWithAggregationPipeline, cloneUpdate?: boolean): void;
|
|
888
898
|
|
|
889
899
|
/** Specifies an `$size` query condition. When called with one argument, the most recent path passed to `where()` is used. */
|
|
890
900
|
size(path: string, val: number): this;
|