mongoose 9.9.4 → 9.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eslint.config.mjs +1 -0
- package/lib/aggregate.js +11 -4
- package/lib/cursor/aggregationCursor.js +19 -3
- package/lib/cursor/queryCursor.js +17 -5
- package/lib/document.js +10 -0
- package/lib/helpers/buildMiddlewareFilter.js +21 -3
- package/lib/helpers/model/applyHooks.js +9 -2
- package/lib/helpers/model/applyStaticHooks.js +9 -1
- package/lib/helpers/projection/isPathSelectedInclusive.js +1 -11
- package/lib/helpers/query/cast$expr.js +1 -1
- package/lib/model.js +47 -5
- package/lib/query.js +7 -1
- package/lib/schema/array.js +9 -1
- package/lib/schema/documentArray.js +2 -2
- package/lib/schema/subdocument.js +2 -2
- package/lib/schema.js +55 -0
- package/lib/schemaType.js +10 -4
- package/package.json +8 -8
- package/types/aggregate.d.ts +14 -1
- package/types/document.d.ts +2 -2
- package/types/index.d.ts +56 -8
- package/types/indexes.d.ts +73 -0
- package/types/middlewares.d.ts +11 -2
- package/types/models.d.ts +317 -1
- package/types/utility.d.ts +40 -0
package/eslint.config.mjs
CHANGED
|
@@ -43,6 +43,7 @@ export default defineConfig([
|
|
|
43
43
|
'no-constant-condition': 'off',
|
|
44
44
|
'no-multi-spaces': 'error',
|
|
45
45
|
'func-call-spacing': 'error',
|
|
46
|
+
'no-unused-vars': ['error', { ignoreRestSiblings: true }],
|
|
46
47
|
'no-trailing-spaces': 'error',
|
|
47
48
|
'no-undef': 'error',
|
|
48
49
|
'no-unneeded-ternary': 'error',
|
package/lib/aggregate.js
CHANGED
|
@@ -810,8 +810,7 @@ Aggregate.prototype.explain = async function explain(verbosity) {
|
|
|
810
810
|
const postFilter = buildMiddlewareFilter(this.options, 'post');
|
|
811
811
|
|
|
812
812
|
// Remove middleware option before passing to MongoDB
|
|
813
|
-
const
|
|
814
|
-
delete options.middleware;
|
|
813
|
+
const { middleware, ...options } = this.options ?? {};
|
|
815
814
|
|
|
816
815
|
try {
|
|
817
816
|
await model.hooks.execPre('aggregate', this, [], { filter: preFilter });
|
|
@@ -925,6 +924,8 @@ Aggregate.prototype.option = function(value) {
|
|
|
925
924
|
* Sets the `cursor` option and executes this aggregation, returning an aggregation cursor.
|
|
926
925
|
* Cursors are useful if you want to process the results of the aggregation one-at-a-time
|
|
927
926
|
* because the aggregation result is too big to fit into memory.
|
|
927
|
+
* Creating an aggregation cursor runs pre aggregate hooks, but not post aggregate hooks.
|
|
928
|
+
* To skip pre aggregate hooks, pass `middleware: false` to `.cursor()`.
|
|
928
929
|
*
|
|
929
930
|
* #### Example:
|
|
930
931
|
*
|
|
@@ -935,6 +936,8 @@ Aggregate.prototype.option = function(value) {
|
|
|
935
936
|
*
|
|
936
937
|
* @param {object} options
|
|
937
938
|
* @param {number} [options.batchSize] set the cursor batch size
|
|
939
|
+
* @param {boolean|object} [options.middleware=true] set to `false` to skip user-defined pre aggregate middleware
|
|
940
|
+
* @param {boolean} [options.middleware.pre=true] set to `false` to skip pre aggregate middleware
|
|
938
941
|
* @param {boolean} [options.useMongooseAggCursor] use experimental mongoose-specific aggregation cursor (for `eachAsync()` and other query cursor semantics)
|
|
939
942
|
* @return {AggregationCursor} cursor representing this aggregation
|
|
940
943
|
* @api public
|
|
@@ -943,6 +946,11 @@ Aggregate.prototype.option = function(value) {
|
|
|
943
946
|
|
|
944
947
|
Aggregate.prototype.cursor = function(options) {
|
|
945
948
|
this._optionsForExec();
|
|
949
|
+
if (utils.hasUserDefinedProperty(options, 'middleware')) {
|
|
950
|
+
const { middleware, ...cursorOptions } = options;
|
|
951
|
+
this.options.middleware = middleware;
|
|
952
|
+
options = cursorOptions;
|
|
953
|
+
}
|
|
946
954
|
this.options.cursor = options || {};
|
|
947
955
|
return new AggregationCursor(this); // return this;
|
|
948
956
|
};
|
|
@@ -1122,8 +1130,7 @@ Aggregate.prototype.exec = async function exec() {
|
|
|
1122
1130
|
throw new MongooseError('Aggregate has empty pipeline');
|
|
1123
1131
|
}
|
|
1124
1132
|
|
|
1125
|
-
const options = clone(_this.options || {});
|
|
1126
|
-
delete options.middleware;
|
|
1133
|
+
const { middleware, ...options } = clone(_this.options || {});
|
|
1127
1134
|
|
|
1128
1135
|
let result;
|
|
1129
1136
|
try {
|
|
@@ -11,6 +11,7 @@ const immediate = require('../helpers/immediate');
|
|
|
11
11
|
const kareem = require('kareem');
|
|
12
12
|
const util = require('util');
|
|
13
13
|
const { cursorNextChannel } = require('../tracing');
|
|
14
|
+
const { buildMiddlewareFilter } = require('../helpers/buildMiddlewareFilter');
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* An AggregationCursor is a concurrency primitive for processing aggregation
|
|
@@ -49,7 +50,14 @@ function AggregationCursor(agg) {
|
|
|
49
50
|
this._mongooseOptions = {};
|
|
50
51
|
|
|
51
52
|
if (connection) {
|
|
52
|
-
|
|
53
|
+
let options = agg.options || {};
|
|
54
|
+
if (Object.hasOwn(options, 'middleware')) {
|
|
55
|
+
// `middleware` is a Mongoose-only option used to skip user hooks; strip
|
|
56
|
+
// it from the clone passed through to the MongoDB driver.
|
|
57
|
+
const { middleware, ...driverOptions } = options;
|
|
58
|
+
options = driverOptions;
|
|
59
|
+
}
|
|
60
|
+
this.cursor = connection.db.aggregate(agg._pipeline, options);
|
|
53
61
|
setImmediate(() => this.emit('cursor', this.cursor));
|
|
54
62
|
} else {
|
|
55
63
|
_init(model, this, agg);
|
|
@@ -63,7 +71,8 @@ util.inherits(AggregationCursor, Readable);
|
|
|
63
71
|
*/
|
|
64
72
|
|
|
65
73
|
function _init(model, c, agg) {
|
|
66
|
-
|
|
74
|
+
const preFilter = buildMiddlewareFilter(agg.options, 'pre');
|
|
75
|
+
model.hooks.execPre('aggregate', agg, [], { filter: preFilter }).then(() => onPreComplete(null), err => onPreComplete(err));
|
|
67
76
|
|
|
68
77
|
function onPreComplete(err) {
|
|
69
78
|
if (err != null) {
|
|
@@ -90,7 +99,14 @@ function _init(model, c, agg) {
|
|
|
90
99
|
|
|
91
100
|
function _getRawCursor(model, aggregationCursor, agg) {
|
|
92
101
|
try {
|
|
93
|
-
|
|
102
|
+
let options = agg.options;
|
|
103
|
+
if (Object.hasOwn(options, 'middleware')) {
|
|
104
|
+
// `middleware` is a Mongoose-only option used to skip user hooks; strip
|
|
105
|
+
// it from the clone passed through to the MongoDB driver.
|
|
106
|
+
const { middleware, ...driverOptions } = options;
|
|
107
|
+
options = driverOptions;
|
|
108
|
+
}
|
|
109
|
+
const cursor = model.collection.aggregate(agg._pipeline, options);
|
|
94
110
|
aggregationCursor.cursor = cursor;
|
|
95
111
|
aggregationCursor.emit('cursor', cursor);
|
|
96
112
|
} catch (err) {
|
|
@@ -13,6 +13,7 @@ const immediate = require('../helpers/immediate');
|
|
|
13
13
|
const { once } = require('events');
|
|
14
14
|
const util = require('util');
|
|
15
15
|
const { cursorNextChannel } = require('../tracing');
|
|
16
|
+
const { buildMiddlewareFilter } = require('../helpers/buildMiddlewareFilter');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* A QueryCursor is a concurrency primitive for processing query results
|
|
@@ -21,7 +22,8 @@ const { cursorNextChannel } = require('../tracing');
|
|
|
21
22
|
* one at a time.
|
|
22
23
|
*
|
|
23
24
|
* QueryCursors execute the model's pre `find` hooks before loading any documents
|
|
24
|
-
* from MongoDB, and the model's post `find` hooks
|
|
25
|
+
* from MongoDB, and the model's post `find` hooks once per document,
|
|
26
|
+
* with an array containing that document.
|
|
25
27
|
*
|
|
26
28
|
* Unless you're an advanced user, do **not** instantiate this class directly.
|
|
27
29
|
* Use [`Query#cursor()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.cursor()) instead.
|
|
@@ -50,6 +52,7 @@ function QueryCursor(query) {
|
|
|
50
52
|
this._transforms = [];
|
|
51
53
|
this.model = model;
|
|
52
54
|
this.options = {};
|
|
55
|
+
this._postMiddlewareFilter = null;
|
|
53
56
|
|
|
54
57
|
const onPreComplete = (err) => {
|
|
55
58
|
if (err != null) {
|
|
@@ -74,7 +77,15 @@ function QueryCursor(query) {
|
|
|
74
77
|
this.listeners('error').length > 0 && this.emit('error', err);
|
|
75
78
|
return;
|
|
76
79
|
}
|
|
77
|
-
|
|
80
|
+
const { middleware, ...options } = query._optionsForExec();
|
|
81
|
+
Object.assign(this.options, options);
|
|
82
|
+
// `middleware` is a Mongoose-only option used to skip user hooks; strip it
|
|
83
|
+
// so it isn't passed through to the MongoDB driver. `this.options` is public
|
|
84
|
+
// and may be modified while pre hooks run, so check the target after assign.
|
|
85
|
+
if (Object.hasOwn(this.options, 'middleware')) {
|
|
86
|
+
delete this.options.middleware;
|
|
87
|
+
}
|
|
88
|
+
this._postMiddlewareFilter = buildMiddlewareFilter(query.options, 'post');
|
|
78
89
|
this._transforms = this._transforms.concat(query._transforms.slice());
|
|
79
90
|
if (this.options.transform) {
|
|
80
91
|
this._transforms.push(this.options.transform);
|
|
@@ -98,7 +109,8 @@ function QueryCursor(query) {
|
|
|
98
109
|
}
|
|
99
110
|
};
|
|
100
111
|
|
|
101
|
-
|
|
112
|
+
const preFilter = buildMiddlewareFilter(query.options, 'pre');
|
|
113
|
+
model.hooks.execPre('find', query, [], { filter: preFilter }).then(() => onPreComplete(null), err => onPreComplete(err));
|
|
102
114
|
}
|
|
103
115
|
|
|
104
116
|
util.inherits(QueryCursor, Readable);
|
|
@@ -607,7 +619,7 @@ function _populateBatch() {
|
|
|
607
619
|
|
|
608
620
|
function _nextDoc(ctx, doc, pop, callback) {
|
|
609
621
|
if (ctx.query._mongooseOptions.lean) {
|
|
610
|
-
return ctx.model.hooks.execPost('find', ctx.query, [[doc]]).then(() => callback(null, doc), err => callback(err));
|
|
622
|
+
return ctx.model.hooks.execPost('find', ctx.query, [[doc]], { filter: ctx._postMiddlewareFilter }).then(() => callback(null, doc), err => callback(err));
|
|
611
623
|
}
|
|
612
624
|
|
|
613
625
|
const { model, _fields, _userProvidedFields, options } = ctx.query;
|
|
@@ -618,7 +630,7 @@ function _nextDoc(ctx, doc, pop, callback) {
|
|
|
618
630
|
if (options.session != null) {
|
|
619
631
|
doc.$session(options.session);
|
|
620
632
|
}
|
|
621
|
-
ctx.model.hooks.execPost('find', ctx.query, [[doc]]).then(() => callback(null, doc), err => callback(err));
|
|
633
|
+
ctx.model.hooks.execPost('find', ctx.query, [[doc]], { filter: ctx._postMiddlewareFilter }).then(() => callback(null, doc), err => callback(err));
|
|
622
634
|
});
|
|
623
635
|
}
|
|
624
636
|
|
package/lib/document.js
CHANGED
|
@@ -28,6 +28,7 @@ const compile = require('./helpers/document/compile').compile;
|
|
|
28
28
|
const defineKey = require('./helpers/document/compile').defineKey;
|
|
29
29
|
const firstKey = require('./helpers/firstKey');
|
|
30
30
|
const flatten = require('./helpers/common').flatten;
|
|
31
|
+
const getConstructorName = require('./helpers/getConstructorName');
|
|
31
32
|
const getEmbeddedDiscriminatorPath = require('./helpers/document/getEmbeddedDiscriminatorPath');
|
|
32
33
|
const getKeysInSchemaOrder = require('./helpers/schema/getKeysInSchemaOrder');
|
|
33
34
|
const getSubdocumentStrictValue = require('./helpers/schema/getSubdocumentStrictValue');
|
|
@@ -3589,6 +3590,15 @@ Document.prototype.invalidate = function(path, err, val, kind) {
|
|
|
3589
3590
|
return this.$__.validationError;
|
|
3590
3591
|
}
|
|
3591
3592
|
|
|
3593
|
+
// Set the model on cast errors with a custom message so `{MODEL}` gets
|
|
3594
|
+
// replaced for document validation, not just query casting (gh-8300)
|
|
3595
|
+
if (typeof err.setModel === 'function') {
|
|
3596
|
+
const owner = this.ownerDocument();
|
|
3597
|
+
if (getConstructorName(owner) === 'model') {
|
|
3598
|
+
err.setModel(owner.constructor);
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3592
3602
|
this.$__.validationError.addError(path, err);
|
|
3593
3603
|
return this.$__.validationError;
|
|
3594
3604
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const isPOJO = require('./isPOJO');
|
|
3
4
|
const symbols = require('../schema/symbols');
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -15,10 +16,27 @@ const isBuiltInMiddleware = hook => hook.fn[symbols.builtInMiddleware];
|
|
|
15
16
|
* @returns {Function|null} - null runs all middleware, isBuiltInMiddleware skips user middleware
|
|
16
17
|
*/
|
|
17
18
|
function buildMiddlewareFilter(options, phase) {
|
|
18
|
-
const
|
|
19
|
-
return
|
|
19
|
+
const shouldSkip = options?.middleware === false || options?.middleware?.[phase] === false;
|
|
20
|
+
return shouldSkip ? isBuiltInMiddleware : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* kareem `getOptions` implementation for custom statics and methods: reads the
|
|
25
|
+
* `middleware` option from the call's last argument if it is a plain object.
|
|
26
|
+
*
|
|
27
|
+
* @param {Array} args - Arguments the custom static or method was called with
|
|
28
|
+
* @returns {object} - Per-call kareem options with pre/post filters
|
|
29
|
+
*/
|
|
30
|
+
function middlewareFiltersFromLastArg(args) {
|
|
31
|
+
const lastArg = args[args.length - 1];
|
|
32
|
+
const options = isPOJO(lastArg) ? lastArg : null;
|
|
33
|
+
return {
|
|
34
|
+
pre: { filter: buildMiddlewareFilter(options, 'pre') },
|
|
35
|
+
post: { filter: buildMiddlewareFilter(options, 'post') }
|
|
36
|
+
};
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
module.exports = {
|
|
23
|
-
buildMiddlewareFilter
|
|
40
|
+
buildMiddlewareFilter,
|
|
41
|
+
middlewareFiltersFromLastArg
|
|
24
42
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { buildMiddlewareFilter } = require('../buildMiddlewareFilter');
|
|
3
|
+
const { buildMiddlewareFilter, middlewareFiltersFromLastArg } = require('../buildMiddlewareFilter');
|
|
4
4
|
|
|
5
5
|
/*!
|
|
6
6
|
* ignore
|
|
@@ -118,8 +118,15 @@ function applyHooks(model, schema, options) {
|
|
|
118
118
|
}
|
|
119
119
|
const originalMethod = objToDecorate[method];
|
|
120
120
|
objToDecorate[`$__${method}`] = objToDecorate[method];
|
|
121
|
+
// Only read the `middleware` option from the method's last argument if
|
|
122
|
+
// the method opts in via `supportsMiddlewareOption`. Custom methods have
|
|
123
|
+
// arbitrary signatures, so the last argument may be an object with an
|
|
124
|
+
// unrelated `middleware` property.
|
|
125
|
+
const methodOptions = originalMethod.supportsMiddlewareOption === true ?
|
|
126
|
+
{ ...customMethodOptions, getOptions: middlewareFiltersFromLastArg } :
|
|
127
|
+
customMethodOptions;
|
|
121
128
|
objToDecorate[method] = middleware.
|
|
122
|
-
createWrapper(method, originalMethod, null,
|
|
129
|
+
createWrapper(method, originalMethod, null, methodOptions);
|
|
123
130
|
}
|
|
124
131
|
}
|
|
125
132
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { middlewareFiltersFromLastArg } = require('../buildMiddlewareFilter');
|
|
3
4
|
const { queryMiddlewareFunctions, aggregateMiddlewareFunctions, modelMiddlewareFunctions, documentMiddlewareFunctions } = require('../../constants');
|
|
4
5
|
|
|
5
6
|
const middlewareFunctions = Array.from(
|
|
@@ -27,7 +28,14 @@ module.exports = function applyStaticHooks(model, hooks, statics) {
|
|
|
27
28
|
if (hooks.hasHooks(key)) {
|
|
28
29
|
const original = model[key];
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
// Only read the `middleware` option from the static's last argument if
|
|
32
|
+
// the static opts in via `supportsMiddlewareOption`. Custom statics have
|
|
33
|
+
// arbitrary signatures, so the last argument may be an object with an
|
|
34
|
+
// unrelated `middleware` property.
|
|
35
|
+
const wrapperOptions = original.supportsMiddlewareOption === true ?
|
|
36
|
+
{ getOptions: middlewareFiltersFromLastArg } :
|
|
37
|
+
undefined;
|
|
38
|
+
model[key] = hooks.createWrapper(key, original, null, wrapperOptions);
|
|
31
39
|
}
|
|
32
40
|
}
|
|
33
41
|
};
|
|
@@ -7,19 +7,9 @@
|
|
|
7
7
|
module.exports = function isPathSelectedInclusive(fields, path) {
|
|
8
8
|
const chunks = path.split('.');
|
|
9
9
|
let cur = '';
|
|
10
|
-
let j;
|
|
11
|
-
let keys;
|
|
12
|
-
let numKeys;
|
|
13
10
|
for (let i = 0; i < chunks.length; ++i) {
|
|
14
|
-
cur += cur.length ? '.'
|
|
11
|
+
cur += cur.length ? '.' + chunks[i] : chunks[i];
|
|
15
12
|
if (fields[cur]) {
|
|
16
|
-
keys = Object.keys(fields);
|
|
17
|
-
numKeys = keys.length;
|
|
18
|
-
for (j = 0; j < numKeys; ++j) {
|
|
19
|
-
if (keys[i].indexOf(cur + '.') === 0 && keys[i].indexOf(path) !== 0) {
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
13
|
return true;
|
|
24
14
|
}
|
|
25
15
|
}
|
package/lib/model.js
CHANGED
|
@@ -1229,8 +1229,8 @@ Model.createCollection = async function createCollection(options) {
|
|
|
1229
1229
|
|
|
1230
1230
|
// Remove middleware option before passing to MongoDB
|
|
1231
1231
|
if (options?.middleware != null) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1232
|
+
const { middleware, ...driverOptions } = options;
|
|
1233
|
+
options = driverOptions;
|
|
1234
1234
|
}
|
|
1235
1235
|
|
|
1236
1236
|
[options] = await this.hooks.execPre('createCollection', this, [options], { filter: preFilter }).catch(err => {
|
|
@@ -1431,7 +1431,7 @@ Model.dropSearchIndex = async function dropSearchIndex(name) {
|
|
|
1431
1431
|
* const Customer = mongoose.model('Customer', schema);
|
|
1432
1432
|
*
|
|
1433
1433
|
* await Customer.createSearchIndex({ name: 'test', definition: { mappings: { dynamic: true } } });
|
|
1434
|
-
* const res = await Customer.listSearchIndexes(); // Includes `[{ name: 'test' }]`
|
|
1434
|
+
* const res = await Customer.listSearchIndexes(); // Includes `[{ id: '...', name: 'test', status: 'READY', queryable: true, latestDefinition: { ... } }]`
|
|
1435
1435
|
*
|
|
1436
1436
|
* @param {object} [options]
|
|
1437
1437
|
* @return {Promise<Array>}
|
|
@@ -2134,6 +2134,48 @@ Model.find = function find(conditions, projection, options) {
|
|
|
2134
2134
|
return mq.find(conditions);
|
|
2135
2135
|
};
|
|
2136
2136
|
|
|
2137
|
+
/**
|
|
2138
|
+
* Finds documents and counts the number of documents matching the filter.
|
|
2139
|
+
*
|
|
2140
|
+
* This function fires both `find` and `countDocuments` middleware.
|
|
2141
|
+
*
|
|
2142
|
+
* #### Example:
|
|
2143
|
+
*
|
|
2144
|
+
* const [adventures, total] = await Adventure.findAndCount(
|
|
2145
|
+
* { type: 'jungle' },
|
|
2146
|
+
* null,
|
|
2147
|
+
* { sort: { name: 1 }, skip: 10, limit: 10 }
|
|
2148
|
+
* );
|
|
2149
|
+
*
|
|
2150
|
+
* adventures; // Array of at most 10 documents matching type 'jungle'
|
|
2151
|
+
* total; // Number of documents matching type 'jungle'
|
|
2152
|
+
*
|
|
2153
|
+
* @param {object|ObjectId} filter
|
|
2154
|
+
* @param {object|string|string[]} [projection] optional fields to return
|
|
2155
|
+
* @param {object} [options] optional query options
|
|
2156
|
+
* @return {Promise<Array>} a promise that resolves to `[documents, total]`
|
|
2157
|
+
* @api public
|
|
2158
|
+
*/
|
|
2159
|
+
|
|
2160
|
+
Model.findAndCount = async function findAndCount(conditions, projection, options) {
|
|
2161
|
+
_checkContext(this, 'findAndCount');
|
|
2162
|
+
|
|
2163
|
+
if (options == null) {
|
|
2164
|
+
throw new MongooseError('Model.findAndCount() requires an `options` argument with `sort` and `limit`');
|
|
2165
|
+
}
|
|
2166
|
+
if (options.sort == null) {
|
|
2167
|
+
throw new MongooseError('Model.findAndCount() requires `options.sort`');
|
|
2168
|
+
}
|
|
2169
|
+
if (options.limit == null) {
|
|
2170
|
+
throw new MongooseError('Model.findAndCount() requires `options.limit`');
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
const findQuery = this.find(conditions, projection, options);
|
|
2174
|
+
const countQuery = findQuery.clone().countDocuments().skip(0).limit(null);
|
|
2175
|
+
|
|
2176
|
+
return Promise.all([findQuery.exec(), countQuery.exec()]);
|
|
2177
|
+
};
|
|
2178
|
+
|
|
2137
2179
|
/**
|
|
2138
2180
|
* Finds a single document by its _id field. `findById(id)` is equivalent to `findOne({ _id: id })`.
|
|
2139
2181
|
*
|
|
@@ -4597,8 +4639,8 @@ async function _populatePath(model, docs, populateOptions) {
|
|
|
4597
4639
|
select = select.filter(field => field !== '-_id');
|
|
4598
4640
|
} else {
|
|
4599
4641
|
// preserve original select conditions by copying
|
|
4600
|
-
|
|
4601
|
-
|
|
4642
|
+
const { _id, ...selectWithoutId } = select;
|
|
4643
|
+
select = selectWithoutId;
|
|
4602
4644
|
}
|
|
4603
4645
|
}
|
|
4604
4646
|
|
package/lib/query.js
CHANGED
|
@@ -5329,7 +5329,8 @@ Query.prototype._applyPaths = function applyPaths() {
|
|
|
5329
5329
|
* Returns a wrapper around a [mongodb driver cursor](https://mongodb.github.io/node-mongodb-native/7.0/classes/FindCursor.html).
|
|
5330
5330
|
* A QueryCursor exposes a Streams3 interface, as well as a `.next()` function.
|
|
5331
5331
|
*
|
|
5332
|
-
* The `.cursor()` function triggers pre find hooks
|
|
5332
|
+
* The `.cursor()` function triggers pre find hooks before opening the cursor
|
|
5333
|
+
* and post find hooks once per document, with an array containing that document.
|
|
5333
5334
|
*
|
|
5334
5335
|
* #### Example:
|
|
5335
5336
|
*
|
|
@@ -5358,9 +5359,14 @@ Query.prototype._applyPaths = function applyPaths() {
|
|
|
5358
5359
|
* #### Valid options
|
|
5359
5360
|
*
|
|
5360
5361
|
* - `transform`: optional function which accepts a mongoose document. The return value of the function will be emitted on `data` and returned by `.next()`.
|
|
5362
|
+
* - [`middleware`](https://mongoosejs.com/docs/middleware.html#skipping): set to `false` to skip all user-defined middleware, or `{ pre: false }` / `{ post: false }` to skip only pre or post find hooks
|
|
5361
5363
|
*
|
|
5362
5364
|
* @return {QueryCursor}
|
|
5363
5365
|
* @param {object} [options]
|
|
5366
|
+
* @param {Function} [options.transform] optional function which accepts a mongoose document. The return value of the function will be emitted on `data` and returned by `.next()`.
|
|
5367
|
+
* @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware
|
|
5368
|
+
* @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
|
|
5369
|
+
* @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks
|
|
5364
5370
|
* @see QueryCursor https://mongoosejs.com/docs/api/querycursor.html
|
|
5365
5371
|
* @api public
|
|
5366
5372
|
*/
|
package/lib/schema/array.js
CHANGED
|
@@ -400,7 +400,15 @@ SchemaArray.prototype.cast = function(value, doc, init, prev, options) {
|
|
|
400
400
|
if (options.virtuals) {
|
|
401
401
|
opts.virtuals = options.virtuals;
|
|
402
402
|
}
|
|
403
|
-
|
|
403
|
+
if (rawValue[i] === undefined && caster.defaultValue !== undefined) {
|
|
404
|
+
// No need to add _skipMarkModified to opts here (fallback for `getDefault()` opts parameter)
|
|
405
|
+
// because array setters don't rely on Document.prototype.set() - subdocuments do rely on
|
|
406
|
+
// Document.prototype.set() when applying setters, which is why _skipMarkModified is necessary
|
|
407
|
+
// for subdocuments. But here we know that `caster` is an array not a subdocument.
|
|
408
|
+
rawValue[i] = caster.getDefault(doc, init, null, opts);
|
|
409
|
+
} else {
|
|
410
|
+
rawValue[i] = caster.applySetters(rawValue[i], doc, init, void 0, opts);
|
|
411
|
+
}
|
|
404
412
|
}
|
|
405
413
|
} catch (e) {
|
|
406
414
|
// rethrow
|
|
@@ -466,8 +466,8 @@ SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) {
|
|
|
466
466
|
// Don't pass `path` to $init - it's only for this DocumentArray itself, not its element fields.
|
|
467
467
|
// Element subdocuments use relative paths internally for change tracking.
|
|
468
468
|
if (options.path != null) {
|
|
469
|
-
|
|
470
|
-
|
|
469
|
+
const { path, ...initOptions } = options;
|
|
470
|
+
options = initOptions;
|
|
471
471
|
}
|
|
472
472
|
rawArray[i] = subdoc.$init(rawArray[i], options);
|
|
473
473
|
} else {
|
|
@@ -206,8 +206,8 @@ SchemaSubdocument.prototype.cast = function(val, doc, init, priorVal, options) {
|
|
|
206
206
|
// For change tracking, subdocuments use relative paths internally.
|
|
207
207
|
// Here, `options.path` contains the absolute path and is only used by the subdocument constructor, not by $init.
|
|
208
208
|
if (options.path != null) {
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
const { path, ...initOptions } = options;
|
|
210
|
+
options = initOptions;
|
|
211
211
|
}
|
|
212
212
|
subdoc.$init(val, options);
|
|
213
213
|
const exclude = isExclusive(selected);
|
package/lib/schema.js
CHANGED
|
@@ -2280,6 +2280,37 @@ Schema.prototype.plugin = function(fn, opts) {
|
|
|
2280
2280
|
return this;
|
|
2281
2281
|
};
|
|
2282
2282
|
|
|
2283
|
+
/**
|
|
2284
|
+
* Adds a query helper to this schema. Equivalent to `schema.query[name] = fn`.
|
|
2285
|
+
*
|
|
2286
|
+
* #### Example:
|
|
2287
|
+
*
|
|
2288
|
+
* schema.queryHelper('byName', function(name) {
|
|
2289
|
+
* return this.where({ name });
|
|
2290
|
+
* });
|
|
2291
|
+
* const Test = mongoose.model('Test', schema);
|
|
2292
|
+
* await Test.find().byName('John'); // Equivalent to `Test.find({ name: 'John' })`
|
|
2293
|
+
*
|
|
2294
|
+
* @param {string} name The query helper name.
|
|
2295
|
+
* @param {Function} fn The query helper function.
|
|
2296
|
+
* @return {Schema} this
|
|
2297
|
+
* @api public
|
|
2298
|
+
*/
|
|
2299
|
+
|
|
2300
|
+
Schema.prototype.queryHelper = function queryHelper(name, fn) {
|
|
2301
|
+
if (typeof name !== 'string') {
|
|
2302
|
+
throw new MongooseError('First param to `schema.queryHelper()` must be a string, ' +
|
|
2303
|
+
'got "' + (typeof name) + '"');
|
|
2304
|
+
}
|
|
2305
|
+
if (typeof fn !== 'function') {
|
|
2306
|
+
throw new MongooseError('Second param to `schema.queryHelper()` must be a function, ' +
|
|
2307
|
+
'got "' + (typeof fn) + '"');
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
this.query[name] = fn;
|
|
2311
|
+
return this;
|
|
2312
|
+
};
|
|
2313
|
+
|
|
2283
2314
|
/**
|
|
2284
2315
|
* Adds an instance method to documents constructed from Models compiled from this schema.
|
|
2285
2316
|
*
|
|
@@ -2310,9 +2341,21 @@ Schema.prototype.plugin = function(fn, opts) {
|
|
|
2310
2341
|
*
|
|
2311
2342
|
* NOTE: `Schema.method()` adds instance methods to the `Schema.methods` object. You can also add instance methods directly to the `Schema.methods` object as seen in the [guide](https://mongoosejs.com/docs/guide.html#methods)
|
|
2312
2343
|
*
|
|
2344
|
+
* If the method has hooks registered via `schema.pre('meow')` or `schema.post('meow')`, you can allow callers
|
|
2345
|
+
* to skip them for a single call by setting `supportsMiddlewareOption = true` on the function:
|
|
2346
|
+
*
|
|
2347
|
+
* schema.method('meow', function(options = {}) {
|
|
2348
|
+
* console.log('meeeeeoooooooooooow');
|
|
2349
|
+
* });
|
|
2350
|
+
* schema.methods.meow.supportsMiddlewareOption = true;
|
|
2351
|
+
*
|
|
2352
|
+
* // Skips `pre('meow')` and `post('meow')` hooks
|
|
2353
|
+
* await fizz.meow({ middleware: false });
|
|
2354
|
+
*
|
|
2313
2355
|
* @param {string|object} name The Method Name for a single function, or an Object of "string-function" pairs.
|
|
2314
2356
|
* @param {Function} [fn] The Function in a single-function definition.
|
|
2315
2357
|
* @api public
|
|
2358
|
+
* @see Skip Middleware https://mongoosejs.com/docs/middleware.html#skip-custom-statics-and-methods
|
|
2316
2359
|
*/
|
|
2317
2360
|
|
|
2318
2361
|
Schema.prototype.method = function(name, fn, options) {
|
|
@@ -2355,10 +2398,22 @@ Schema.prototype.method = function(name, fn, options) {
|
|
|
2355
2398
|
*
|
|
2356
2399
|
* If a hash of name/fn pairs is passed as the only argument, each name/fn pair will be added as statics.
|
|
2357
2400
|
*
|
|
2401
|
+
* If the static has hooks registered via `schema.pre('findByName')` or `schema.post('findByName')`, you can allow
|
|
2402
|
+
* callers to skip them for a single call by setting `supportsMiddlewareOption = true` on the function:
|
|
2403
|
+
*
|
|
2404
|
+
* schema.static('findByName', function(name, options = {}) {
|
|
2405
|
+
* return this.find({ name: name });
|
|
2406
|
+
* });
|
|
2407
|
+
* schema.statics.findByName.supportsMiddlewareOption = true;
|
|
2408
|
+
*
|
|
2409
|
+
* // Skips `pre('findByName')` and `post('findByName')` hooks
|
|
2410
|
+
* await Drink.findByName('LaCroix', { middleware: false });
|
|
2411
|
+
*
|
|
2358
2412
|
* @param {string|object} name The Method Name for a single function, or an Object of "string-function" pairs.
|
|
2359
2413
|
* @param {Function} [fn] The Function in a single-function definition.
|
|
2360
2414
|
* @api public
|
|
2361
2415
|
* @see Statics https://mongoosejs.com/docs/guide.html#statics
|
|
2416
|
+
* @see Skip Middleware https://mongoosejs.com/docs/middleware.html#skip-custom-statics-and-methods
|
|
2362
2417
|
*/
|
|
2363
2418
|
|
|
2364
2419
|
Schema.prototype.static = function(name, fn) {
|
package/lib/schemaType.js
CHANGED
|
@@ -176,8 +176,7 @@ SchemaType.prototype.path;
|
|
|
176
176
|
*/
|
|
177
177
|
|
|
178
178
|
SchemaType.prototype.toJSON = function toJSON() {
|
|
179
|
-
const res =
|
|
180
|
-
delete res.parentSchema;
|
|
179
|
+
const { parentSchema, ...res } = this;
|
|
181
180
|
return res;
|
|
182
181
|
};
|
|
183
182
|
|
|
@@ -1299,11 +1298,12 @@ SchemaType.prototype.ref = function(ref) {
|
|
|
1299
1298
|
* @param {object} options
|
|
1300
1299
|
* @param {object} [options.context]
|
|
1301
1300
|
* @param {boolean} [options.skipCast]
|
|
1301
|
+
* @param {object} [applySettersOptions] passed to applySetters
|
|
1302
1302
|
* @return {any} The Stored default value.
|
|
1303
1303
|
* @api private
|
|
1304
1304
|
*/
|
|
1305
1305
|
|
|
1306
|
-
SchemaType.prototype.getDefault = function getDefault(parentDoc, init, options) {
|
|
1306
|
+
SchemaType.prototype.getDefault = function getDefault(parentDoc, init, options, applySettersOptions) {
|
|
1307
1307
|
let ret;
|
|
1308
1308
|
if (this.defaultValue == null) {
|
|
1309
1309
|
return this.defaultValue;
|
|
@@ -1337,7 +1337,13 @@ SchemaType.prototype.getDefault = function getDefault(parentDoc, init, options)
|
|
|
1337
1337
|
return this._applySetters(ret, parentDoc);
|
|
1338
1338
|
}
|
|
1339
1339
|
|
|
1340
|
-
const casted = this.applySetters(
|
|
1340
|
+
const casted = this.applySetters(
|
|
1341
|
+
ret,
|
|
1342
|
+
parentDoc,
|
|
1343
|
+
init,
|
|
1344
|
+
undefined,
|
|
1345
|
+
applySettersOptions ?? setOptionsForDefaults
|
|
1346
|
+
);
|
|
1341
1347
|
if (casted && !Array.isArray(casted) && casted.$isSingleNested) {
|
|
1342
1348
|
casted.$__parent = parentDoc;
|
|
1343
1349
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mongoose",
|
|
3
3
|
"description": "Mongoose MongoDB ODM",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.10.0",
|
|
5
5
|
"author": "Guillermo Rauch <guillermo@learnboost.com>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mongodb",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@standard-schema/spec": "^1.1.0",
|
|
24
|
-
"kareem": "3.
|
|
25
|
-
"mongodb": "~7.
|
|
24
|
+
"kareem": "3.4.0",
|
|
25
|
+
"mongodb": "~7.6",
|
|
26
26
|
"mpath": "0.9.0",
|
|
27
27
|
"mquery": "6.0.0",
|
|
28
28
|
"ms": "2.1.3",
|
|
@@ -40,20 +40,20 @@
|
|
|
40
40
|
"c8": "12.0.0",
|
|
41
41
|
"cheerio": "1.2.0",
|
|
42
42
|
"dox": "1.0.0",
|
|
43
|
-
"eslint": "10.
|
|
43
|
+
"eslint": "10.9.1",
|
|
44
44
|
"eslint-plugin-mocha-no-only": "1.2.0",
|
|
45
45
|
"express": "5.2.1",
|
|
46
46
|
"fs-extra": "~11.4.0",
|
|
47
47
|
"glob": "^13.0.6",
|
|
48
48
|
"globals": "^17.4.0",
|
|
49
|
-
"highlight.js": "11.
|
|
49
|
+
"highlight.js": "11.12.0",
|
|
50
50
|
"linkinator": "8.x",
|
|
51
51
|
"lodash.isequal": "4.5.0",
|
|
52
52
|
"lodash.isequalwith": "4.4.0",
|
|
53
53
|
"markdownlint-cli2": "0.23.2",
|
|
54
|
-
"marked": "18.0.
|
|
54
|
+
"marked": "18.0.11",
|
|
55
55
|
"mkdirp": "^3.0.1",
|
|
56
|
-
"mocha": "12.0.0-rc.
|
|
56
|
+
"mocha": "12.0.0-rc.6",
|
|
57
57
|
"moment": "2.30.1",
|
|
58
58
|
"mongodb-client-encryption": "^7.2.0",
|
|
59
59
|
"mongodb-memory-server": "11.2.0",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"tstyche": "^7.0.0",
|
|
65
65
|
"typescript": "5.9.3",
|
|
66
66
|
"typescript-eslint": "^8.31.1",
|
|
67
|
-
"uuid": "14.0.
|
|
67
|
+
"uuid": "14.0.2",
|
|
68
68
|
"xss": "1.0.15"
|
|
69
69
|
},
|
|
70
70
|
"directories": {
|