mongoose 9.2.1 → 9.2.2
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 +44 -8
- package/lib/helpers/document/isInPathsToSave.js +24 -0
- package/lib/helpers/populate/getModelsMapForPopulate.js +160 -42
- package/lib/helpers/populate/getSchemaTypes.js +3 -2
- package/lib/helpers/populate/modelNamesFromRefPath.js +10 -2
- package/lib/helpers/setDefaultsOnInsert.js +15 -1
- package/lib/model.js +42 -14
- package/lib/schemaType.js +0 -1
- package/package.json +4 -3
- package/tstyche.config.json +6 -0
package/lib/document.js
CHANGED
|
@@ -22,6 +22,7 @@ const $__hasIncludedChildren = require('./helpers/projection/hasIncludedChildren
|
|
|
22
22
|
const applyDefaults = require('./helpers/document/applyDefaults');
|
|
23
23
|
const cleanModifiedSubpaths = require('./helpers/document/cleanModifiedSubpaths');
|
|
24
24
|
const clone = require('./helpers/clone');
|
|
25
|
+
const isInPathsToSave = require('./helpers/document/isInPathsToSave');
|
|
25
26
|
const compile = require('./helpers/document/compile').compile;
|
|
26
27
|
const defineKey = require('./helpers/document/compile').defineKey;
|
|
27
28
|
const firstKey = require('./helpers/firstKey');
|
|
@@ -1428,11 +1429,25 @@ Document.prototype.$set = function $set(path, val, type, options) {
|
|
|
1428
1429
|
}
|
|
1429
1430
|
|
|
1430
1431
|
// Check refPath
|
|
1431
|
-
|
|
1432
|
+
let refPath = schema.options.refPath;
|
|
1432
1433
|
if (refPath == null) {
|
|
1433
1434
|
return false;
|
|
1434
1435
|
}
|
|
1435
|
-
|
|
1436
|
+
|
|
1437
|
+
if (typeof refPath === 'function' && !refPath[modelSymbol]) {
|
|
1438
|
+
let fullPath = path;
|
|
1439
|
+
const fullPathWithIndexes = this.$__fullPathWithIndexes?.();
|
|
1440
|
+
if (fullPathWithIndexes?.length) {
|
|
1441
|
+
fullPath = fullPathWithIndexes + '.' + path;
|
|
1442
|
+
}
|
|
1443
|
+
refPath = refPath.call(this, this, fullPath);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
if (typeof refPath !== 'string') {
|
|
1447
|
+
throw new MongooseError('`refPath` must be a string or a function that returns a string, got ' + inspect(refPath));
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const modelName = this.ownerDocument().get(refPath);
|
|
1436
1451
|
return modelName === model.modelName || modelName === model.baseModelName;
|
|
1437
1452
|
})();
|
|
1438
1453
|
|
|
@@ -4998,26 +5013,47 @@ Document.prototype.getChanges = function() {
|
|
|
4998
5013
|
/**
|
|
4999
5014
|
* Produces a special query document of the modified properties used in updates.
|
|
5000
5015
|
*
|
|
5016
|
+
* @param {string[]|null} [pathsToSave] paths to include in delta generation
|
|
5017
|
+
* @param {Set<string>|null} [pathsToSaveSet] pre-built Set of pathsToSave for O(1) exact lookup
|
|
5001
5018
|
* @api private
|
|
5002
5019
|
* @method $__delta
|
|
5003
5020
|
* @memberOf Document
|
|
5004
5021
|
* @instance
|
|
5005
5022
|
*/
|
|
5006
5023
|
|
|
5007
|
-
Document.prototype.$__delta = function $__delta() {
|
|
5008
|
-
const
|
|
5024
|
+
Document.prototype.$__delta = function $__delta(pathsToSave, pathsToSaveSet) {
|
|
5025
|
+
const allDirty = this.$__dirty();
|
|
5026
|
+
let dirty = allDirty;
|
|
5027
|
+
let unsavedDirty = null;
|
|
5028
|
+
if (pathsToSave != null) {
|
|
5029
|
+
dirty = [];
|
|
5030
|
+
unsavedDirty = [];
|
|
5031
|
+
for (const data of allDirty) {
|
|
5032
|
+
if (isInPathsToSave(data.path, pathsToSaveSet, pathsToSave)) {
|
|
5033
|
+
dirty.push(data);
|
|
5034
|
+
} else {
|
|
5035
|
+
unsavedDirty.push(data);
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5009
5039
|
const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
|
|
5010
5040
|
if (optimisticConcurrency) {
|
|
5011
5041
|
if (Array.isArray(optimisticConcurrency)) {
|
|
5012
5042
|
const optCon = new Set(optimisticConcurrency);
|
|
5013
5043
|
const modPaths = this.modifiedPaths();
|
|
5014
|
-
|
|
5044
|
+
const hasRelevantModPaths = pathsToSave == null ?
|
|
5045
|
+
modPaths.find(path => optCon.has(path)) :
|
|
5046
|
+
modPaths.find(path => optCon.has(path) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5047
|
+
if (hasRelevantModPaths) {
|
|
5015
5048
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5016
5049
|
}
|
|
5017
5050
|
} else if (Array.isArray(optimisticConcurrency?.exclude)) {
|
|
5018
5051
|
const excluded = new Set(optimisticConcurrency.exclude);
|
|
5019
5052
|
const modPaths = this.modifiedPaths();
|
|
5020
|
-
|
|
5053
|
+
const hasRelevantModPaths = pathsToSave == null ?
|
|
5054
|
+
modPaths.find(path => !excluded.has(path)) :
|
|
5055
|
+
modPaths.find(path => !excluded.has(path) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5056
|
+
if (hasRelevantModPaths) {
|
|
5021
5057
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5022
5058
|
}
|
|
5023
5059
|
} else {
|
|
@@ -5123,10 +5159,10 @@ Document.prototype.$__delta = function $__delta() {
|
|
|
5123
5159
|
}
|
|
5124
5160
|
|
|
5125
5161
|
if (utils.hasOwnKeys(delta) === false) {
|
|
5126
|
-
return [where, null];
|
|
5162
|
+
return [where, null, unsavedDirty];
|
|
5127
5163
|
}
|
|
5128
5164
|
|
|
5129
|
-
return [where, delta];
|
|
5165
|
+
return [where, delta, unsavedDirty];
|
|
5130
5166
|
};
|
|
5131
5167
|
|
|
5132
5168
|
/**
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns true if `path` is included by the `pathsToSave` filter.
|
|
5
|
+
* Matches exact paths and child paths (e.g. 'metadata.views' is included by 'metadata').
|
|
6
|
+
*
|
|
7
|
+
* @param {string} path
|
|
8
|
+
* @param {Set<string>} pathsToSaveSet - pre-built Set of pathsToSave for O(1) exact lookup
|
|
9
|
+
* @param {string[]} pathsToSave - original array, used for prefix matching
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
module.exports = function isInPathsToSave(path, pathsToSaveSet, pathsToSave) {
|
|
13
|
+
if (pathsToSaveSet.has(path)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (const pathToSave of pathsToSave) {
|
|
18
|
+
if (path.slice(0, pathToSave.length) === pathToSave && path.charAt(pathToSave.length) === '.') {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
@@ -17,6 +17,9 @@ const modelSymbol = require('../symbols').modelSymbol;
|
|
|
17
17
|
const populateModelSymbol = require('../symbols').populateModelSymbol;
|
|
18
18
|
const schemaMixedSymbol = require('../../schema/symbols').schemaMixedSymbol;
|
|
19
19
|
const StrictPopulate = require('../../error/strictPopulate');
|
|
20
|
+
const numericPathSegmentPattern = '\\.\\d+(?=\\.|$)';
|
|
21
|
+
const hasNumericPathSegmentRE = new RegExp(numericPathSegmentPattern);
|
|
22
|
+
const numericPathSegmentRE = new RegExp(numericPathSegmentPattern, 'g');
|
|
20
23
|
|
|
21
24
|
module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
22
25
|
let doc;
|
|
@@ -43,7 +46,8 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
43
46
|
return _virtualPopulate(model, docs, options, _virtualRes);
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
const parts = modelSchema.paths[options.path]?.splitPath() ?? options.path.split('.');
|
|
50
|
+
let allSchemaTypes = getSchemaTypes(model, modelSchema, null, options.path, parts);
|
|
47
51
|
allSchemaTypes = Array.isArray(allSchemaTypes) ? allSchemaTypes : [allSchemaTypes].filter(v => v != null);
|
|
48
52
|
|
|
49
53
|
const isStrictPopulateDisabled = options.strictPopulate === false || options.options?.strictPopulate === false;
|
|
@@ -63,7 +67,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
const docSchema = doc?.$__ != null ? doc.$__schema : modelSchema;
|
|
66
|
-
schema = getSchemaTypes(model, docSchema, doc, options.path);
|
|
70
|
+
schema = getSchemaTypes(model, docSchema, doc, options.path, parts);
|
|
67
71
|
|
|
68
72
|
// Special case: populating a path that's a DocumentArray unless
|
|
69
73
|
// there's an explicit `ref` or `refPath` re: gh-8946
|
|
@@ -201,8 +205,11 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
201
205
|
data.modelNamesInOrder = modelNamesInOrder;
|
|
202
206
|
|
|
203
207
|
if (isRefPath) {
|
|
208
|
+
const normalizedRefPathForDiscriminators = typeof normalizedRefPath === 'string' ?
|
|
209
|
+
normalizedRefPath.replace(numericPathSegmentRE, '') :
|
|
210
|
+
normalizedRefPath;
|
|
204
211
|
const embeddedDiscriminatorModelNames = _findRefPathForDiscriminators(doc,
|
|
205
|
-
modelSchema, data, options,
|
|
212
|
+
modelSchema, data, options, normalizedRefPathForDiscriminators, ret);
|
|
206
213
|
|
|
207
214
|
modelNames = embeddedDiscriminatorModelNames || modelNames;
|
|
208
215
|
}
|
|
@@ -215,23 +222,23 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
215
222
|
}
|
|
216
223
|
return map;
|
|
217
224
|
|
|
218
|
-
function _getModelNames(doc,
|
|
225
|
+
function _getModelNames(doc, schemaType, modelNameFromQuery, model) {
|
|
219
226
|
let modelNames;
|
|
220
227
|
let isRefPath = false;
|
|
221
228
|
let justOne = null;
|
|
222
229
|
|
|
223
|
-
const originalSchema =
|
|
224
|
-
if (
|
|
225
|
-
|
|
230
|
+
const originalSchema = schemaType;
|
|
231
|
+
if (schemaType?.instance === 'Array') {
|
|
232
|
+
schemaType = schemaType.embeddedSchemaType;
|
|
226
233
|
}
|
|
227
|
-
if (
|
|
228
|
-
|
|
234
|
+
if (schemaType?.$isSchemaMap) {
|
|
235
|
+
schemaType = schemaType.$__schemaType;
|
|
229
236
|
}
|
|
230
237
|
|
|
231
|
-
const ref =
|
|
232
|
-
refPath =
|
|
233
|
-
if (
|
|
234
|
-
|
|
238
|
+
const ref = schemaType?.options?.ref;
|
|
239
|
+
refPath = schemaType?.options?.refPath;
|
|
240
|
+
if (schemaType != null &&
|
|
241
|
+
schemaType[schemaMixedSymbol] &&
|
|
235
242
|
!ref &&
|
|
236
243
|
!refPath &&
|
|
237
244
|
!modelNameFromQuery) {
|
|
@@ -242,19 +249,9 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
242
249
|
modelNames = [modelNameFromQuery]; // query options
|
|
243
250
|
} else if (refPath != null) {
|
|
244
251
|
if (typeof refPath === 'function') {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
utils.array.flatten(vals) :
|
|
249
|
-
(vals ? [vals] : []);
|
|
250
|
-
|
|
251
|
-
modelNames = new Set();
|
|
252
|
-
for (const subdoc of subdocsBeingPopulated) {
|
|
253
|
-
refPath = refPath.call(subdoc, subdoc, options.path);
|
|
254
|
-
modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
|
|
255
|
-
forEach(name => modelNames.add(name));
|
|
256
|
-
}
|
|
257
|
-
modelNames = Array.from(modelNames);
|
|
252
|
+
const res = _getModelNamesFromFunctionRefPath(refPath, doc, schemaType, options.path, modelSchema, options._queryProjection);
|
|
253
|
+
modelNames = res.modelNames;
|
|
254
|
+
refPath = res.refPath;
|
|
258
255
|
} else {
|
|
259
256
|
modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
|
|
260
257
|
}
|
|
@@ -268,7 +265,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
268
265
|
let modelForCurrentDoc = model;
|
|
269
266
|
const discriminatorKey = model.schema.options.discriminatorKey;
|
|
270
267
|
|
|
271
|
-
if (!
|
|
268
|
+
if (!schemaType && discriminatorKey && (discriminatorValue = utils.getValue(discriminatorKey, doc))) {
|
|
272
269
|
// `modelNameForFind` is the discriminator value, so we might need
|
|
273
270
|
// find the discriminated model name
|
|
274
271
|
const discriminatorModel = getDiscriminatorByValue(model.discriminators, discriminatorValue) || model;
|
|
@@ -288,7 +285,7 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
288
285
|
schemaForCurrentDoc = schemaForCurrentDoc.embeddedSchemaType;
|
|
289
286
|
}
|
|
290
287
|
} else {
|
|
291
|
-
schemaForCurrentDoc =
|
|
288
|
+
schemaForCurrentDoc = schemaType;
|
|
292
289
|
}
|
|
293
290
|
|
|
294
291
|
if (originalSchema && originalSchema.path.endsWith('.$*')) {
|
|
@@ -322,22 +319,12 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
322
319
|
ref = handleRefFunction(ref, doc);
|
|
323
320
|
modelNames = [ref];
|
|
324
321
|
}
|
|
325
|
-
} else if ((
|
|
322
|
+
} else if ((refPath = get(schemaForCurrentDoc, 'options.refPath')) != null) {
|
|
326
323
|
isRefPath = true;
|
|
327
324
|
if (typeof refPath === 'function') {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
utils.array.flatten(vals) :
|
|
332
|
-
(vals ? [vals] : []);
|
|
333
|
-
|
|
334
|
-
modelNames = new Set();
|
|
335
|
-
for (const subdoc of subdocsBeingPopulated) {
|
|
336
|
-
refPath = refPath.call(subdoc, subdoc, options.path);
|
|
337
|
-
modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection).
|
|
338
|
-
forEach(name => modelNames.add(name));
|
|
339
|
-
}
|
|
340
|
-
modelNames = Array.from(modelNames);
|
|
325
|
+
const res = _getModelNamesFromFunctionRefPath(refPath, doc, schemaForCurrentDoc, options.path, modelSchema, options._queryProjection);
|
|
326
|
+
modelNames = res.modelNames;
|
|
327
|
+
refPath = res.refPath;
|
|
341
328
|
} else {
|
|
342
329
|
modelNames = modelNamesFromRefPath(refPath, doc, options.path, modelSchema, options._queryProjection);
|
|
343
330
|
}
|
|
@@ -361,6 +348,137 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
|
|
|
361
348
|
}
|
|
362
349
|
};
|
|
363
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Resolve model names for function-style `refPath` and return the first
|
|
353
|
+
* resolved refPath value so discriminator handling can inspect it later.
|
|
354
|
+
*
|
|
355
|
+
* @param {Function} refPath
|
|
356
|
+
* @param {Document|Object} doc
|
|
357
|
+
* @param {SchemaType} schema
|
|
358
|
+
* @param {String} populatePath
|
|
359
|
+
* @param {Schema} modelSchema
|
|
360
|
+
* @param {Object} queryProjection
|
|
361
|
+
* @returns {{modelNames: String[], refPath: String|null}}
|
|
362
|
+
* @private
|
|
363
|
+
*/
|
|
364
|
+
function _getModelNamesFromFunctionRefPath(refPath, doc, schemaType, populatePath, modelSchema, queryProjection) {
|
|
365
|
+
const modelNames = [];
|
|
366
|
+
let normalizedRefPath = null;
|
|
367
|
+
const schemaPath = schemaType?.path;
|
|
368
|
+
|
|
369
|
+
if (schemaPath != null &&
|
|
370
|
+
populatePath.length > schemaPath.length + 1 &&
|
|
371
|
+
populatePath.charAt(populatePath.length - schemaPath.length - 1) === '.' &&
|
|
372
|
+
populatePath.slice(populatePath.length - schemaPath.length) === schemaPath
|
|
373
|
+
) {
|
|
374
|
+
const subdocPath = populatePath.slice(0, populatePath.length - schemaPath.length - 1);
|
|
375
|
+
const segments = subdocPath.indexOf('.') === -1 ? [subdocPath] : subdocPath.split('.');
|
|
376
|
+
let hasSubdoc = false;
|
|
377
|
+
|
|
378
|
+
walkSubdocs(doc, '', 0);
|
|
379
|
+
if (hasSubdoc) {
|
|
380
|
+
return { modelNames, refPath: normalizedRefPath };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function walkSubdocs(currentSubdoc, indexedPathPrefix, segmentIndex) {
|
|
384
|
+
if (currentSubdoc == null) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (segmentIndex >= segments.length) {
|
|
389
|
+
hasSubdoc = true;
|
|
390
|
+
const indexedPath = indexedPathPrefix + '.' + schemaPath;
|
|
391
|
+
const subdocRefPath = refPath.call(currentSubdoc, currentSubdoc, indexedPath);
|
|
392
|
+
normalizedRefPath = normalizedRefPath || subdocRefPath;
|
|
393
|
+
modelNames.push(..._getModelNamesFromRefPath(subdocRefPath, doc, populatePath, indexedPath, modelSchema, queryProjection));
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const segment = segments[segmentIndex];
|
|
398
|
+
const source = currentSubdoc?._doc ?? currentSubdoc;
|
|
399
|
+
|
|
400
|
+
if (segment === '$*') {
|
|
401
|
+
if (source instanceof Map) {
|
|
402
|
+
for (const [key, value] of source.entries()) {
|
|
403
|
+
if (value != null) {
|
|
404
|
+
const valuePath = indexedPathPrefix.length === 0 ?
|
|
405
|
+
key :
|
|
406
|
+
indexedPathPrefix + '.' + key;
|
|
407
|
+
walkSubdocs(value, valuePath, segmentIndex + 1);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (source != null && typeof source === 'object') {
|
|
414
|
+
for (const key of Object.keys(source)) {
|
|
415
|
+
const value = source[key];
|
|
416
|
+
if (value != null) {
|
|
417
|
+
const valuePath = indexedPathPrefix.length === 0 ?
|
|
418
|
+
key :
|
|
419
|
+
indexedPathPrefix + '.' + key;
|
|
420
|
+
walkSubdocs(value, valuePath, segmentIndex + 1);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const child = source[segment];
|
|
428
|
+
const childPath = indexedPathPrefix.length === 0 ?
|
|
429
|
+
segment :
|
|
430
|
+
indexedPathPrefix + '.' + segment;
|
|
431
|
+
|
|
432
|
+
if (Array.isArray(child)) {
|
|
433
|
+
for (let i = 0; i < child.length; ++i) {
|
|
434
|
+
if (child[i] != null) {
|
|
435
|
+
walkSubdocs(child[i], childPath + '.' + i, segmentIndex + 1);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} else if (child != null) {
|
|
439
|
+
walkSubdocs(child, childPath, segmentIndex + 1);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const topLevelRefPath = refPath.call(doc, doc, populatePath);
|
|
445
|
+
normalizedRefPath = normalizedRefPath || topLevelRefPath;
|
|
446
|
+
modelNames.push(...modelNamesFromRefPath(topLevelRefPath, doc, populatePath, modelSchema, queryProjection));
|
|
447
|
+
|
|
448
|
+
return { modelNames, refPath: normalizedRefPath };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Resolve model names from a refPath, preferring the indexed populated path
|
|
453
|
+
* for array subdocuments and falling back to the original populate path if
|
|
454
|
+
* normalization fails.
|
|
455
|
+
*
|
|
456
|
+
* @param {String|Function} refPath
|
|
457
|
+
* @param {Document|Object} doc
|
|
458
|
+
* @param {String} populatePath
|
|
459
|
+
* @param {String} indexedPath
|
|
460
|
+
* @param {Schema} modelSchema
|
|
461
|
+
* @param {Object} queryProjection
|
|
462
|
+
* @returns {String[]}
|
|
463
|
+
* @private
|
|
464
|
+
*/
|
|
465
|
+
function _getModelNamesFromRefPath(refPath, doc, populatePath, indexedPath, modelSchema, queryProjection) {
|
|
466
|
+
const populatedPath = typeof refPath === 'string' && hasNumericPathSegmentRE.test(refPath) ?
|
|
467
|
+
populatePath :
|
|
468
|
+
indexedPath;
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
return modelNamesFromRefPath(refPath, doc, populatedPath, modelSchema, queryProjection);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (populatedPath === indexedPath &&
|
|
474
|
+
typeof error?.message === 'string' &&
|
|
475
|
+
error.message.startsWith('Could not normalize ref path')) {
|
|
476
|
+
return modelNamesFromRefPath(refPath, doc, populatePath, modelSchema, queryProjection);
|
|
477
|
+
}
|
|
478
|
+
throw error;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
364
482
|
/*!
|
|
365
483
|
* ignore
|
|
366
484
|
*/
|
|
@@ -22,10 +22,11 @@ const populateModelSymbol = require('../symbols').populateModelSymbol;
|
|
|
22
22
|
* @param {Schema} schema
|
|
23
23
|
* @param {Object} doc POJO
|
|
24
24
|
* @param {string} path
|
|
25
|
+
* @param {string[]} [parts] pass in pre-split `path` to avoid extra splitting
|
|
25
26
|
* @api private
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
|
-
module.exports = function getSchemaTypes(model, schema, doc, path) {
|
|
29
|
+
module.exports = function getSchemaTypes(model, schema, doc, path, parts) {
|
|
29
30
|
const pathschema = schema.path(path);
|
|
30
31
|
const topLevelDoc = doc;
|
|
31
32
|
if (pathschema) {
|
|
@@ -217,7 +218,7 @@ module.exports = function getSchemaTypes(model, schema, doc, path) {
|
|
|
217
218
|
}
|
|
218
219
|
}
|
|
219
220
|
// look for arrays
|
|
220
|
-
|
|
221
|
+
parts = parts || path.split('.');
|
|
221
222
|
for (let i = 0; i < parts.length; ++i) {
|
|
222
223
|
if (parts[i] === '$') {
|
|
223
224
|
// Re: gh-5628, because `schema.path()` doesn't take $ into account.
|
|
@@ -14,7 +14,11 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
|
|
|
14
14
|
return [];
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
if (typeof refPath
|
|
17
|
+
if (typeof refPath !== 'string') {
|
|
18
|
+
throw new MongooseError('`refPath` must be a string or a function that returns a string, got ' + util.inspect(refPath));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (queryProjection != null && isPathExcluded(queryProjection, refPath)) {
|
|
18
22
|
throw new MongooseError('refPath `' + refPath + '` must not be excluded in projection, got ' +
|
|
19
23
|
util.inspect(queryProjection));
|
|
20
24
|
}
|
|
@@ -35,7 +39,11 @@ module.exports = function modelNamesFromRefPath(refPath, doc, populatedPath, mod
|
|
|
35
39
|
// 2nd, 4th, etc. will be numeric props. For example: `[ 'a', '.0.', 'b' ]`
|
|
36
40
|
for (let i = 0; i < chunks.length; i += 2) {
|
|
37
41
|
const chunk = chunks[i];
|
|
38
|
-
if (
|
|
42
|
+
if (
|
|
43
|
+
_remaining.length >= chunk.length + 1 &&
|
|
44
|
+
_remaining.charAt(chunk.length) === '.' &&
|
|
45
|
+
_remaining.slice(0, chunk.length) === chunk
|
|
46
|
+
) {
|
|
39
47
|
_refPath += _remaining.substring(0, chunk.length) + chunks[i + 1];
|
|
40
48
|
_remaining = _remaining.substring(chunk.length + 1);
|
|
41
49
|
} else if (i === chunks.length - 1) {
|
|
@@ -22,7 +22,7 @@ module.exports = function(filter, schema, castedDoc, options) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const keys = Object.keys(castedDoc || {});
|
|
25
|
-
const updatedKeys =
|
|
25
|
+
const updatedKeys = Object.create(null);
|
|
26
26
|
const updatedValues = {};
|
|
27
27
|
const numKeys = keys.length;
|
|
28
28
|
|
|
@@ -55,6 +55,20 @@ module.exports = function(filter, schema, castedDoc, options) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
updatedKeys[path] = true;
|
|
58
|
+
if (path.indexOf('.') === -1) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Also mark all parent prefixes so child-path lookups are O(1).
|
|
62
|
+
// e.g. 'extraProps.location' also marks 'extraProps'
|
|
63
|
+
const pieces = schema.paths[path] ?
|
|
64
|
+
// If the SchemaType already split for us, use that to avoid the extra overhead
|
|
65
|
+
schema.paths[path].splitPath() :
|
|
66
|
+
path.split('.');
|
|
67
|
+
let cur = pieces[0];
|
|
68
|
+
for (let j = 1; j < pieces.length; ++j) {
|
|
69
|
+
updatedKeys[cur] = true;
|
|
70
|
+
cur += '.' + pieces[j];
|
|
71
|
+
}
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
if (options?.overwrite && !hasDollarUpdate) {
|
package/lib/model.js
CHANGED
|
@@ -21,6 +21,7 @@ const ValidationError = require('./error/validation');
|
|
|
21
21
|
const VersionError = require('./error/version');
|
|
22
22
|
const ParallelSaveError = require('./error/parallelSave');
|
|
23
23
|
const applyDefaultsHelper = require('./helpers/document/applyDefaults');
|
|
24
|
+
const isInPathsToSave = require('./helpers/document/isInPathsToSave');
|
|
24
25
|
const applyDefaultsToPOJO = require('./helpers/model/applyDefaultsToPOJO');
|
|
25
26
|
const applyEmbeddedDiscriminators = require('./helpers/discriminator/applyEmbeddedDiscriminators');
|
|
26
27
|
const applyHooks = require('./helpers/model/applyHooks');
|
|
@@ -73,7 +74,11 @@ const ObjectExpectedError = require('./error/objectExpected');
|
|
|
73
74
|
const decorateBulkWriteResult = require('./helpers/model/decorateBulkWriteResult');
|
|
74
75
|
const modelCollectionSymbol = Symbol('mongoose#Model#collection');
|
|
75
76
|
const modelDbSymbol = Symbol('mongoose#Model#db');
|
|
76
|
-
const
|
|
77
|
+
const {
|
|
78
|
+
arrayAtomicsBackupSymbol,
|
|
79
|
+
arrayAtomicsSymbol,
|
|
80
|
+
modelSymbol
|
|
81
|
+
} = require('./helpers/symbols');
|
|
77
82
|
const subclassedSymbol = Symbol('mongoose#Model#subclassed');
|
|
78
83
|
|
|
79
84
|
const { VERSION_INC, VERSION_WHERE, VERSION_ALL } = Document;
|
|
@@ -405,19 +410,14 @@ Model.prototype.$__save = async function $__save(options) {
|
|
|
405
410
|
// Make sure we don't treat it as a new object on error,
|
|
406
411
|
// since it already exists
|
|
407
412
|
this.$__.inserting = false;
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
} else {
|
|
417
|
-
delete delta[1]['$set'][key];
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
413
|
+
const pathsToSave = Array.isArray(options.pathsToSave) ? options.pathsToSave : null;
|
|
414
|
+
const pathsToSaveSet = pathsToSave != null ? new Set(pathsToSave) : null;
|
|
415
|
+
const delta = this.$__delta(pathsToSave, pathsToSaveSet);
|
|
416
|
+
const unsavedDirty = pathsToSave != null ? (delta != null ? delta[2] : this.$__dirty()) : null;
|
|
417
|
+
const unsavedDefaultPaths = pathsToSave != null
|
|
418
|
+
? Object.keys(this.$__.activePaths.getStatePaths('default')).filter(path => !isInPathsToSave(path, pathsToSaveSet, pathsToSave))
|
|
419
|
+
: null;
|
|
420
|
+
|
|
421
421
|
if (delta) {
|
|
422
422
|
where = this.$__where(delta[0]);
|
|
423
423
|
_applyCustomWhere(this, where);
|
|
@@ -448,6 +448,7 @@ Model.prototype.$__save = async function $__save(options) {
|
|
|
448
448
|
// store the modified paths before the document is reset
|
|
449
449
|
this.$__.modifiedPaths = this.modifiedPaths();
|
|
450
450
|
this.$__reset();
|
|
451
|
+
restoreUnsavedState(this, unsavedDirty, unsavedDefaultPaths);
|
|
451
452
|
|
|
452
453
|
_setIsNew(this, false);
|
|
453
454
|
result = await this[modelCollectionSymbol].updateOne(where, update, saveOptions).catch(err => {
|
|
@@ -526,6 +527,33 @@ Model.prototype.$__save = async function $__save(options) {
|
|
|
526
527
|
await this._execDocumentPostHooks('save', options);
|
|
527
528
|
};
|
|
528
529
|
|
|
530
|
+
/*!
|
|
531
|
+
* Restores $__.activePaths state and any atomics for paths that failed
|
|
532
|
+
* to save.
|
|
533
|
+
*
|
|
534
|
+
* @param {Document} doc
|
|
535
|
+
* @param {object[]} unsavedDirty
|
|
536
|
+
* @param {string[]} unsavedDefaultPaths
|
|
537
|
+
*/
|
|
538
|
+
|
|
539
|
+
function restoreUnsavedState(doc, unsavedDirty, unsavedDefaultPaths) {
|
|
540
|
+
if (unsavedDirty == null) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
for (const dirty of unsavedDirty) {
|
|
545
|
+
doc.$__.activePaths.modify(dirty.path);
|
|
546
|
+
if (dirty.value?.[arrayAtomicsBackupSymbol]) {
|
|
547
|
+
dirty.value[arrayAtomicsSymbol] = dirty.value[arrayAtomicsBackupSymbol];
|
|
548
|
+
dirty.value[arrayAtomicsBackupSymbol] = null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
for (const path of unsavedDefaultPaths) {
|
|
553
|
+
doc.$__.activePaths.default(path);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
529
557
|
/*!
|
|
530
558
|
* ignore
|
|
531
559
|
*/
|
package/lib/schemaType.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongoose",
|
|
3
3
|
"description": "Mongoose MongoDB ODM",
|
|
4
|
-
"version": "9.2.
|
|
4
|
+
"version": "9.2.2",
|
|
5
5
|
"author": "Guillermo Rauch <guillermo@learnboost.com>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongodb",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"acquit-ignore": "0.2.2",
|
|
36
36
|
"acquit-require": "0.1.1",
|
|
37
37
|
"ajv": "8.17.1",
|
|
38
|
+
"c8": "10.1.3",
|
|
38
39
|
"cheerio": "^1.2",
|
|
39
40
|
"dox": "1.0.0",
|
|
40
41
|
"eslint": "9.39.2",
|
|
@@ -54,9 +55,9 @@
|
|
|
54
55
|
"mongodb-memory-server": "11.0.1",
|
|
55
56
|
"mongodb-runner": "^6.0.0",
|
|
56
57
|
"ncp": "^2.0.0",
|
|
57
|
-
"c8": "10.1.3",
|
|
58
58
|
"pug": "3.0.3",
|
|
59
59
|
"sinon": "21.0.1",
|
|
60
|
+
"tstyche": "^6.2.0",
|
|
60
61
|
"typescript": "5.9.3",
|
|
61
62
|
"typescript-eslint": "^8.31.1",
|
|
62
63
|
"uuid": "11.1.0"
|
|
@@ -98,7 +99,7 @@
|
|
|
98
99
|
"test-deno:ci": "npm run test-deno -- --reporter min",
|
|
99
100
|
"test-rs": "START_REPLICA_SET=1 mocha --timeout 30000 --exit ./test/*.test.js",
|
|
100
101
|
"test-rs:ci": "npm run test-rs -- --reporter min",
|
|
101
|
-
"test:types": "
|
|
102
|
+
"test:types": "tstyche",
|
|
102
103
|
"setup-test-encryption": "node scripts/setup-encryption-tests.js",
|
|
103
104
|
"test-encryption": "mocha --exit ./test/encryption/*.test.js",
|
|
104
105
|
"test-encryption:ci": "npm run test-encryption -- --reporter min",
|