mongoose 9.10.4 → 9.11.1

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.
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const isPOJO = require('../isPOJO');
4
+
5
+ /**
6
+ * Returns the single discriminator value that a query filter condition selects,
7
+ * like `'Clicked'` for `'Clicked'`, `{ $eq: 'Clicked' }`, or `{ $in: ['Clicked'] }`.
8
+ * Returns the condition unchanged if it does not select exactly one value.
9
+ *
10
+ * @param {any} value the filter condition on the discriminator key
11
+ * @return {any}
12
+ * @api private
13
+ */
14
+
15
+ module.exports = function getDiscriminatorValueFromFilter(value) {
16
+ if (!isPOJO(value)) {
17
+ return value;
18
+ }
19
+ const keys = Object.keys(value);
20
+ if (keys.length !== 1) {
21
+ return value;
22
+ }
23
+ if (keys[0] === '$eq') {
24
+ return value.$eq;
25
+ }
26
+ if (keys[0] === '$in' && Array.isArray(value.$in) && value.$in.length === 1) {
27
+ return value.$in[0];
28
+ }
29
+ return value;
30
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const isPOJO = require('../isPOJO');
4
+
5
+ /**
6
+ * Convert nested path documents underneath POJOs and arrays to POJOs without
7
+ * modifying the original value. Only clone containers that contain a nested
8
+ * path document. Preserve regular document children so casting can handle
9
+ * populated documents and subdocuments.
10
+ */
11
+
12
+ module.exports = function toObjectRecursive(value, options) {
13
+ return _toObjectRecursive(value, options, true, new Set());
14
+ };
15
+
16
+ function _toObjectRecursive(value, options, isRoot, seen) {
17
+ if (value == null) {
18
+ return value;
19
+ }
20
+
21
+ if (value.$__ != null && typeof value.toObject === 'function') {
22
+ return isRoot || value.$__isNested ? value.toObject(options) : value;
23
+ }
24
+
25
+ if (!Array.isArray(value) && !isPOJO(value)) {
26
+ return value;
27
+ }
28
+
29
+ if (seen.has(value)) {
30
+ return value;
31
+ }
32
+ seen.add(value);
33
+
34
+ let ret = value;
35
+ for (const key of Object.keys(value)) {
36
+ const converted = _toObjectRecursive(value[key], options, false, seen);
37
+ if (converted === value[key]) {
38
+ continue;
39
+ }
40
+
41
+ if (ret === value) {
42
+ ret = Array.isArray(value) ? Object.assign([], value) : { ...value };
43
+ }
44
+ ret[key] = converted;
45
+ }
46
+
47
+ seen.delete(value);
48
+ return ret;
49
+ }
@@ -3,6 +3,7 @@
3
3
  const cleanPositionalOperators = require('../schema/cleanPositionalOperators');
4
4
  const get = require('../get');
5
5
  const getDiscriminatorByValue = require('../discriminator/getDiscriminatorByValue');
6
+ const getDiscriminatorValueFromFilter = require('../discriminator/getDiscriminatorValueFromFilter');
6
7
  const updatedPathsByArrayFilter = require('../update/updatedPathsByArrayFilter');
7
8
 
8
9
  /**
@@ -46,16 +47,16 @@ module.exports = function getEmbeddedDiscriminatorPath(schema, update, filter, p
46
47
  let discriminatorKey = null;
47
48
 
48
49
  if (discriminatorValuePath in filter) {
49
- discriminatorKey = filter[discriminatorValuePath];
50
+ discriminatorKey = getDiscriminatorValueFromFilter(filter[discriminatorValuePath]);
50
51
  }
51
52
  if (discriminatorFilterPath in filter) {
52
- discriminatorKey = filter[discriminatorFilterPath];
53
+ discriminatorKey = getDiscriminatorValueFromFilter(filter[discriminatorFilterPath]);
53
54
  }
54
55
 
55
56
  const wrapperPath = subpath.replace(/\.\d+$/, '');
56
57
  if (schematype.$isMongooseDocumentArrayElement &&
57
58
  get(filter[wrapperPath], '$elemMatch.' + key) != null) {
58
- discriminatorKey = filter[wrapperPath].$elemMatch[key];
59
+ discriminatorKey = getDiscriminatorValueFromFilter(filter[wrapperPath].$elemMatch[key]);
59
60
  }
60
61
 
61
62
  const discriminatorKeyUpdatePath = originalSubpath + '.' + key;
@@ -92,7 +92,7 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt
92
92
 
93
93
  if (schematype == null) {
94
94
  if (!strictQuery) {
95
- return;
95
+ continue;
96
96
  }
97
97
  const filterPath = filterPathRelativeToBase == null ?
98
98
  baseFilterPath + '.0' :
package/lib/query.js CHANGED
@@ -2563,6 +2563,35 @@ Query.prototype.find = function(conditions) {
2563
2563
  return this;
2564
2564
  };
2565
2565
 
2566
+ /**
2567
+ * Executes the query as a `find()` and also runs a `countDocuments()` query
2568
+ * on the same filter, returning `[documents, count]`. The count ignores
2569
+ * `skip` and `limit` so that it returns the total number of matching documents.
2570
+ *
2571
+ * #### Example:
2572
+ *
2573
+ * const query = Adventure.find({ type: 'animal' }).sort({ name: 1 }).limit(10);
2574
+ * const [adventures, total] = await query.findAndCount();
2575
+ *
2576
+ * @return {Promise<Array>} a promise that resolves to `[documents, total]`
2577
+ * @api public
2578
+ */
2579
+
2580
+ Query.prototype.findAndCount = async function findAndCount() {
2581
+ if (this.options.sort == null) {
2582
+ throw new MongooseError('Query.prototype.findAndCount() requires sort. Use `sort()` to set a sort order before calling `findAndCount()`.');
2583
+ }
2584
+ if (this.options.limit == null) {
2585
+ throw new MongooseError('Query.prototype.findAndCount() requires limit. Use `limit()` to set a limit before calling `findAndCount()`.');
2586
+ }
2587
+
2588
+ this.op = 'find';
2589
+
2590
+ const countQuery = this.clone().countDocuments().skip(0).limit(null);
2591
+
2592
+ return Promise.all([this.exec(), countQuery.exec()]);
2593
+ };
2594
+
2566
2595
  /**
2567
2596
  * Merges another Query or conditions object into this one.
2568
2597
  *
@@ -611,6 +611,19 @@ function cast$elemMatch(val, context) {
611
611
  for (let i = 0; i < numKeys; ++i) {
612
612
  const key = keys[i];
613
613
  const value = val[key];
614
+ if (key === '$not') {
615
+ // `$not` wraps other operators, like `{ $not: { $gt: 5 } }` or
616
+ // `{ $not: { $elemMatch: ... } }`, so cast each wrapped operator.
617
+ // A RegExp is left as-is.
618
+ if (utils.isPOJO(value)) {
619
+ for (const notKey of Object.keys(value)) {
620
+ if (isOperator(notKey) && value[notKey] != null) {
621
+ value[notKey] = this.castForQuery(notKey, value[notKey], context);
622
+ }
623
+ }
624
+ }
625
+ continue;
626
+ }
614
627
  if (isOperator(key) && value != null) {
615
628
  val[key] = this.castForQuery(key, value, context);
616
629
  }
@@ -65,7 +65,6 @@ function SchemaDocumentArray(key, schema, options, schemaOptions, parentSchema)
65
65
  $parentSchemaType,
66
66
  Constructor
67
67
  });
68
-
69
68
  SchemaArray.call(this, key, embeddedSchemaType, options, null, parentSchema);
70
69
 
71
70
  this.schema = schema;
@@ -234,8 +233,16 @@ SchemaDocumentArray.prototype.doValidate = async function doValidate(array, scop
234
233
  // lazy load
235
234
  MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentArray'));
236
235
 
237
- await SchemaType.prototype.doValidate.call(this, array, scope);
236
+ let schemaTypeError;
237
+ try {
238
+ await SchemaType.prototype.doValidate.call(this, array, scope);
239
+ } catch (err) {
240
+ schemaTypeError = err;
241
+ }
238
242
  if (options?.updateValidator) {
243
+ if (schemaTypeError != null) {
244
+ throw schemaTypeError;
245
+ }
239
246
  return;
240
247
  }
241
248
  if (!utils.isMongooseDocumentArray(array)) {
@@ -258,14 +265,29 @@ SchemaDocumentArray.prototype.doValidate = async function doValidate(array, scop
258
265
  doc = array[i] = new Constructor(doc, array, undefined, undefined, i);
259
266
  }
260
267
 
261
- if (options?.validateModifiedOnly && !doc.$isModified()) {
268
+ if (options?.validateModifiedOnly && !doc.$isNew && !doc.$isModified()) {
262
269
  continue;
263
270
  }
264
271
 
265
- promises.push(doc.validate(null, options));
272
+ const subdocOptions = doc.$isNew && options?.validateModifiedOnly ?
273
+ { ...options, validateModifiedOnly: false } :
274
+ options;
275
+ promises.push(doc.validate(null, subdocOptions));
266
276
  }
267
277
 
268
- await Promise.all(promises);
278
+ let subdocumentError;
279
+ try {
280
+ await Promise.all(promises);
281
+ } catch (err) {
282
+ subdocumentError = err;
283
+ }
284
+
285
+ if (schemaTypeError != null) {
286
+ throw schemaTypeError;
287
+ }
288
+ if (subdocumentError != null) {
289
+ throw subdocumentError;
290
+ }
269
291
  };
270
292
 
271
293
  /**
@@ -281,15 +303,11 @@ SchemaDocumentArray.prototype.doValidate = async function doValidate(array, scop
281
303
 
282
304
  SchemaDocumentArray.prototype.doValidateSync = function(array, scope, options) {
283
305
  const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, array, scope);
284
- if (schemaTypeError != null) {
285
- return schemaTypeError;
286
- }
287
-
288
306
  const count = array?.length;
289
- let resultError = null;
307
+ let resultError = schemaTypeError;
290
308
 
291
309
  if (!count) {
292
- return;
310
+ return resultError;
293
311
  }
294
312
 
295
313
  // handle sparse arrays, do not use array.forEach which does not
@@ -310,11 +328,14 @@ SchemaDocumentArray.prototype.doValidateSync = function(array, scope, options) {
310
328
  doc = array[i] = new Constructor(doc, array, undefined, undefined, i);
311
329
  }
312
330
 
313
- if (options?.validateModifiedOnly && !doc.$isModified()) {
331
+ if (options?.validateModifiedOnly && !doc.$isNew && !doc.$isModified()) {
314
332
  continue;
315
333
  }
316
334
 
317
- const subdocValidateError = doc.$__validateSync(options);
335
+ const subdocOptions = doc.$isNew && options?.validateModifiedOnly ?
336
+ { ...options, validateModifiedOnly: false } :
337
+ options;
338
+ const subdocValidateError = doc.$__validateSync(subdocOptions);
318
339
 
319
340
  if (subdocValidateError && resultError == null) {
320
341
  resultError = subdocValidateError;
@@ -463,10 +484,10 @@ SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) {
463
484
  }
464
485
 
465
486
  subdoc = new Constructor(null, value, initDocumentOptions, selected, i);
466
- // Don't pass `path` to $init - it's only for this DocumentArray itself, not its element fields.
467
- // Element subdocuments use relative paths internally for change tracking.
468
- if (options.path != null) {
469
- const { path, ...initOptions } = options;
487
+ // Don't pass `path` or `arrayPathIndex` to $init - they're only for this DocumentArray itself,
488
+ // not its element fields. Element subdocuments use relative paths internally for change tracking.
489
+ if (options.path != null || options.arrayPathIndex != null) {
490
+ const { path, arrayPathIndex, ...initOptions } = options;
470
491
  options = initOptions;
471
492
  }
472
493
  rawArray[i] = subdoc.$init(rawArray[i], options);
@@ -108,9 +108,41 @@ SchemaDocumentArrayElement.prototype.doValidate = async function doValidate(valu
108
108
  value = new Constructor(value, scope, null, null, options?.index ?? null);
109
109
  }
110
110
 
111
+ // The parent document array validates each of its subdocuments, so when it
112
+ // already did, this element's own validators are all that is left to run.
113
+ if (options?.skipSubdocumentValidation) {
114
+ if (options.skipSchemaValidators) {
115
+ return;
116
+ }
117
+ return SchemaType.prototype.doValidate.call(this, value, scope, options);
118
+ }
119
+
111
120
  return SchemaSubdocument.prototype.doValidate.call(this, value, scope, options);
112
121
  };
113
122
 
123
+ /**
124
+ * Synchronous validation on this individual array element
125
+ *
126
+ * @api private
127
+ */
128
+
129
+ SchemaDocumentArrayElement.prototype.doValidateSync = function doValidateSync(value, scope, options) {
130
+ const Constructor = getConstructor(this.Constructor, value);
131
+
132
+ if (value && !(value instanceof Constructor)) {
133
+ value = new Constructor(value, scope, null, null, options?.index ?? null);
134
+ }
135
+
136
+ if (options?.skipSubdocumentValidation) {
137
+ if (options.skipSchemaValidators) {
138
+ return;
139
+ }
140
+ return SchemaType.prototype.doValidateSync.call(this, value, scope, options);
141
+ }
142
+
143
+ return SchemaSubdocument.prototype.doValidateSync.call(this, value, scope, options);
144
+ };
145
+
114
146
  /**
115
147
  * Clone the current SchemaType
116
148
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "9.10.4",
4
+ "version": "9.11.1",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "@standard-schema/spec": "^1.1.0",
24
24
  "kareem": "3.4.0",
25
- "mongodb": "~7.6",
25
+ "mongodb": "~7.7",
26
26
  "mpath": "0.9.0",
27
27
  "mquery": "6.0.0",
28
28
  "ms": "2.1.3",
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://tstyche.org/schemas/config.json",
3
+ "checkDeclarationFiles": false,
4
+ "checkSuppressedErrors": false,
5
+ "testFileMatch": [
6
+ "test/types/*.test.*",
7
+ "test/types/**/*.test.*"
8
+ ]
9
+ }
@@ -203,7 +203,7 @@ declare module 'mongoose' {
203
203
  ObtainSchemaGeneric<TSchema, 'TSchemaOptions'>
204
204
  >,
205
205
  TSchema,
206
- ObtainSchemaGeneric<TSchema, 'TLeanResultType'>
206
+ Default__v<Require_id<ObtainSchemaGeneric<TSchema, 'TLeanResultType'>>>
207
207
  > & ObtainSchemaGeneric<TSchema, 'TStaticMethods'>;
208
208
  model<T, U, TQueryHelpers = {}>(
209
209
  name: string,
@@ -141,7 +141,9 @@ declare module 'mongoose' {
141
141
  this,
142
142
  TQueryHelpers,
143
143
  DocType,
144
- 'deleteOne'
144
+ 'deleteOne',
145
+ Record<string, never>,
146
+ Default__v<Require_id<DocType>>
145
147
  >;
146
148
 
147
149
  /**
package/types/index.d.ts CHANGED
@@ -104,7 +104,7 @@ declare module 'mongoose' {
104
104
  ObtainSchemaGeneric<TSchema, 'TSchemaOptions'>
105
105
  >,
106
106
  TSchema,
107
- ObtainSchemaGeneric<TSchema, 'TLeanResultType'>
107
+ Default__v<Require_id<ObtainSchemaGeneric<TSchema, 'TLeanResultType'>>>
108
108
  > & ObtainSchemaGeneric<TSchema, 'TStaticMethods'>;
109
109
 
110
110
  export function model<T>(name: string, schema?: Schema<T, any, any> | Schema<T & Document, any, any>, collection?: string, options?: CompileModelOptions): Model<T>;
@@ -738,7 +738,7 @@ declare module 'mongoose' {
738
738
  static ObjectId: typeof Schema.Types.ObjectId;
739
739
  }
740
740
 
741
- export type NumberSchemaDefinition = typeof Number | 'number' | 'Number' | typeof Schema.Types.Number | Schema.Types.Number;
741
+ export type NumberSchemaDefinition = typeof Number | 'number' | 'Number' | typeof Schema.Types.Number | Schema.Types.Number | 'int32' | 'Int32' | typeof Schema.Types.Int32 | Schema.Types.Int32;
742
742
  export type StringSchemaDefinition = typeof String | 'string' | 'String' | typeof Schema.Types.String | Schema.Types.String;
743
743
  export type BooleanSchemaDefinition = typeof Boolean | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean | Schema.Types.Boolean;
744
744
  export type DateSchemaDefinition = DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date | Schema.Types.Date;