Skip to content

Commit 3620dc1

Browse files
committed
Merge branch 'master' into 6.6
2 parents c4f0027 + fbf543c commit 3620dc1

25 files changed

+377
-316
lines changed

.github/dependabot.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ updates:
77
open-pull-requests-limit: 100
88
target-branch: master
99
ignore:
10-
- dependency-name: "dox"
1110
- package-ecosystem: github-actions
1211
directory: "/"
1312
schedule:

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
6.5.5 / 2022-09-07
2+
==================
3+
* fix(setDefaultsOnInsert): avoid applying defaults on insert if nested property set #12279
4+
* fix(model): make applyHooks() and applyMethods() handle case where custom method is set to Mongoose implementation #12254
5+
* fix(types): add string "ascending" and "descending" index-directions #10269
6+
* docs: upgrade dox to 1.0.0 #12403 [hasezoey](https://github.com/hasezoey)
7+
* docs: update old mongodb nodejs driver documentation urls #12387 [hasezoey](https://github.com/hasezoey)
8+
* docs: update JSDOC ... (spread) definition #12388 [hasezoey](https://github.com/hasezoey)
9+
* refactor(model): allow optionally passing indexes to createIndexes and cleanIndexes #12280 [AbdelrahmanHafez](https://github.com/AbdelrahmanHafez)
10+
111
6.5.4 / 2022-08-30
212
==================
313
* fix(document): allow calling $assertPopulated() with values to better support manual population #12233

docs/api_split.pug

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ block content
6868
if prop.inherits != null
6969
h5 Inherits:
7070
ul
71-
li <span class="method-type">&laquo;#{prop.inherits}&raquo;</span>
71+
li <span class="method-type"><a href="#{prop.inherits.url}">&laquo;#{prop.inherits.text}&raquo;</a></span>
7272
if prop.see != null && prop.see.length > 0
7373
h5 See:
7474
ul

docs/source/api.js

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ parse();
9999
* @property {string} [deprecated] Defines wheter the current Property is signaled as deprecated
100100
* @property {SeeObject[]} [see] Defines all "@see" references
101101
* @property {TagObject[]} [param] Defines all "@param" references
102-
* @property {String} [inherits] Defines the string for "@inherits"
102+
* @property {SeeObject} [inherits] Defines the string for "@inherits"
103103
*/
104104

