mongoose 9.9.2 → 9.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/cast/double.js +6 -0
- package/lib/cast/int32.js +6 -0
- package/lib/document.js +65 -31
- package/lib/internal.js +2 -0
- package/lib/model.js +10 -5
- package/lib/query.js +49 -50
- package/lib/schema/documentArrayElement.js +19 -7
- package/lib/schema.js +28 -1
- package/lib/schemaType.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -340,7 +340,7 @@ new Schema({
|
|
|
340
340
|
|
|
341
341
|
### Driver Access
|
|
342
342
|
|
|
343
|
-
Mongoose is built on top of the [official MongoDB Node.js driver](https://github.com/mongodb/node-mongodb-native). Each mongoose model keeps a reference to a [native MongoDB driver collection](
|
|
343
|
+
Mongoose is built on top of the [official MongoDB Node.js driver](https://github.com/mongodb/node-mongodb-native). Each mongoose model keeps a reference to a [native MongoDB driver collection](https://mongodb.github.io/node-mongodb-native/7.5/classes/Collection.html). The collection object can be accessed using `YourModel.collection`. However, using the collection object directly bypasses all mongoose features, including hooks, validation, etc. The one
|
|
344
344
|
notable exception is that `YourModel.collection` still buffers
|
|
345
345
|
commands. As such, `YourModel.collection.find()` will **not**
|
|
346
346
|
return a cursor.
|
package/lib/cast/double.js
CHANGED
|
@@ -30,6 +30,12 @@ module.exports = function castDouble(val) {
|
|
|
30
30
|
assert.ok(false);
|
|
31
31
|
}
|
|
32
32
|
} else if (typeof val === 'object') {
|
|
33
|
+
if (Array.isArray(val)) {
|
|
34
|
+
// `[5].valueOf()` returns the array itself, so without this guard a
|
|
35
|
+
// single-element or empty array would fall through to `Number(tempVal)`
|
|
36
|
+
// below and silently coerce instead of throwing.
|
|
37
|
+
assert.ok(false);
|
|
38
|
+
}
|
|
33
39
|
const tempVal = val.valueOf() ?? val.toString();
|
|
34
40
|
// ex: { a: 'im an object, valueOf: () => 'helloworld' } // throw an error
|
|
35
41
|
if (typeof tempVal === 'string') {
|
package/lib/cast/int32.js
CHANGED
|
@@ -20,6 +20,12 @@ module.exports = function castInt32(val) {
|
|
|
20
20
|
if (val === '') {
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
|
+
if (Array.isArray(val)) {
|
|
24
|
+
// `Number([5])` is `5` and `Number([])` is `0`, so without this guard a
|
|
25
|
+
// single-element or empty array would silently coerce instead of throwing,
|
|
26
|
+
// unlike every other array input and unlike `castNumber()`'s own guard.
|
|
27
|
+
assert.ok(false);
|
|
28
|
+
}
|
|
23
29
|
|
|
24
30
|
const coercedVal = isBsonType(val, 'Long') ? val.toNumber() : Number(val);
|
|
25
31
|
|
package/lib/document.js
CHANGED
|
@@ -2570,25 +2570,15 @@ Document.prototype.isSelected = function isSelected(path) {
|
|
|
2570
2570
|
return path.some(p => this.$__isSelected(p));
|
|
2571
2571
|
}
|
|
2572
2572
|
|
|
2573
|
-
const
|
|
2574
|
-
|
|
2573
|
+
const index = _getProjectionIndex(this);
|
|
2574
|
+
const paths = index.paths;
|
|
2575
|
+
const inclusive = index.inclusive;
|
|
2575
2576
|
|
|
2576
|
-
if (
|
|
2577
|
+
if (index.onlyId) {
|
|
2577
2578
|
// only _id was selected.
|
|
2578
2579
|
return this.$__.selected._id === 0;
|
|
2579
2580
|
}
|
|
2580
2581
|
|
|
2581
|
-
for (const cur of paths) {
|
|
2582
|
-
if (cur === '_id') {
|
|
2583
|
-
continue;
|
|
2584
|
-
}
|
|
2585
|
-
if (!isDefiningProjection(this.$__.selected[cur])) {
|
|
2586
|
-
continue;
|
|
2587
|
-
}
|
|
2588
|
-
inclusive = !!this.$__.selected[cur];
|
|
2589
|
-
break;
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
2582
|
if (inclusive === null) {
|
|
2593
2583
|
return true;
|
|
2594
2584
|
}
|
|
@@ -2599,6 +2589,25 @@ Document.prototype.isSelected = function isSelected(path) {
|
|
|
2599
2589
|
|
|
2600
2590
|
const pathHasDot = path.indexOf('.') !== -1;
|
|
2601
2591
|
|
|
2592
|
+
if (!index.hasNestedKey) {
|
|
2593
|
+
// No projection key is nested, so no key can start with `path + '.'` and
|
|
2594
|
+
// only the "is an ancestor of `path` projected?" check below can match.
|
|
2595
|
+
// Every ancestor that matches returns the same value, so we can walk
|
|
2596
|
+
// `path`'s ancestors instead of scanning every projection key. This keeps
|
|
2597
|
+
// `isSelected()` O(depth of path) rather than O(number of projected paths),
|
|
2598
|
+
// which matters because `toObject({ getters: true })` calls this once per
|
|
2599
|
+
// schema path. Re: gh-16373
|
|
2600
|
+
if (pathHasDot) {
|
|
2601
|
+
for (let dot = path.indexOf('.'); dot !== -1; dot = path.indexOf('.', dot + 1)) {
|
|
2602
|
+
const ancestor = path.slice(0, dot);
|
|
2603
|
+
if (ancestor !== '_id' && Object.hasOwn(this.$__.selected, ancestor)) {
|
|
2604
|
+
return inclusive;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
return !inclusive;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2602
2611
|
for (const cur of paths) {
|
|
2603
2612
|
if (cur === '_id') {
|
|
2604
2613
|
continue;
|
|
@@ -2615,6 +2624,43 @@ Document.prototype.isSelected = function isSelected(path) {
|
|
|
2615
2624
|
return !inclusive;
|
|
2616
2625
|
};
|
|
2617
2626
|
|
|
2627
|
+
/*!
|
|
2628
|
+
* Computes and caches the derived properties of `doc.$__.selected` that
|
|
2629
|
+
* `isSelected()` and `isDirectSelected()` would otherwise recompute on every
|
|
2630
|
+
* call. `$__.selected` is assigned once in the Document constructor and never
|
|
2631
|
+
* mutated afterwards, so this can safely be cached for the document's lifetime.
|
|
2632
|
+
*/
|
|
2633
|
+
|
|
2634
|
+
function _getProjectionIndex(doc) {
|
|
2635
|
+
if (doc.$__.selectedIndex !== undefined) {
|
|
2636
|
+
return doc.$__.selectedIndex;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
const selected = doc.$__.selected;
|
|
2640
|
+
const paths = Object.keys(selected);
|
|
2641
|
+
let inclusive = null;
|
|
2642
|
+
let hasNestedKey = false;
|
|
2643
|
+
|
|
2644
|
+
for (const cur of paths) {
|
|
2645
|
+
if (cur === '_id') {
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
if (cur.indexOf('.') !== -1) {
|
|
2649
|
+
hasNestedKey = true;
|
|
2650
|
+
}
|
|
2651
|
+
if (inclusive === null && isDefiningProjection(selected[cur])) {
|
|
2652
|
+
inclusive = !!selected[cur];
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
return (doc.$__.selectedIndex = {
|
|
2657
|
+
paths,
|
|
2658
|
+
inclusive,
|
|
2659
|
+
hasNestedKey,
|
|
2660
|
+
onlyId: paths.length === 1 && paths[0] === '_id'
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2618
2664
|
Document.prototype.$__isSelected = Document.prototype.isSelected;
|
|
2619
2665
|
|
|
2620
2666
|
/**
|
|
@@ -2650,34 +2696,22 @@ Document.prototype.isDirectSelected = function isDirectSelected(path) {
|
|
|
2650
2696
|
return path.some(p => this.isDirectSelected(p));
|
|
2651
2697
|
}
|
|
2652
2698
|
|
|
2653
|
-
const
|
|
2654
|
-
let inclusive = null;
|
|
2699
|
+
const index = _getProjectionIndex(this);
|
|
2655
2700
|
|
|
2656
|
-
if (
|
|
2701
|
+
if (index.onlyId) {
|
|
2657
2702
|
// only _id was selected.
|
|
2658
2703
|
return this.$__.selected._id === 0;
|
|
2659
2704
|
}
|
|
2660
2705
|
|
|
2661
|
-
|
|
2662
|
-
if (cur === '_id') {
|
|
2663
|
-
continue;
|
|
2664
|
-
}
|
|
2665
|
-
if (!isDefiningProjection(this.$__.selected[cur])) {
|
|
2666
|
-
continue;
|
|
2667
|
-
}
|
|
2668
|
-
inclusive = !!this.$__.selected[cur];
|
|
2669
|
-
break;
|
|
2670
|
-
}
|
|
2671
|
-
|
|
2672
|
-
if (inclusive === null) {
|
|
2706
|
+
if (index.inclusive === null) {
|
|
2673
2707
|
return true;
|
|
2674
2708
|
}
|
|
2675
2709
|
|
|
2676
2710
|
if (Object.hasOwn(this.$__.selected, path)) {
|
|
2677
|
-
return inclusive;
|
|
2711
|
+
return index.inclusive;
|
|
2678
2712
|
}
|
|
2679
2713
|
|
|
2680
|
-
return !inclusive;
|
|
2714
|
+
return !index.inclusive;
|
|
2681
2715
|
};
|
|
2682
2716
|
|
|
2683
2717
|
/**
|
package/lib/internal.js
CHANGED
|
@@ -17,6 +17,8 @@ InternalCache.prototype.strictMode = true;
|
|
|
17
17
|
|
|
18
18
|
InternalCache.prototype.fullPath = undefined;
|
|
19
19
|
InternalCache.prototype.selected = undefined;
|
|
20
|
+
// Lazily computed index over `selected`, see `_getProjectionIndex()` in document.js
|
|
21
|
+
InternalCache.prototype.selectedIndex = undefined;
|
|
20
22
|
InternalCache.prototype.shardval = undefined;
|
|
21
23
|
InternalCache.prototype.saveError = undefined;
|
|
22
24
|
InternalCache.prototype.validationError = undefined;
|
package/lib/model.js
CHANGED
|
@@ -1727,6 +1727,7 @@ function _ensureIndexes(model, options, callback) {
|
|
|
1727
1727
|
utils.warn('mongoose: Duplicate schema index on ' + JSON.stringify(fields) +
|
|
1728
1728
|
' for model "' + model.modelName + '". ' +
|
|
1729
1729
|
'This is often due to declaring an index using both "index: true" and "schema.index()". ' +
|
|
1730
|
+
'MongoDB will not create the duplicate index and options on the duplicate definition (such as expireAfterSeconds or unique) will not be applied. ' +
|
|
1730
1731
|
'Please remove the duplicate index definition.');
|
|
1731
1732
|
break;
|
|
1732
1733
|
}
|
|
@@ -3654,15 +3655,19 @@ Model.bulkSave = async function bulkSave(documents, options) {
|
|
|
3654
3655
|
);
|
|
3655
3656
|
}
|
|
3656
3657
|
|
|
3658
|
+
const failedDocumentIds = documents.length >= 25 ? new Set(bulkWriteError?.writeErrors?.map(writeError => {
|
|
3659
|
+
const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
|
|
3660
|
+
return writeErrorDocumentId.toString();
|
|
3661
|
+
})) : null;
|
|
3657
3662
|
const successfulDocuments = [];
|
|
3658
3663
|
for (let i = 0; i < documents.length; i++) {
|
|
3659
3664
|
const document = documents[i];
|
|
3660
|
-
const
|
|
3665
|
+
const documentId = document._doc._id.toString();
|
|
3666
|
+
const failed = failedDocumentIds == null ? bulkWriteError?.writeErrors.find(writeError => {
|
|
3661
3667
|
const writeErrorDocumentId = writeError.err.op._id || writeError.err.op.q._id;
|
|
3662
|
-
return writeErrorDocumentId.toString() ===
|
|
3663
|
-
});
|
|
3664
|
-
|
|
3665
|
-
if (documentError == null) {
|
|
3668
|
+
return writeErrorDocumentId.toString() === documentId;
|
|
3669
|
+
}) != null : failedDocumentIds.has(documentId);
|
|
3670
|
+
if (!failed) {
|
|
3666
3671
|
successfulDocuments.push(document);
|
|
3667
3672
|
}
|
|
3668
3673
|
}
|
package/lib/query.js
CHANGED
|
@@ -1754,9 +1754,16 @@ Query.prototype.setOptions = function(options, overwrite) {
|
|
|
1754
1754
|
}
|
|
1755
1755
|
}
|
|
1756
1756
|
|
|
1757
|
-
if ('
|
|
1758
|
-
this._mongooseOptions.
|
|
1759
|
-
delete options.
|
|
1757
|
+
if ('cloneUpdate' in options) {
|
|
1758
|
+
this._mongooseOptions.cloneUpdate = options.cloneUpdate;
|
|
1759
|
+
delete options.cloneUpdate;
|
|
1760
|
+
}
|
|
1761
|
+
if ('defaults' in options) {
|
|
1762
|
+
this._mongooseOptions.defaults = options.defaults;
|
|
1763
|
+
// deleting options.defaults will cause 7287 to fail
|
|
1764
|
+
}
|
|
1765
|
+
if (options.lean == null && this.schema && 'lean' in this.schema.options) {
|
|
1766
|
+
this._mongooseOptions.lean = this.schema.options.lean;
|
|
1760
1767
|
}
|
|
1761
1768
|
if ('overwriteDiscriminatorKey' in options) {
|
|
1762
1769
|
this._mongooseOptions.overwriteDiscriminatorKey = options.overwriteDiscriminatorKey;
|
|
@@ -1766,13 +1773,9 @@ Query.prototype.setOptions = function(options, overwrite) {
|
|
|
1766
1773
|
this._mongooseOptions.overwriteImmutable = options.overwriteImmutable;
|
|
1767
1774
|
delete options.overwriteImmutable;
|
|
1768
1775
|
}
|
|
1769
|
-
if ('
|
|
1770
|
-
this._mongooseOptions.
|
|
1771
|
-
delete options.
|
|
1772
|
-
}
|
|
1773
|
-
if ('cloneUpdate' in options) {
|
|
1774
|
-
this._mongooseOptions.cloneUpdate = options.cloneUpdate;
|
|
1775
|
-
delete options.cloneUpdate;
|
|
1776
|
+
if ('sanitizeFilter' in options) {
|
|
1777
|
+
this._mongooseOptions.sanitizeFilter = options.sanitizeFilter;
|
|
1778
|
+
delete options.sanitizeFilter;
|
|
1776
1779
|
}
|
|
1777
1780
|
if ('sanitizeProjection' in options) {
|
|
1778
1781
|
if (options.sanitizeProjection && !this._mongooseOptions.sanitizeProjection) {
|
|
@@ -1782,29 +1785,33 @@ Query.prototype.setOptions = function(options, overwrite) {
|
|
|
1782
1785
|
this._mongooseOptions.sanitizeProjection = options.sanitizeProjection;
|
|
1783
1786
|
delete options.sanitizeProjection;
|
|
1784
1787
|
}
|
|
1785
|
-
if ('
|
|
1786
|
-
this._mongooseOptions.
|
|
1787
|
-
delete options.
|
|
1788
|
+
if ('schemaLevelProjections' in options) {
|
|
1789
|
+
this._mongooseOptions.schemaLevelProjections = options.schemaLevelProjections;
|
|
1790
|
+
delete options.schemaLevelProjections;
|
|
1791
|
+
}
|
|
1792
|
+
if ('setDefaultsOnInsert' in options) {
|
|
1793
|
+
this._mongooseOptions.setDefaultsOnInsert = options.setDefaultsOnInsert;
|
|
1794
|
+
delete options.setDefaultsOnInsert;
|
|
1795
|
+
}
|
|
1796
|
+
if ('strict' in options) {
|
|
1797
|
+
this._mongooseOptions.strict = options.strict;
|
|
1798
|
+
delete options.strict;
|
|
1799
|
+
}
|
|
1800
|
+
if ('strictQuery' in options) {
|
|
1801
|
+
this._mongooseOptions.strictQuery = options.strictQuery;
|
|
1802
|
+
delete options.strictQuery;
|
|
1788
1803
|
}
|
|
1789
1804
|
if ('timestamps' in options) {
|
|
1790
1805
|
this._mongooseOptions.timestamps = options.timestamps;
|
|
1791
1806
|
delete options.timestamps;
|
|
1792
1807
|
}
|
|
1793
|
-
if ('defaults' in options) {
|
|
1794
|
-
this._mongooseOptions.defaults = options.defaults;
|
|
1795
|
-
// deleting options.defaults will cause 7287 to fail
|
|
1796
|
-
}
|
|
1797
1808
|
if ('translateAliases' in options) {
|
|
1798
1809
|
this._mongooseOptions.translateAliases = options.translateAliases;
|
|
1799
1810
|
delete options.translateAliases;
|
|
1800
1811
|
}
|
|
1801
|
-
if ('
|
|
1802
|
-
this._mongooseOptions.
|
|
1803
|
-
delete options.
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
if (options.lean == null && this.schema && 'lean' in this.schema.options) {
|
|
1807
|
-
this._mongooseOptions.lean = this.schema.options.lean;
|
|
1812
|
+
if ('updatePipeline' in options) {
|
|
1813
|
+
this._mongooseOptions.updatePipeline = options.updatePipeline;
|
|
1814
|
+
delete options.updatePipeline;
|
|
1808
1815
|
}
|
|
1809
1816
|
|
|
1810
1817
|
if (typeof options.limit === 'string') {
|
|
@@ -2361,13 +2368,22 @@ Query.prototype._unsetCastError = function _unsetCastError() {
|
|
|
2361
2368
|
* Getter/setter around the current mongoose-specific options for this query
|
|
2362
2369
|
* Below are the current Mongoose-specific options.
|
|
2363
2370
|
*
|
|
2364
|
-
* - `
|
|
2371
|
+
* - `cloneUpdate`: if `false`, Mongoose will not clone updates before executing the query
|
|
2372
|
+
* - `defaults`: if `false`, Mongoose will not apply defaults to the returned document(s)
|
|
2365
2373
|
* - `lean`: if truthy, Mongoose will not [hydrate](https://mongoosejs.com/docs/api/model.html#Model.hydrate()) any documents that are returned from this query. See [`Query.prototype.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) for more information.
|
|
2366
|
-
* - `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.
|
|
2367
|
-
* - `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.
|
|
2368
2374
|
* - `nearSphere`: use `$nearSphere` instead of `near()`. See the [`Query.prototype.nearSphere()` docs](https://mongoosejs.com/docs/api/query.html#Query.prototype.nearSphere())
|
|
2375
|
+
* - `overwriteDiscriminatorKey`: allow setting the discriminator key in the update
|
|
2376
|
+
* - `overwriteImmutable`: allow overwriting properties that are set to `immutable` in the schema
|
|
2377
|
+
* - `populate`: an array representing what paths will be populated. Should have one entry for each call to [`Query.prototype.populate()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.populate())
|
|
2378
|
+
* - `sanitizeFilter`: if truthy, Mongoose will sanitize query filters before executing the query
|
|
2379
|
+
* - `sanitizeProjection`: if truthy, Mongoose will sanitize projections before executing the query
|
|
2369
2380
|
* - `schemaLevelProjections`: if `false`, Mongoose will not apply schema-level `select: false` or `select: true` for this query
|
|
2370
|
-
* - `
|
|
2381
|
+
* - `setDefaultsOnInsert`: if `true`, Mongoose will apply defaults to upserted documents
|
|
2382
|
+
* - `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.
|
|
2383
|
+
* - `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.
|
|
2384
|
+
* - `timestamps`: if `false`, Mongoose will not apply timestamps to the update
|
|
2385
|
+
* - `translateAliases`: if `true`, Mongoose will translate schema aliases in the query
|
|
2386
|
+
* - `updatePipeline`: if `true`, Mongoose will treat the update as an update pipeline
|
|
2371
2387
|
*
|
|
2372
2388
|
* Mongoose maintains a separate object for internal options because
|
|
2373
2389
|
* Mongoose sends `Query.prototype.options` to the MongoDB server, and the
|
|
@@ -3565,9 +3581,6 @@ Query.prototype._findOneAndUpdate = async function _findOneAndUpdate() {
|
|
|
3565
3581
|
applyGlobalMaxTimeMS(this.options, dbOptions, baseOptions);
|
|
3566
3582
|
applyGlobalDiskUse(this.options, dbOptions, baseOptions);
|
|
3567
3583
|
|
|
3568
|
-
if ('strict' in this.options) {
|
|
3569
|
-
this._mongooseOptions.strict = this.options.strict;
|
|
3570
|
-
}
|
|
3571
3584
|
const options = this._optionsForExec(this.model);
|
|
3572
3585
|
convertNewToReturnDocument(options);
|
|
3573
3586
|
|
|
@@ -3828,11 +3841,6 @@ Query.prototype._findOneAndReplace = async function _findOneAndReplace() {
|
|
|
3828
3841
|
throw this.error();
|
|
3829
3842
|
}
|
|
3830
3843
|
|
|
3831
|
-
if ('strict' in this.options) {
|
|
3832
|
-
this._mongooseOptions.strict = this.options.strict;
|
|
3833
|
-
delete this.options.strict;
|
|
3834
|
-
}
|
|
3835
|
-
|
|
3836
3844
|
const filter = this._conditions;
|
|
3837
3845
|
const options = this._optionsForExec();
|
|
3838
3846
|
convertNewToReturnDocument(options);
|
|
@@ -4548,13 +4556,6 @@ function _update(query, op, filter, doc, options, callback) {
|
|
|
4548
4556
|
query.op = op;
|
|
4549
4557
|
doc = doc || {};
|
|
4550
4558
|
|
|
4551
|
-
// strict is an option used in the update checking, make sure it gets set
|
|
4552
|
-
if (options != null) {
|
|
4553
|
-
if ('strict' in options) {
|
|
4554
|
-
query._mongooseOptions.strict = options.strict;
|
|
4555
|
-
}
|
|
4556
|
-
}
|
|
4557
|
-
|
|
4558
4559
|
if (!(filter instanceof Query) &&
|
|
4559
4560
|
filter != null &&
|
|
4560
4561
|
filter.toString() !== '[object Object]') {
|
|
@@ -5212,13 +5213,11 @@ Query.prototype.cast = function(model, obj) {
|
|
|
5212
5213
|
}
|
|
5213
5214
|
|
|
5214
5215
|
const opts = { upsert: this.options?.upsert };
|
|
5215
|
-
if (this.
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
opts.strictQuery = this.options.strictQuery;
|
|
5221
|
-
}
|
|
5216
|
+
if ('strict' in this._mongooseOptions) {
|
|
5217
|
+
opts.strict = this._mongooseOptions.strict;
|
|
5218
|
+
}
|
|
5219
|
+
if ('strictQuery' in this._mongooseOptions) {
|
|
5220
|
+
opts.strictQuery = this._mongooseOptions.strictQuery;
|
|
5222
5221
|
}
|
|
5223
5222
|
if ('sanitizeFilter' in this._mongooseOptions) {
|
|
5224
5223
|
opts.sanitizeFilter = this._mongooseOptions.sanitizeFilter;
|
|
@@ -119,14 +119,26 @@ SchemaDocumentArrayElement.prototype.doValidate = async function doValidate(valu
|
|
|
119
119
|
*/
|
|
120
120
|
|
|
121
121
|
SchemaDocumentArrayElement.prototype.clone = function() {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
// This schematype takes the subdocument schema where `SchemaType` takes the
|
|
123
|
+
// options, so it cannot go through `SchemaType.prototype.clone()`: the
|
|
124
|
+
// arguments would land in the wrong parameters and `$parentSchemaType` would
|
|
125
|
+
// never reach the constructor.
|
|
126
|
+
const options = Object.assign({}, this.options, {
|
|
127
|
+
$parentSchemaType: this.$parentSchemaType,
|
|
128
|
+
Constructor: this.Constructor
|
|
129
|
+
});
|
|
130
|
+
const schematype = new this.constructor(
|
|
131
|
+
this.path,
|
|
132
|
+
this.schema,
|
|
133
|
+
options,
|
|
134
|
+
this.parentSchema
|
|
135
|
+
);
|
|
136
|
+
schematype.validators = this.validators.slice();
|
|
137
|
+
if (this.requiredValidator !== undefined) {
|
|
138
|
+
schematype.requiredValidator = this.requiredValidator;
|
|
139
|
+
}
|
|
128
140
|
|
|
129
|
-
return
|
|
141
|
+
return schematype;
|
|
130
142
|
};
|
|
131
143
|
|
|
132
144
|
/*!
|
package/lib/schema.js
CHANGED
|
@@ -451,6 +451,16 @@ Schema.prototype._clone = function _clone(Constructor) {
|
|
|
451
451
|
);
|
|
452
452
|
s.nested = clone(this.nested);
|
|
453
453
|
s.subpaths = clone(this.subpaths);
|
|
454
|
+
|
|
455
|
+
s.mapPaths = [];
|
|
456
|
+
for (const [path, schemaType] of Object.entries(s.paths)) {
|
|
457
|
+
const mapPath = path + '.$*';
|
|
458
|
+
if (!schemaType.$isSchemaMap || !Object.hasOwn(s.paths, mapPath)) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
s.paths[mapPath] = schemaType.$__schemaType;
|
|
462
|
+
s.mapPaths.push(s.paths[mapPath]);
|
|
463
|
+
}
|
|
454
464
|
for (const schemaType of Object.values(s.paths)) {
|
|
455
465
|
if (schemaType.$isSingleNested) {
|
|
456
466
|
const path = schemaType.path;
|
|
@@ -478,7 +488,6 @@ Schema.prototype._clone = function _clone(Constructor) {
|
|
|
478
488
|
s.$implicitlyCreated = this.$implicitlyCreated;
|
|
479
489
|
s.$id = ++id;
|
|
480
490
|
s.$originalSchemaId = this.$id;
|
|
481
|
-
s.mapPaths = [].concat(this.mapPaths);
|
|
482
491
|
|
|
483
492
|
if (this.discriminatorMapping != null) {
|
|
484
493
|
s.discriminatorMapping = Object.assign({}, this.discriminatorMapping);
|
|
@@ -2764,12 +2773,27 @@ Schema.prototype.remove = function(path) {
|
|
|
2764
2773
|
}
|
|
2765
2774
|
}
|
|
2766
2775
|
|
|
2776
|
+
this.mapPaths = this.mapPaths.filter(
|
|
2777
|
+
schemaType => !schemaType.path.startsWith(name + '.')
|
|
2778
|
+
);
|
|
2779
|
+
|
|
2767
2780
|
delete this.nested[name];
|
|
2768
2781
|
_deletePath(this, name);
|
|
2769
2782
|
return;
|
|
2770
2783
|
}
|
|
2771
2784
|
|
|
2772
2785
|
delete this.paths[name];
|
|
2786
|
+
// A map registers its values under `<path>.$*`, both in `paths` and in
|
|
2787
|
+
// `mapPaths`. Left behind, they keep answering `pathType()` and casting queries
|
|
2788
|
+
// for a path the schema no longer has.
|
|
2789
|
+
for (const subpath of Object.keys(this.paths)) {
|
|
2790
|
+
if (subpath.startsWith(name + '.')) {
|
|
2791
|
+
delete this.paths[subpath];
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
this.mapPaths = this.mapPaths.filter(
|
|
2795
|
+
schemaType => !schemaType.path.startsWith(name + '.')
|
|
2796
|
+
);
|
|
2773
2797
|
_deletePath(this, name);
|
|
2774
2798
|
|
|
2775
2799
|
this._removeEncryptedField(name);
|
|
@@ -2790,6 +2814,9 @@ function _deletePath(schema, name) {
|
|
|
2790
2814
|
|
|
2791
2815
|
for (const piece of pieces) {
|
|
2792
2816
|
branch = branch[piece];
|
|
2817
|
+
if (branch == null) {
|
|
2818
|
+
return;
|
|
2819
|
+
}
|
|
2793
2820
|
}
|
|
2794
2821
|
|
|
2795
2822
|
delete branch[last];
|
package/lib/schemaType.js
CHANGED
|
@@ -230,8 +230,12 @@ SchemaType.prototype._addJSONSchemaEnum = function _addJSONSchemaEnum(definition
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
// The enum validator allows nullish values, so allow `null` wherever the type does.
|
|
233
|
+
// Skip that when the user already listed `null`: MongoDB refuses a `$jsonSchema`
|
|
234
|
+
// whose `enum` repeats a value, and so does Ajv.
|
|
233
235
|
const allowsNull = Array.isArray(definition.type ?? definition.bsonType);
|
|
234
|
-
definition.enum = allowsNull
|
|
236
|
+
definition.enum = allowsNull && !this.enumValues.includes(null)
|
|
237
|
+
? [...this.enumValues, null]
|
|
238
|
+
: [...this.enumValues];
|
|
235
239
|
|
|
236
240
|
return definition;
|
|
237
241
|
};
|
|
@@ -1722,7 +1726,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init, options) {
|
|
|
1722
1726
|
!doc.$__.populated[path].options ||
|
|
1723
1727
|
!doc.$__.populated[path].options.options ||
|
|
1724
1728
|
!doc.$__.populated[path].options.options.lean) {
|
|
1725
|
-
const PopulatedModel = pop ? pop.options[populateModelSymbol] :
|
|
1729
|
+
const PopulatedModel = pop ? pop.options[populateModelSymbol] : owner.constructor.db.model(this.options.ref);
|
|
1726
1730
|
ret = PopulatedModel.hydrate(value, null, options);
|
|
1727
1731
|
ret.$__.wasPopulated = { value: ret._doc._id, options: { [populateModelSymbol]: PopulatedModel } };
|
|
1728
1732
|
}
|