mongoose 9.9.1 → 9.9.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/connection.js +3 -0
- package/lib/document.js +26 -55
- package/lib/helpers/pathTrie.js +167 -0
- package/lib/model.js +9 -0
- package/lib/plugins/trackTransaction.js +25 -10
- package/lib/query.js +8 -7
- package/lib/schema/number.js +1 -1
- package/lib/schema/string.js +1 -1
- package/lib/schema.js +7 -14
- package/lib/schemaType.js +21 -0
- package/lib/utils.js +5 -15
- package/package.json +11 -11
- package/types/document.d.ts +4 -4
- package/types/index.d.ts +25 -0
- package/types/schematypes.d.ts +1 -1
package/lib/connection.js
CHANGED
|
@@ -778,6 +778,9 @@ function _resetSessionDocuments(session) {
|
|
|
778
778
|
if (Object.hasOwn(state, 'versionKey')) {
|
|
779
779
|
doc.set(doc.schema.options.versionKey, state.versionKey);
|
|
780
780
|
}
|
|
781
|
+
if (Object.hasOwn(state, 'isDeleted')) {
|
|
782
|
+
doc.$isDeleted(state.isDeleted);
|
|
783
|
+
}
|
|
781
784
|
|
|
782
785
|
for (const path of state.modifiedPaths) {
|
|
783
786
|
doc.$__.activePaths.modify(path);
|
package/lib/document.js
CHANGED
|
@@ -14,6 +14,7 @@ const ModifiedPathsSnapshot = require('./modifiedPathsSnapshot');
|
|
|
14
14
|
const ObjectExpectedError = require('./error/objectExpected');
|
|
15
15
|
const ObjectParameterError = require('./error/objectParameter');
|
|
16
16
|
const ParallelValidateError = require('./error/parallelValidate');
|
|
17
|
+
const PathTrie = require('./helpers/pathTrie');
|
|
17
18
|
const Schema = require('./schema');
|
|
18
19
|
const StrictModeError = require('./error/strict');
|
|
19
20
|
const ValidationError = require('./error/validation');
|
|
@@ -31,7 +32,6 @@ const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDisc
|
|
|
31
32
|
const getKeysInSchemaOrder = require('./helpers/schema/getKeysInSchemaOrder');
|
|
32
33
|
const getSubdocumentStrictValue = require('./helpers/schema/getSubdocumentStrictValue');
|
|
33
34
|
const handleSpreadDoc = require('./helpers/document/handleSpreadDoc');
|
|
34
|
-
const isBsonType = require('./helpers/isBsonType');
|
|
35
35
|
const isDefiningProjection = require('./helpers/projection/isDefiningProjection');
|
|
36
36
|
const isExclusive = require('./helpers/projection/isExclusive');
|
|
37
37
|
const isPathExcluded = require('./helpers/projection/isPathExcluded');
|
|
@@ -5242,26 +5242,26 @@ Document.prototype.$__delta = function $__delta(pathsToSave, pathsToSaveSet) {
|
|
|
5242
5242
|
const optimisticConcurrency = this.$__schema.options.optimisticConcurrency;
|
|
5243
5243
|
if (optimisticConcurrency) {
|
|
5244
5244
|
if (Array.isArray(optimisticConcurrency)) {
|
|
5245
|
-
if (!this.$__schema.options.
|
|
5246
|
-
this.$__schema.options.
|
|
5245
|
+
if (!this.$__schema.options._optimisticConcurrencyTrie) {
|
|
5246
|
+
this.$__schema.options._optimisticConcurrencyTrie = new PathTrie(optimisticConcurrency);
|
|
5247
5247
|
}
|
|
5248
|
-
const
|
|
5248
|
+
const optimisticConcurrencyTrie = this.$__schema.options._optimisticConcurrencyTrie;
|
|
5249
5249
|
const modPaths = this.directModifiedPaths();
|
|
5250
5250
|
const hasRelevantModPaths = pathsToSave == null ?
|
|
5251
|
-
modPaths.find(path =>
|
|
5252
|
-
modPaths.find(path =>
|
|
5251
|
+
modPaths.find(path => optimisticConcurrencyTrie.overlapsPath(path.indexOf('.') === -1 ? path : path.split('.'))) :
|
|
5252
|
+
modPaths.find(path => optimisticConcurrencyTrie.overlapsPath(path.indexOf('.') === -1 ? path : path.split('.')) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5253
5253
|
if (hasRelevantModPaths) {
|
|
5254
5254
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5255
5255
|
}
|
|
5256
5256
|
} else if (Array.isArray(optimisticConcurrency?.exclude)) {
|
|
5257
|
-
if (!this.$__schema.options.
|
|
5258
|
-
this.$__schema.options.
|
|
5257
|
+
if (!this.$__schema.options._optimisticConcurrencyExcludeTrie) {
|
|
5258
|
+
this.$__schema.options._optimisticConcurrencyExcludeTrie = new PathTrie(optimisticConcurrency.exclude);
|
|
5259
5259
|
}
|
|
5260
|
-
const
|
|
5260
|
+
const optimisticConcurrencyExcludeTrie = this.$__schema.options._optimisticConcurrencyExcludeTrie;
|
|
5261
5261
|
const modPaths = this.directModifiedPaths();
|
|
5262
5262
|
const hasRelevantModPaths = pathsToSave == null ?
|
|
5263
|
-
modPaths.find(path => !
|
|
5264
|
-
modPaths.find(path => !
|
|
5263
|
+
modPaths.find(path => !optimisticConcurrencyExcludeTrie.matchesPathOrAncestor(path.indexOf('.') === -1 ? path : path.split('.'))) :
|
|
5264
|
+
modPaths.find(path => !optimisticConcurrencyExcludeTrie.matchesPathOrAncestor(path.indexOf('.') === -1 ? path : path.split('.')) && isInPathsToSave(path, pathsToSaveSet, pathsToSave));
|
|
5265
5265
|
if (hasRelevantModPaths) {
|
|
5266
5266
|
this.$__.version = dirty.length ? VERSION_ALL : VERSION_WHERE;
|
|
5267
5267
|
}
|
|
@@ -5755,11 +5755,21 @@ Document.prototype.$__hasOnlyPrimitiveValues = function $__hasOnlyPrimitiveValue
|
|
|
5755
5755
|
const doc = this._doc;
|
|
5756
5756
|
for (const key in doc) {
|
|
5757
5757
|
const v = doc[key];
|
|
5758
|
-
if (v == null
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5758
|
+
if (v == null || typeof v !== 'object') {
|
|
5759
|
+
continue;
|
|
5760
|
+
}
|
|
5761
|
+
// Equivalent to `isNativeObject(v) && !Array.isArray(v)`, plus the
|
|
5762
|
+
// `isBsonType()` checks below, but without the helper calls or the
|
|
5763
|
+
// repeated `Array.isArray()`. This runs for every key of every document
|
|
5764
|
+
// that `toObject()` touches, so the call overhead is measurable.
|
|
5765
|
+
if (Array.isArray(v)) {
|
|
5766
|
+
return false;
|
|
5767
|
+
}
|
|
5768
|
+
if (v instanceof Date || v instanceof Boolean || v instanceof Number || v instanceof String) {
|
|
5769
|
+
continue;
|
|
5770
|
+
}
|
|
5771
|
+
const bsontype = v._bsontype;
|
|
5772
|
+
if (bsontype === 'ObjectId' || bsontype === 'Decimal128') {
|
|
5763
5773
|
continue;
|
|
5764
5774
|
}
|
|
5765
5775
|
return false;
|
|
@@ -5787,45 +5797,6 @@ Document.prototype._applyVersionIncrement = function _applyVersionIncrement() {
|
|
|
5787
5797
|
* Module exports.
|
|
5788
5798
|
*/
|
|
5789
5799
|
|
|
5790
|
-
/*!
|
|
5791
|
-
* Check if `path` or any of its ancestor paths exist in `pathSet`.
|
|
5792
|
-
* For example:
|
|
5793
|
-
* _pathOrAncestorInSet('profile.firstName', Set(['profile'])) === true
|
|
5794
|
-
* _pathOrAncestorInSet('profile', Set(['profile.firstName'])) === false
|
|
5795
|
-
*/
|
|
5796
|
-
function _pathOrAncestorInSet(path, pathSet) {
|
|
5797
|
-
if (pathSet.has(path)) {
|
|
5798
|
-
return true;
|
|
5799
|
-
}
|
|
5800
|
-
let idx = path.indexOf('.');
|
|
5801
|
-
while (idx !== -1) {
|
|
5802
|
-
if (pathSet.has(path.substring(0, idx))) {
|
|
5803
|
-
return true;
|
|
5804
|
-
}
|
|
5805
|
-
idx = path.indexOf('.', idx + 1);
|
|
5806
|
-
}
|
|
5807
|
-
return false;
|
|
5808
|
-
}
|
|
5809
|
-
|
|
5810
|
-
/*!
|
|
5811
|
-
* Check if `path`, any of its ancestor paths, or any of its descendant paths
|
|
5812
|
-
* exist in `pathSet`.
|
|
5813
|
-
* For example:
|
|
5814
|
-
* _pathOverlapsSet('profile.firstName', Set(['profile'])) === true
|
|
5815
|
-
* _pathOverlapsSet('profile', Set(['profile.firstName'])) === true
|
|
5816
|
-
*/
|
|
5817
|
-
function _pathOverlapsSet(path, pathSet) {
|
|
5818
|
-
if (_pathOrAncestorInSet(path, pathSet)) {
|
|
5819
|
-
return true;
|
|
5820
|
-
}
|
|
5821
|
-
for (const p of pathSet) {
|
|
5822
|
-
if (p.length > path.length + 1 && p[path.length] === '.' && p.slice(0, path.length) === path) {
|
|
5823
|
-
return true;
|
|
5824
|
-
}
|
|
5825
|
-
}
|
|
5826
|
-
return false;
|
|
5827
|
-
}
|
|
5828
|
-
|
|
5829
5800
|
Document.VERSION_WHERE = VERSION_WHERE;
|
|
5830
5801
|
Document.VERSION_INC = VERSION_INC;
|
|
5831
5802
|
Document.VERSION_ALL = VERSION_ALL;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const wildcardSegment = '$*';
|
|
4
|
+
const numericSegmentRegex = /^\d+$/;
|
|
5
|
+
|
|
6
|
+
class PathTrieNode {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.children = new Map();
|
|
9
|
+
this.terminal = false;
|
|
10
|
+
this.hasWildcard = false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Trie of dotted paths for efficient path matching. Supports the map wildcard
|
|
16
|
+
* segment `$*`, which matches any single segment. For example, a trie
|
|
17
|
+
* containing `settings.$*` matches `settings.theme` but not `settings` or
|
|
18
|
+
* `profile.theme`. In array lookups, numeric segments like array indexes are
|
|
19
|
+
* ignorable: a trie containing `comments.text` matches `comments.0.text`.
|
|
20
|
+
* Single-segment string lookups skip the numeric check entirely.
|
|
21
|
+
*
|
|
22
|
+
* @api private
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
class PathTrie {
|
|
26
|
+
/**
|
|
27
|
+
* @param {string[]} [paths] paths to add to the trie
|
|
28
|
+
*/
|
|
29
|
+
constructor(paths) {
|
|
30
|
+
this.root = new PathTrieNode();
|
|
31
|
+
if (paths != null) {
|
|
32
|
+
for (const path of paths) {
|
|
33
|
+
this.add(path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Add a dotted path, like `settings.$*` or `profile.firstName`, to the trie.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} path
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
add(path) {
|
|
45
|
+
if (path.indexOf('.') === -1) {
|
|
46
|
+
this._addSegment(this.root, path).terminal = true;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let node = this.root;
|
|
50
|
+
for (const segment of path.split('.')) {
|
|
51
|
+
node = this._addSegment(node, segment);
|
|
52
|
+
}
|
|
53
|
+
node.terminal = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {PathTrieNode} node
|
|
58
|
+
* @param {string} segment
|
|
59
|
+
*/
|
|
60
|
+
_addSegment(node, segment) {
|
|
61
|
+
let child = node.children.get(segment);
|
|
62
|
+
if (child == null) {
|
|
63
|
+
child = new PathTrieNode();
|
|
64
|
+
node.children.set(segment, child);
|
|
65
|
+
if (segment === wildcardSegment) {
|
|
66
|
+
node.hasWildcard = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return child;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if `path` or any of its ancestor paths exist in the trie. `path` is
|
|
74
|
+
* either a single segment string or a pre-split array of segments — lookups
|
|
75
|
+
* never split, callers are responsible for splitting dotted paths.
|
|
76
|
+
* For example, with a trie containing 'profile':
|
|
77
|
+
* matchesPathOrAncestor(['profile', 'firstName']) === true
|
|
78
|
+
* matchesPathOrAncestor('profile') === true
|
|
79
|
+
* But with a trie containing 'profile.firstName':
|
|
80
|
+
* matchesPathOrAncestor('profile') === false
|
|
81
|
+
*
|
|
82
|
+
* @param {string|string[]} path
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
matchesPathOrAncestor(path) {
|
|
86
|
+
return this._walk(path) === true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if `path`, any of its ancestor paths, or any of its descendant paths
|
|
91
|
+
* exist in the trie. `path` is either a single segment string or a pre-split
|
|
92
|
+
* array of segments — lookups never split, callers are responsible for
|
|
93
|
+
* splitting dotted paths. For example:
|
|
94
|
+
* new PathTrie(['profile']).overlapsPath(['profile', 'firstName']) === true
|
|
95
|
+
* new PathTrie(['profile.firstName']).overlapsPath('profile') === true
|
|
96
|
+
*
|
|
97
|
+
* @param {string|string[]} path
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
overlapsPath(path) {
|
|
101
|
+
return this._walk(path) !== false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Walk `path` through the trie. `path` is assumed to be pre-split - if the
|
|
106
|
+
* path has multiple segments, pass in an array of strings. Returns `true` if
|
|
107
|
+
* a terminal node was found at `path` or one of its ancestors, `false` if the
|
|
108
|
+
* walk dead-ended, and the array of reached nodes if `path` is a strict prefix
|
|
109
|
+
* of entries in the trie.
|
|
110
|
+
*
|
|
111
|
+
* @param {string|string[]} path
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
_walk(path) {
|
|
115
|
+
if (typeof path === 'string') {
|
|
116
|
+
const next = [];
|
|
117
|
+
const literal = this.root.children.get(path);
|
|
118
|
+
if (literal != null) {
|
|
119
|
+
if (literal.terminal) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
next.push(literal);
|
|
123
|
+
}
|
|
124
|
+
if (this.root.hasWildcard) {
|
|
125
|
+
const wildcard = this.root.children.get(wildcardSegment);
|
|
126
|
+
if (wildcard.terminal) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
next.push(wildcard);
|
|
130
|
+
}
|
|
131
|
+
return next.length === 0 ? false : next;
|
|
132
|
+
}
|
|
133
|
+
let nodes = [this.root];
|
|
134
|
+
for (const segment of path) {
|
|
135
|
+
const next = [];
|
|
136
|
+
const isNumericSegment = numericSegmentRegex.test(segment);
|
|
137
|
+
for (const node of nodes) {
|
|
138
|
+
if (isNumericSegment) {
|
|
139
|
+
// Numeric segments like array indexes are ignorable: `comments.text`
|
|
140
|
+
// in the trie matches the modified path `comments.0.text`.
|
|
141
|
+
next.push(node);
|
|
142
|
+
}
|
|
143
|
+
const literal = node.children.get(segment);
|
|
144
|
+
if (literal != null) {
|
|
145
|
+
if (literal.terminal) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
next.push(literal);
|
|
149
|
+
}
|
|
150
|
+
if (node.hasWildcard) {
|
|
151
|
+
const wildcard = node.children.get(wildcardSegment);
|
|
152
|
+
if (wildcard.terminal) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
next.push(wildcard);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (next.length === 0) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
nodes = next;
|
|
162
|
+
}
|
|
163
|
+
return nodes;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = PathTrie;
|
package/lib/model.js
CHANGED
|
@@ -3622,6 +3622,15 @@ Model.bulkSave = async function bulkSave(documents, options) {
|
|
|
3622
3622
|
}
|
|
3623
3623
|
}
|
|
3624
3624
|
|
|
3625
|
+
// Associate the documents with the session like `save()` does, so built-in
|
|
3626
|
+
// `pre('save')` middleware like `trackTransaction` can snapshot document
|
|
3627
|
+
// state and restore it between transaction retries. Re: gh-16432
|
|
3628
|
+
if (Object.hasOwn(options, 'session')) {
|
|
3629
|
+
for (const document of documents) {
|
|
3630
|
+
document.$session(options.session);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3625
3634
|
await Promise.all(documents.map(doc => buildPreSavePromise(doc, options)));
|
|
3626
3635
|
|
|
3627
3636
|
const writeOperations = this.buildBulkWriteOperations(documents, options);
|
|
@@ -7,31 +7,45 @@ const utils = require('../utils');
|
|
|
7
7
|
|
|
8
8
|
module.exports = function trackTransaction(schema) {
|
|
9
9
|
schema.pre('save', trackTransactionPreSave);
|
|
10
|
+
schema.pre('deleteOne', { document: true, query: false }, trackTransactionPreDeleteOne);
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
function trackTransactionPreSave() {
|
|
13
|
-
|
|
14
|
+
_getInitialState(this);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function trackTransactionPreDeleteOne() {
|
|
18
|
+
const initialState = _getInitialState(this);
|
|
19
|
+
if (initialState != null && !Object.hasOwn(initialState, 'isDeleted')) {
|
|
20
|
+
initialState.isDeleted = this.$isDeleted();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function _getInitialState(doc) {
|
|
25
|
+
const session = doc.$session();
|
|
14
26
|
if (session == null) {
|
|
15
|
-
return;
|
|
27
|
+
return null;
|
|
16
28
|
}
|
|
17
29
|
if (session.transaction == null || session[sessionNewDocuments] == null) {
|
|
18
|
-
return;
|
|
30
|
+
return null;
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
if (!session[sessionNewDocuments].has(
|
|
33
|
+
if (!session[sessionNewDocuments].has(doc)) {
|
|
22
34
|
const initialState = {};
|
|
23
|
-
if (
|
|
35
|
+
if (doc.isNew) {
|
|
24
36
|
initialState.isNew = true;
|
|
25
37
|
}
|
|
26
|
-
if (
|
|
27
|
-
initialState.versionKey =
|
|
38
|
+
if (doc.$__schema.options.versionKey) {
|
|
39
|
+
initialState.versionKey = doc.get(doc.$__schema.options.versionKey);
|
|
28
40
|
}
|
|
29
41
|
|
|
30
|
-
initialState.modifiedPaths = new Set(Object.keys(
|
|
31
|
-
initialState.atomics = _getAtomics(
|
|
42
|
+
initialState.modifiedPaths = new Set(Object.keys(doc.$__.activePaths.getStatePaths('modify')));
|
|
43
|
+
initialState.atomics = _getAtomics(doc);
|
|
32
44
|
|
|
33
|
-
session[sessionNewDocuments].set(
|
|
45
|
+
session[sessionNewDocuments].set(doc, initialState);
|
|
34
46
|
}
|
|
47
|
+
|
|
48
|
+
return session[sessionNewDocuments].get(doc);
|
|
35
49
|
}
|
|
36
50
|
|
|
37
51
|
function _getAtomics(doc, previous) {
|
|
@@ -87,3 +101,4 @@ function mergeAtomics(destination, source) {
|
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
trackTransactionPreSave[symbols.builtInMiddleware] = true;
|
|
104
|
+
trackTransactionPreDeleteOne[symbols.builtInMiddleware] = true;
|
package/lib/query.js
CHANGED
|
@@ -2849,12 +2849,7 @@ Query.prototype.findOne = function(conditions, projection, options) {
|
|
|
2849
2849
|
|
|
2850
2850
|
Query.prototype._countDocuments = async function _countDocuments() {
|
|
2851
2851
|
this._applyTranslateAliases();
|
|
2852
|
-
|
|
2853
|
-
try {
|
|
2854
|
-
this.cast(this.model);
|
|
2855
|
-
} catch (err) {
|
|
2856
|
-
this.error(err);
|
|
2857
|
-
}
|
|
2852
|
+
this._castConditions();
|
|
2858
2853
|
|
|
2859
2854
|
if (this.error()) {
|
|
2860
2855
|
throw this.error();
|
|
@@ -5377,11 +5372,17 @@ Query.prototype.cursor = function cursor(opts) {
|
|
|
5377
5372
|
}
|
|
5378
5373
|
|
|
5379
5374
|
try {
|
|
5380
|
-
this.
|
|
5375
|
+
this._castConditions();
|
|
5381
5376
|
} catch (err) {
|
|
5377
|
+
// `_castConditions()` reports cast errors on the query, but `sanitizeFilter`
|
|
5378
|
+
// throws for filters it refuses outright, like `$where`.
|
|
5382
5379
|
return (new QueryCursor(this))._markError(err);
|
|
5383
5380
|
}
|
|
5384
5381
|
|
|
5382
|
+
if (this.error()) {
|
|
5383
|
+
return (new QueryCursor(this))._markError(this.error());
|
|
5384
|
+
}
|
|
5385
|
+
|
|
5385
5386
|
return new QueryCursor(this);
|
|
5386
5387
|
};
|
|
5387
5388
|
|
package/lib/schema/number.js
CHANGED
|
@@ -498,7 +498,7 @@ SchemaNumber.prototype.castForQuery = function($conditional, val, context) {
|
|
|
498
498
|
*/
|
|
499
499
|
|
|
500
500
|
SchemaNumber.prototype.toJSONSchema = function toJSONSchema(options) {
|
|
501
|
-
return this._createJSONSchemaTypeDefinition('number', 'number', options);
|
|
501
|
+
return this._addJSONSchemaEnum(this._createJSONSchemaTypeDefinition('number', 'number', options));
|
|
502
502
|
};
|
|
503
503
|
|
|
504
504
|
/*!
|
package/lib/schema/string.js
CHANGED
|
@@ -719,7 +719,7 @@ SchemaString.prototype.castForQuery = function($conditional, val, context) {
|
|
|
719
719
|
*/
|
|
720
720
|
|
|
721
721
|
SchemaString.prototype.toJSONSchema = function toJSONSchema(options) {
|
|
722
|
-
return this._createJSONSchemaTypeDefinition('string', 'string', options);
|
|
722
|
+
return this._addJSONSchemaEnum(this._createJSONSchemaTypeDefinition('string', 'string', options));
|
|
723
723
|
};
|
|
724
724
|
|
|
725
725
|
SchemaString.prototype.autoEncryptionType = function autoEncryptionType() {
|
package/lib/schema.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
const EventEmitter = require('events').EventEmitter;
|
|
8
8
|
const Kareem = require('kareem');
|
|
9
9
|
const MongooseError = require('./error/mongooseError');
|
|
10
|
+
const PathTrie = require('./helpers/pathTrie');
|
|
10
11
|
const SchemaType = require('./schemaType');
|
|
11
12
|
const SchemaTypeOptions = require('./options/schemaTypeOptions');
|
|
12
13
|
const VirtualOptions = require('./options/virtualOptions');
|
|
@@ -79,7 +80,7 @@ const numberRE = /^\d+$/;
|
|
|
79
80
|
* - [validateBeforeSave](https://mongoosejs.com/docs/guide.html#validateBeforeSave) - bool - defaults to `true`
|
|
80
81
|
* - [validateModifiedOnly](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) - bool - defaults to `false`
|
|
81
82
|
* - [versionKey](https://mongoosejs.com/docs/guide.html#versionKey): string or object - defaults to "__v"
|
|
82
|
-
* - [optimisticConcurrency](https://mongoosejs.com/docs/guide.html#optimisticConcurrency): bool or string[] or { exclude: string[] } - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html) for all fields. Set to a string array to enable optimistic concurrency only for the specified fields; note that this **replaces** the default array versioning behavior. Set to `{ exclude: string[] }` to enable optimistic concurrency for all fields except the specified ones; this also replaces the default array versioning.
|
|
83
|
+
* - [optimisticConcurrency](https://mongoosejs.com/docs/guide.html#optimisticConcurrency): bool or string[] or { exclude: string[] } - defaults to false. Set to true to enable [optimistic concurrency](https://thecodebarbarian.com/whats-new-in-mongoose-5-10-optimistic-concurrency.html) for all fields. Set to a string array to enable optimistic concurrency only for the specified fields; note that this **replaces** the default array versioning behavior. Set to `{ exclude: string[] }` to enable optimistic concurrency for all fields except the specified ones; this also replaces the default array versioning. Paths may include the map wildcard `$*`, like `'settings.$*'`, to match all keys of a map.
|
|
83
84
|
* - [collation](https://mongoosejs.com/docs/guide.html#collation): object - defaults to null (which means use no collation)
|
|
84
85
|
* - [timeseries](https://mongoosejs.com/docs/guide.html#timeseries): object - defaults to null (which means this schema's collection won't be a timeseries collection)
|
|
85
86
|
* - [selectPopulatedPaths](https://mongoosejs.com/docs/guide.html#selectPopulatedPaths): boolean - defaults to `true`
|
|
@@ -3142,9 +3143,9 @@ Schema.prototype._preCompile = function _preCompile() {
|
|
|
3142
3143
|
};
|
|
3143
3144
|
|
|
3144
3145
|
/*!
|
|
3145
|
-
* Build precomputed
|
|
3146
|
-
*
|
|
3147
|
-
*
|
|
3146
|
+
* Build precomputed path tries for optimisticConcurrency include/exclude so
|
|
3147
|
+
* that lookups at save time are a single trie walk that handles nested paths
|
|
3148
|
+
* and map wildcards like `settings.$*`.
|
|
3148
3149
|
*/
|
|
3149
3150
|
|
|
3150
3151
|
function _precomputeOptimisticConcurrency(schema) {
|
|
@@ -3153,9 +3154,9 @@ function _precomputeOptimisticConcurrency(schema) {
|
|
|
3153
3154
|
return;
|
|
3154
3155
|
}
|
|
3155
3156
|
if (Array.isArray(opt)) {
|
|
3156
|
-
schema.options.
|
|
3157
|
+
schema.options._optimisticConcurrencyTrie = new PathTrie(opt);
|
|
3157
3158
|
} else if (Array.isArray(opt.exclude)) {
|
|
3158
|
-
schema.options.
|
|
3159
|
+
schema.options._optimisticConcurrencyExcludeTrie = new PathTrie(opt.exclude);
|
|
3159
3160
|
}
|
|
3160
3161
|
}
|
|
3161
3162
|
|
|
@@ -3215,27 +3216,19 @@ Schema.prototype.toJSONSchema = function toJSONSchema(options) {
|
|
|
3215
3216
|
}
|
|
3216
3217
|
|
|
3217
3218
|
const lastSubpath = schemaType._presplitPath[schemaType._presplitPath.length - 1];
|
|
3218
|
-
let isRequired = false;
|
|
3219
3219
|
if (path === '_id') {
|
|
3220
3220
|
if (!jsonSchemaForPath.required) {
|
|
3221
3221
|
jsonSchemaForPath.required = [];
|
|
3222
3222
|
}
|
|
3223
3223
|
jsonSchemaForPath.required.push('_id');
|
|
3224
|
-
isRequired = true;
|
|
3225
3224
|
} else if (schemaType.options.required && typeof schemaType.options.required !== 'function') {
|
|
3226
3225
|
if (!jsonSchemaForPath.required) {
|
|
3227
3226
|
jsonSchemaForPath.required = [];
|
|
3228
3227
|
}
|
|
3229
3228
|
// Only `required: true` paths are required, conditional required is not required
|
|
3230
3229
|
jsonSchemaForPath.required.push(lastSubpath);
|
|
3231
|
-
isRequired = true;
|
|
3232
3230
|
}
|
|
3233
3231
|
jsonSchemaForPath.properties[lastSubpath] = schemaType.toJSONSchema(options);
|
|
3234
|
-
if (schemaType.options.enum) {
|
|
3235
|
-
jsonSchemaForPath.properties[lastSubpath].enum = isRequired
|
|
3236
|
-
? schemaType.options.enum
|
|
3237
|
-
: [...schemaType.options.enum, null];
|
|
3238
|
-
}
|
|
3239
3232
|
}
|
|
3240
3233
|
|
|
3241
3234
|
// Otherwise MongoDB errors with "$jsonSchema keyword 'required' cannot be an empty array"
|
package/lib/schemaType.js
CHANGED
|
@@ -215,6 +215,27 @@ SchemaType.prototype._createJSONSchemaTypeDefinition = function _createJSONSchem
|
|
|
215
215
|
return { type: [type, 'null'] };
|
|
216
216
|
};
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Helper for adding this SchemaType's enum values to a JSON schema type definition.
|
|
220
|
+
* Reads `enumValues` rather than `options.enum` because `enum` also accepts an object.
|
|
221
|
+
*
|
|
222
|
+
* @param {object} definition the type definition from `_createJSONSchemaTypeDefinition()`
|
|
223
|
+
* @returns {object} the same definition
|
|
224
|
+
* @api private
|
|
225
|
+
*/
|
|
226
|
+
|
|
227
|
+
SchemaType.prototype._addJSONSchemaEnum = function _addJSONSchemaEnum(definition) {
|
|
228
|
+
if (!Array.isArray(this.enumValues) || this.enumValues.length === 0) {
|
|
229
|
+
return definition;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// The enum validator allows nullish values, so allow `null` wherever the type does.
|
|
233
|
+
const allowsNull = Array.isArray(definition.type ?? definition.bsonType);
|
|
234
|
+
definition.enum = allowsNull ? [...this.enumValues, null] : [...this.enumValues];
|
|
235
|
+
|
|
236
|
+
return definition;
|
|
237
|
+
};
|
|
238
|
+
|
|
218
239
|
/**
|
|
219
240
|
* The validators that Mongoose should run to validate properties at this SchemaType's path.
|
|
220
241
|
*
|
package/lib/utils.js
CHANGED
|
@@ -421,26 +421,16 @@ exports.isPOJO = require('./helpers/isPOJO');
|
|
|
421
421
|
|
|
422
422
|
exports.isNonBuiltinObject = function isNonBuiltinObject(val) {
|
|
423
423
|
return typeof val === 'object' &&
|
|
424
|
-
!
|
|
424
|
+
!Array.isArray(val) &&
|
|
425
|
+
!(val instanceof Date) &&
|
|
426
|
+
!(val instanceof Boolean) &&
|
|
427
|
+
!(val instanceof Number) &&
|
|
428
|
+
!(val instanceof String) &&
|
|
425
429
|
!exports.isMongooseType(val) &&
|
|
426
430
|
!(val instanceof UUID) &&
|
|
427
431
|
val != null;
|
|
428
432
|
};
|
|
429
433
|
|
|
430
|
-
/**
|
|
431
|
-
* Determines if `obj` is a built-in object like an array, date, boolean,
|
|
432
|
-
* etc.
|
|
433
|
-
* @param {any} arg
|
|
434
|
-
*/
|
|
435
|
-
|
|
436
|
-
exports.isNativeObject = function(arg) {
|
|
437
|
-
return Array.isArray(arg) ||
|
|
438
|
-
arg instanceof Date ||
|
|
439
|
-
arg instanceof Boolean ||
|
|
440
|
-
arg instanceof Number ||
|
|
441
|
-
arg instanceof String;
|
|
442
|
-
};
|
|
443
|
-
|
|
444
434
|
/**
|
|
445
435
|
* Determines if `val` is an object that has no own keys
|
|
446
436
|
* @param {any} val
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongoose",
|
|
3
3
|
"description": "Mongoose MongoDB ODM",
|
|
4
|
-
"version": "9.9.
|
|
4
|
+
"version": "9.9.2",
|
|
5
5
|
"author": "Guillermo Rauch <guillermo@learnboost.com>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongodb",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"sift": "17.1.3"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@ark/attest": "0.56.
|
|
32
|
+
"@ark/attest": "0.56.3",
|
|
33
33
|
"@eslint/js": "^9.39.3",
|
|
34
34
|
"@mongodb-js/mongodb-downloader": "^1.0.0",
|
|
35
35
|
"@types/node": "^20.19.0",
|
|
@@ -37,30 +37,30 @@
|
|
|
37
37
|
"acquit-ignore": "0.2.2",
|
|
38
38
|
"acquit-require": "0.1.1",
|
|
39
39
|
"ajv": "8.20.0",
|
|
40
|
-
"c8": "
|
|
40
|
+
"c8": "12.0.0",
|
|
41
41
|
"cheerio": "1.2.0",
|
|
42
42
|
"dox": "1.0.0",
|
|
43
|
-
"eslint": "10.
|
|
43
|
+
"eslint": "10.8.0",
|
|
44
44
|
"eslint-plugin-mocha-no-only": "1.2.0",
|
|
45
45
|
"express": "5.2.1",
|
|
46
|
-
"fs-extra": "~11.
|
|
46
|
+
"fs-extra": "~11.4.0",
|
|
47
47
|
"glob": "^13.0.6",
|
|
48
48
|
"globals": "^17.4.0",
|
|
49
49
|
"highlight.js": "11.11.1",
|
|
50
|
-
"linkinator": "
|
|
50
|
+
"linkinator": "8.x",
|
|
51
51
|
"lodash.isequal": "4.5.0",
|
|
52
52
|
"lodash.isequalwith": "4.4.0",
|
|
53
|
-
"markdownlint-cli2": "0.
|
|
54
|
-
"marked": "18.0.
|
|
53
|
+
"markdownlint-cli2": "0.23.2",
|
|
54
|
+
"marked": "18.0.7",
|
|
55
55
|
"mkdirp": "^3.0.1",
|
|
56
|
-
"mocha": "12.0.0-
|
|
56
|
+
"mocha": "12.0.0-rc.5",
|
|
57
57
|
"moment": "2.30.1",
|
|
58
58
|
"mongodb-client-encryption": "^7.2.0",
|
|
59
59
|
"mongodb-memory-server": "11.2.0",
|
|
60
60
|
"mongodb-runner": "^6.0.0",
|
|
61
61
|
"ncp": "^2.0.0",
|
|
62
62
|
"pug": "3.0.4",
|
|
63
|
-
"sinon": "22.
|
|
63
|
+
"sinon": "22.1.0",
|
|
64
64
|
"tstyche": "^7.0.0",
|
|
65
65
|
"typescript": "5.9.3",
|
|
66
66
|
"typescript-eslint": "^8.31.1",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
"publish-7x": "npm publish --tag 7x",
|
|
101
101
|
"create-separate-require-instance": "rm -rf ./node_modules/mongoose-separate-require-instance && node ./scripts/create-tarball && tar -xzf mongoose.tgz -C ./node_modules && mv ./node_modules/package ./node_modules/mongoose-separate-require-instance",
|
|
102
102
|
"test": "mocha --exit --ignore \"test/encryption/**/*.test.js\" \"./test/**/*.test.js\"",
|
|
103
|
-
"test:ci": "npm run test -- --reporter min",
|
|
103
|
+
"test:ci": "npm run test -- --reporter min --timeout 10000",
|
|
104
104
|
"test-deno": "deno run --allow-env --allow-read --allow-net --allow-run --allow-sys --allow-write ./test/deno.mjs",
|
|
105
105
|
"test-deno:ci": "npm run test-deno -- --reporter min",
|
|
106
106
|
"test-rs": "START_REPLICA_SET=1 mocha --timeout 30000 --exit --ignore \"test/encryption/**/*.test.js\" \"./test/**/*.test.js\"",
|
package/types/document.d.ts
CHANGED
|
@@ -289,9 +289,9 @@ declare module 'mongoose' {
|
|
|
289
289
|
): ToObjectReturnType<PopulatedRawDocType, TVirtuals, O, TSchemaOptions>;
|
|
290
290
|
toJSON<PopulatedRawDocType>(
|
|
291
291
|
this: PopulatedDocumentMarker<PopulatedRawDocType, any>
|
|
292
|
-
):
|
|
292
|
+
): DefaultToObjectReturnType<PopulatedRawDocType, TVirtuals, TSchemaOptions, 'toJSON'>;
|
|
293
293
|
toJSON<O extends ToObjectOptions>(options: O): ToObjectReturnType<DocType, TVirtuals, O, TSchemaOptions>;
|
|
294
|
-
toJSON(options?: ToObjectOptions):
|
|
294
|
+
toJSON(options?: ToObjectOptions): DefaultToObjectReturnType<DocType, TVirtuals, TSchemaOptions, 'toJSON'>;
|
|
295
295
|
toJSON<T>(options?: ToObjectOptions): Default__v<Require_id<T>, ResolveSchemaOptions<TSchemaOptions>>;
|
|
296
296
|
|
|
297
297
|
/** Converts this document into a plain-old JavaScript object ([POJO](https://masteringjs.io/tutorials/fundamentals/pojo)). */
|
|
@@ -309,9 +309,9 @@ declare module 'mongoose' {
|
|
|
309
309
|
): ToObjectReturnType<PopulatedRawDocType, TVirtuals, O, TSchemaOptions>;
|
|
310
310
|
toObject<PopulatedRawDocType>(
|
|
311
311
|
this: PopulatedDocumentMarker<PopulatedRawDocType, any>
|
|
312
|
-
):
|
|
312
|
+
): DefaultToObjectReturnType<PopulatedRawDocType, TVirtuals, TSchemaOptions, 'toObject'>;
|
|
313
313
|
toObject<O extends ToObjectOptions>(options: O): ToObjectReturnType<DocType, TVirtuals, O, TSchemaOptions>;
|
|
314
|
-
toObject(options?: ToObjectOptions):
|
|
314
|
+
toObject(options?: ToObjectOptions): DefaultToObjectReturnType<DocType, TVirtuals, TSchemaOptions, 'toObject'>;
|
|
315
315
|
toObject<T>(options?: ToObjectOptions): Default__v<Require_id<T>, ResolveSchemaOptions<TSchemaOptions>>;
|
|
316
316
|
|
|
317
317
|
/** Clears the modified state on the specified path. */
|
package/types/index.d.ts
CHANGED
|
@@ -1122,6 +1122,31 @@ declare module 'mongoose' {
|
|
|
1122
1122
|
? { [K in keyof T]: ApplyFlattenTransforms<T[K], O> }
|
|
1123
1123
|
: T;
|
|
1124
1124
|
|
|
1125
|
+
/**
|
|
1126
|
+
* Extracts the `toObject` or `toJSON` options declared in the schema options.
|
|
1127
|
+
* The runtime applies these as defaults when the corresponding method is
|
|
1128
|
+
* called without arguments.
|
|
1129
|
+
*/
|
|
1130
|
+
export type SchemaDeclaredToObjectOptions<TSchemaOptions, Key extends 'toObject' | 'toJSON'> =
|
|
1131
|
+
Key extends keyof TSchemaOptions
|
|
1132
|
+
? NonNullable<TSchemaOptions[Key]> extends infer O extends ToObjectOptions
|
|
1133
|
+
? O
|
|
1134
|
+
: {}
|
|
1135
|
+
: {};
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Computes the return type of toObject/toJSON when called without arguments,
|
|
1139
|
+
* applying the options declared in the schema like the runtime does. When the
|
|
1140
|
+
* schema declares no options for the method, the plain document shape is
|
|
1141
|
+
* returned without running it through the option transforms.
|
|
1142
|
+
*/
|
|
1143
|
+
export type DefaultToObjectReturnType<DocType, TVirtuals, TSchemaOptions, Key extends 'toObject' | 'toJSON'> =
|
|
1144
|
+
SchemaDeclaredToObjectOptions<TSchemaOptions, Key> extends infer O extends ToObjectOptions
|
|
1145
|
+
? keyof O extends never
|
|
1146
|
+
? Default__v<Require_id<DocType>, TSchemaOptions>
|
|
1147
|
+
: ToObjectReturnType<DocType, TVirtuals, O, TSchemaOptions>
|
|
1148
|
+
: Default__v<Require_id<DocType>, TSchemaOptions>;
|
|
1149
|
+
|
|
1125
1150
|
/**
|
|
1126
1151
|
* Computes the return type of toObject/toJSON based on the provided options.
|
|
1127
1152
|
* Uses a single-pass transform for flatten operations to correctly handle all combinations.
|
package/types/schematypes.d.ts
CHANGED