105105
function parse() {
@@ -167,35 +167,7 @@ function parse() {
167167
// for this type, it needs to be parsed from the string itself to support more than 1 word
168168
// this is required because "@see" is kinda badly defined and mongoose uses a slightly customized way (longer text and different kinds of links)
169169

170-
// the following regex matches cases of:
171-
// "External Links http://someurl.com/" -> "External Links"
172-
// "External https://someurl.com/" -> "External"
173-
// "Id href #some_Some-method" -> "Id href"
174-
// "Local Absolute /docs/somewhere" -> "Local Absolute"
175-
// "No Href" -> "No Href"
176-
// "https://someurl.com" -> "" (fallback added)
177-
// "Some#Method #something" -> "Some#Method"
178-
// The remainder is simply taken by a call to "slice" (also the text is trimmed later)
179-
const textMatches = /^(.*? (?=#|\/|(?:https?:)|$))/i.exec(tag.string);
180-
181-
let text = undefined;
182-
let url = undefined;
183-
if (textMatches === null || textMatches === undefined) {
184-
// warn because this most likely is a badly defined "@see"
185-
console.warn(`No Text Matches found in @see for "${ctx.constructor}.${ctx.name}"`)
186-
187-
// if no text is found, add text as url and use the url itself as the text
188-
url = tag.string;
189-
text = tag.string;
190-
} else {
191-
text = textMatches[1].trim();
192-
url = tag.string.slice(text.length).trim();
193-
}
194-
195-
ctx.see.push({
196-
text: text || 'No Description', // fallback text, so that the final text does not end up as a empty element that cannot be seen
197-
url: url || undefined, // change to be "undefined" if text is empty or non-valid
198-
});
170+
ctx.see.push(extractTextUrlFromTag(tag, ctx, true));
199171
break;
200172
case 'receiver':
201173
console.warn(`Found "@receiver" tag in ${ctx.constructor} ${ctx.name}`);
@@ -241,7 +213,7 @@ function parse() {
241213
ctx.return = tag;
242214
break;
243215
case 'inherits':
244-
ctx[tag.type] = tag.string;
216+
ctx.inherits = extractTextUrlFromTag(tag, ctx);
245217
break;
246218
case 'event':
247219
case 'param':
@@ -344,3 +316,44 @@ function parse() {
344316
out.push(data);
345317
}
346318
}
319+
320+
/**
321+
* Extract the Text and Url from a description if any
322+
* @param {Tag} tag The tag to process the resulting object from
323+
* @param {PropContext} ctx The current ctx for warnings
324+
* @param {Boolean} warnOnMissingUrl Warn if the url is missing, false by default
325+
* @returns {{ text: string, url: string }}
326+
*/
327+
function extractTextUrlFromTag(tag, ctx, warnOnMissingUrl = false) {
328+
// the following regex matches cases of:
329+
// "External Links http://someurl.com/" -> "External Links"
330+
// "External https://someurl.com/" -> "External"
331+
// "Id href #some_Some-method" -> "Id href"
332+
// "Local Absolute /docs/somewhere" -> "Local Absolute"
333+
// "No Href" -> "No Href"
334+
// "https://someurl.com" -> "" (fallback added)
335+
// "Some#Method #something" -> "Some#Method"
336+
// The remainder is simply taken by a call to "slice" (also the text is trimmed later)
337+
const textMatches = /^(.*? (?=#|\/|(?:https?:)|$))/i.exec(tag.string);
338+
339+
let text = undefined;
340+
let url = undefined;
341+
if (textMatches === null || textMatches === undefined) {
342+
if (warnOnMissingUrl) {
343+
// warn for the cases where URL should be defined (like in "@see")
344+
console.warn(`No Text Matches found in tag for "${ctx.constructor}.${ctx.name}"`)
345+
}
346+
347+
// if no text is found, add text as url and use the url itself as the text
348+
url = tag.string;
349+
text = tag.string;
350+
} else {
351+
text = textMatches[1].trim();
352+
url = tag.string.slice(text.length).trim();
353+
}
354+
355+
return {
356+
text: text || 'No Description', // fallback text, so that the final text does not end up as a empty element that cannot be seen
357+
url: url || undefined, // change to be "undefined" if text is empty or non-valid
358+
};
359+
}

lib/aggregate.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ Aggregate.prototype.explain = function(verbosity, callback) {
789789
};
790790

791791
/**
792-
* Sets the allowDiskUse option for the aggregation query (ignored for < 2.6.0)
792+
* Sets the allowDiskUse option for the aggregation query
793793
*
794794
* #### Example:
795795
*
@@ -806,7 +806,7 @@ Aggregate.prototype.allowDiskUse = function(value) {
806806
};
807807

808808
/**
809-
* Sets the hint option for the aggregation query (ignored for < 3.6.0)
809+
* Sets the hint option for the aggregation query
810810
*
811811
* #### Example:
812812
*
@@ -1073,7 +1073,7 @@ Aggregate.prototype.catch = function(reject) {
10731073
};
10741074

10751075
/**
1076-
* Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js
1076+
* Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js)
10771077
* You do not need to call this function explicitly, the JavaScript runtime
10781078
* will call it for you.
10791079
*

lib/cursor/AggregationCursor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ AggregationCursor.prototype.eachAsync = function(fn, opts, callback) {
231231
};
232232

233233
/**
234-
* Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js
234+
* Returns an asyncIterator for use with [`for/await/of` loops](https://thecodebarbarian.com/getting-started-with-async-iterators-in-node-js)
235235
* You do not need to call this function explicitly, the JavaScript runtime
236236
* will call it for you.
237237
*

lib/helpers/indexes/isIndexEqual.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ const utils = require('../../utils');
66
* Given a Mongoose index definition (key + options objects) and a MongoDB server
77
* index definition, determine if the two indexes are equal.
88
*
9-
* @param {Object} key the Mongoose index spec
9+
* @param {Object} schemaIndexKeysObject the Mongoose index spec
1010
* @param {Object} options the Mongoose index definition's options
1111
* @param {Object} dbIndex the index in MongoDB as returned by `listIndexes()`
1212
* @api private
1313
*/
1414

15-
module.exports = function isIndexEqual(key, options, dbIndex) {
15+
module.exports = function isIndexEqual(schemaIndexKeysObject, options, dbIndex) {
1616
// Special case: text indexes have a special format in the db. For example,
1717
// `{ name: 'text' }` becomes:
1818
// {
@@ -30,11 +30,11 @@ module.exports = function isIndexEqual(key, options, dbIndex) {
3030
delete dbIndex.key._fts;
3131
delete dbIndex.key._ftsx;
3232
const weights = { ...dbIndex.weights, ...dbIndex.key };
33-
if (Object.keys(weights).length !== Object.keys(key).length) {
33+
if (Object.keys(weights).length !== Object.keys(schemaIndexKeysObject).length) {
3434
return false;
3535
}
3636
for (const prop of Object.keys(weights)) {
37-
if (!(prop in key)) {
37+
if (!(prop in schemaIndexKeysObject)) {
3838
return false;
3939
}
4040
const weight = weights[prop];
@@ -78,7 +78,7 @@ module.exports = function isIndexEqual(key, options, dbIndex) {
7878
}
7979
}
8080

81-
const schemaIndexKeys = Object.keys(key);
81+
const schemaIndexKeys = Object.keys(schemaIndexKeysObject);
8282
const dbIndexKeys = Object.keys(dbIndex.key);
8383
if (schemaIndexKeys.length !== dbIndexKeys.length) {
8484
return false;
@@ -87,7 +87,7 @@ module.exports = function isIndexEqual(key, options, dbIndex) {
8787
if (schemaIndexKeys[i] !== dbIndexKeys[i]) {
8888
return false;
8989
}
90-
if (!utils.deepEqual(key[schemaIndexKeys[i]], dbIndex.key[dbIndexKeys[i]])) {
90+
if (!utils.deepEqual(schemaIndexKeysObject[schemaIndexKeys[i]], dbIndex.key[dbIndexKeys[i]])) {
9191
return false;
9292
}
9393
}

lib/helpers/model/applyHooks.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ applyHooks.middlewareFunctions = [
2222
'init'
2323
];
2424

25+
/*!
26+
* ignore
27+
*/
28+
29+
const alreadyHookedFunctions = new Set(applyHooks.middlewareFunctions.flatMap(fn => ([fn, `$__${fn}`])));
30+
2531
/**
2632
* Register hooks for this model
2733
*
@@ -117,6 +123,9 @@ function applyHooks(model, schema, options) {
117123
checkForPromise: true
118124
});
119125
for (const method of customMethods) {
126+
if (alreadyHookedFunctions.has(method)) {
127+
continue;
128+
}
120129
if (!middleware.hasHooks(method)) {
121130
// Don't wrap if there are no hooks for the custom method to avoid
122131
// surprises. Also, `createWrapper()` enforces consistent async,

lib/helpers/model/applyMethods.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ module.exports = function applyMethods(model, schema) {
3030
throw new Error('You have a method and a property in your schema both ' +
3131
'named "' + method + '"');
3232
}
33+
34+
// Avoid making custom methods if user sets a method to itself, e.g.
35+
// `schema.method(save, Document.prototype.save)`. Can happen when
36+
// calling `loadClass()` with a class that `extends Document`. See gh-12254
37+
if (typeof fn === 'function' && model.prototype[method] === fn) {
38+
delete schema.methods[method];
39+
continue;
40+
}
41+
3342
if (schema.reserved[method] &&
3443
!get(schema, `methodOptions.${method}.suppressWarning`, false)) {
3544
utils.warn(`mongoose: the method name "${method}" is used by mongoose ` +

lib/helpers/setDefaultsOnInsert.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ function isModified(modified, path) {
105105
if (modified[path]) {
106106
return true;
107107
}
108+
109+
// Is any parent path of `path` modified?
108110
const sp = path.split('.');
109111
let cur = sp[0];
110112
for (let i = 1; i < sp.length; ++i) {
@@ -113,5 +115,18 @@ function isModified(modified, path) {
113115
}
114116
cur += '.' + sp[i];
115117
}
118+
119+
// Is any child of `path` modified?
120+
const modifiedKeys = Object.keys(modified);
121+
if (modifiedKeys.length) {
122+
const parentPath = path + '.';
123+
124+
for (const modifiedPath of modifiedKeys) {
125+
if (modifiedPath.slice(0, path.length + 1) === parentPath) {
126+
return true;
127+
}
128+
}
129+
}
130+
116131
return false;
117132
}

0 commit comments

Comments
 (0)