mongoose 9.11.0 → 9.11.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/aggregate.js CHANGED
@@ -1174,7 +1174,7 @@ Aggregate.prototype.then = function(resolve, reject) {
1174
1174
  /**
1175
1175
  * Executes the aggregation returning a `Promise` which will be
1176
1176
  * resolved with either the doc(s) or rejected with the error.
1177
- * Like [`.then()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.then), but only takes a rejection handler.
1177
+ * Like [`.then()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.then()), but only takes a rejection handler.
1178
1178
  * Compatible with `await`.
1179
1179
  *
1180
1180
  * @param {Function} [reject]
package/lib/cast.js CHANGED
@@ -11,6 +11,7 @@ const cast$expr = require('./helpers/query/cast$expr');
11
11
  const castString = require('./cast/string');
12
12
  const castTextSearch = require('./schema/operators/text');
13
13
  const get = require('./helpers/get');
14
+ const getDiscriminatorValueFromFilter = require('./helpers/discriminator/getDiscriminatorValueFromFilter');
14
15
  const getSchemaDiscriminatorByValue = require('./helpers/discriminator/getSchemaDiscriminatorByValue');
15
16
  const isOperator = require('./helpers/query/isOperator');
16
17
  const util = require('util');
@@ -43,7 +44,8 @@ module.exports = function cast(schema, obj, options, context) {
43
44
  }
44
45
 
45
46
  if (schema?.discriminators != null && obj[schema.options.discriminatorKey] != null) {
46
- schema = getSchemaDiscriminatorByValue(schema, obj[schema.options.discriminatorKey]) || schema;
47
+ const discriminatorValue = getDiscriminatorValueFromFilter(obj[schema.options.discriminatorKey]);
48
+ schema = getSchemaDiscriminatorByValue(schema, discriminatorValue) || schema;
47
49
  }
48
50
 
49
51
  const paths = Object.keys(obj);
@@ -134,18 +136,10 @@ module.exports = function cast(schema, obj, options, context) {
134
136
  if (_schematype?.schema?.discriminators != null &&
135
137
  discriminatorKey != null &&
136
138
  pathLastHalf !== discriminatorKey) {
137
- const discriminatorVal = get(obj, pathFirstHalf + '.' + discriminatorKey);
139
+ const discriminatorVal = getDiscriminatorValueFromFilter(get(obj, pathFirstHalf + '.' + discriminatorKey));
138
140
  const discriminators = _schematype.schema.discriminators;
139
141
  if (typeof discriminatorVal === 'string' && discriminators[discriminatorVal] != null) {
140
-
141
142
  schematype = discriminators[discriminatorVal].path(pathLastHalf);
142
- } else if (discriminatorVal != null &&
143
- Object.keys(discriminatorVal).length === 1 &&
144
- Array.isArray(discriminatorVal.$in) &&
145
- discriminatorVal.$in.length === 1 &&
146
- typeof discriminatorVal.$in[0] === 'string' &&
147
- discriminators[discriminatorVal.$in[0]] != null) {
148
- schematype = discriminators[discriminatorVal.$in[0]].path(pathLastHalf);
149
143
  }
150
144
  }
151
145
  }
@@ -329,15 +323,11 @@ module.exports = function cast(schema, obj, options, context) {
329
323
  if (nested && schematype?.schema != null) {
330
324
  cast(schematype.schema, nested, options, context);
331
325
  } else if (nested && schematype?.$isMongooseArray) {
332
- if (utils.isPOJO(nested) && nested.$not != null) {
333
- cast(schema, nested, options, context);
334
- } else {
335
- val[$cond] = schematype.castForQuery(
336
- $cond,
337
- nested,
338
- context
339
- );
340
- }
326
+ val[$cond] = schematype.castForQuery(
327
+ $cond,
328
+ nested,
329
+ context
330
+ );
341
331
  }
342
332
  } else if ($cond === '$not') {
343
333
  if (nested && schematype) {
@@ -170,6 +170,9 @@ QueryCursor.prototype.getDriverCursor = async function getDriverCursor() {
170
170
  if (this.cursor) {
171
171
  return this.cursor;
172
172
  }
173
+ if (this._error) {
174
+ throw this._error;
175
+ }
173
176
 
174
177
  await once(this, 'cursor');
175
178
  return this.cursor;
@@ -278,7 +281,7 @@ QueryCursor.prototype._destroy = function _destroy(_err, callback) {
278
281
 
279
282
  waitForCursor
280
283
  .then(() => {
281
- this.cursor.close();
284
+ return this.cursor.close();
282
285
  })
283
286
  .then(() => {
284
287
  this._closed = true;
package/lib/document.js CHANGED
@@ -63,6 +63,7 @@ const scopeSymbol = require('./helpers/symbols').scopeSymbol;
63
63
  const schemaMixedSymbol = require('./schema/symbols').schemaMixedSymbol;
64
64
  const getDeepestSubdocumentForPath = require('./helpers/document/getDeepestSubdocumentForPath');
65
65
  const sessionNewDocuments = require('./helpers/symbols').sessionNewDocuments;
66
+ const toObjectRecursive = require('./helpers/document/toObjectRecursive');
66
67
 
67
68
  let DocumentArray;
68
69
  let Embedded;
@@ -75,7 +76,7 @@ const VERSION_ALL = VERSION_WHERE | VERSION_INC;
75
76
 
76
77
  /**
77
78
  * The core Mongoose document constructor. You should not call this directly,
78
- * the Mongoose [Model constructor](./api/model.html#Model) calls this for you.
79
+ * the Mongoose [Model constructor](./api/model.html#Model()) calls this for you.
79
80
  *
80
81
  * @param {object} obj the values to set
81
82
  * @param {object} [fields] optional object containing the fields which were selected in the query returning this document and any populated paths data
@@ -878,9 +879,9 @@ function init(self, obj, doc, opts, prefix) {
878
879
  *
879
880
  * #### Valid options:
880
881
  *
881
- * - same as in [Model.updateOne](https://mongoosejs.com/docs/api/model.html#Model.updateOne)
882
+ * - same as in [Model.updateOne](https://mongoosejs.com/docs/api/model.html#Model.updateOne())
882
883
  *
883
- * @see Model.updateOne https://mongoosejs.com/docs/api/model.html#Model.updateOne
884
+ * @see Model.updateOne https://mongoosejs.com/docs/api/model.html#Model.updateOne()
884
885
  * @param {object} update
885
886
  * @param {object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
886
887
  * @param {object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and the [Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
@@ -1253,9 +1254,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
1253
1254
 
1254
1255
  if (pathType === 'nested' && val) {
1255
1256
  if (typeof val === 'object' && val != null) {
1256
- if (val.$__ != null) {
1257
- val = val.toObject(internalToObjectOptions);
1258
- }
1257
+ val = toObjectRecursive(val, internalToObjectOptions);
1259
1258
  if (val == null) {
1260
1259
  this.invalidate(path, new MongooseError.CastError('Object', val, path));
1261
1260
  return this;
@@ -3792,7 +3791,7 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
3792
3791
  }
3793
3792
 
3794
3793
  /**
3795
- * Saves this document by inserting a new document into the database if [document.isNew](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()) is `true`,
3794
+ * Saves this document by inserting a new document into the database if [document.isNew](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) is `true`,
3796
3795
  * or sends an [updateOne](https://mongoosejs.com/docs/api/document.html#Document.prototype.updateOne()) operation **only** with the modifications to the database, it does not replace the whole document in the latter case.
3797
3796
  *
3798
3797
  * #### Example:
@@ -3825,7 +3824,7 @@ function _checkImmutableSubpaths(subdoc, schematype, priorVal) {
3825
3824
  * @method save
3826
3825
  * @memberOf Document
3827
3826
  * @instance
3828
- * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
3827
+ * @throws {DocumentNotFoundError} if this [save updates an existing document](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew) but the document doesn't exist in the database. For example, you will get this error if the document is [deleted between when you retrieved the document and when you saved it](documents.html#updating).
3829
3828
  * @return {Promise}
3830
3829
  * @api public
3831
3830
  * @see middleware https://mongoosejs.com/docs/middleware.html
@@ -4072,7 +4071,7 @@ Document.prototype.$__setSchema = function(schema) {
4072
4071
  this.schema = schema;
4073
4072
  }
4074
4073
  this.$__schema = schema;
4075
- this.$__middleware = schema._getDocumentMiddleware();
4074
+ this.$__middleware = schema._getDocumentMiddleware(this.$isSubdocument);
4076
4075
  this[documentSchemaSymbol] = schema;
4077
4076
  };
4078
4077
 
@@ -4265,9 +4264,11 @@ Document.prototype.$toObject = function(options, json) {
4265
4264
  ?? false;
4266
4265
 
4267
4266
  let ret;
4268
- if (hasOnlyPrimitiveValues && !options.flattenObjectIds) {
4267
+ if (hasOnlyPrimitiveValues && !options.flattenObjectIds && !options.flattenDecimals) {
4269
4268
  // Fast path: if we don't have any nested objects or arrays, we only need a
4270
- // shallow clone.
4269
+ // shallow clone. Skipped for `flattenObjectIds` and `flattenDecimals`, because
4270
+ // `$__hasOnlyPrimitiveValues()` counts ObjectIds and Decimal128s as primitives
4271
+ // and only the full `clone()` path converts them.
4271
4272
  ret = this.$__toObjectShallow(schemaFieldsOnly);
4272
4273
  } else if (schemaFieldsOnly) {
4273
4274
  ret = {};
@@ -26,14 +26,14 @@ const MongooseError = require('./mongooseError');
26
26
  * - `MongooseError`: general Mongoose error
27
27
  * - `CastError`: Mongoose could not convert a value to the type defined in the schema path. May be in a `ValidationError` class' `errors` property.
28
28
  * - `DivergentArrayError`: You attempted to `save()` an array that was modified after you loaded it with a `$elemMatch` or similar projection
29
- * - `MissingSchemaError`: You tried to access a model with [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.model()) that was not defined
29
+ * - `MissingSchemaError`: You tried to access a model with [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) that was not defined
30
30
  * - `DocumentNotFoundError`: The document you tried to [`save()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.save()) was not found
31
31
  * - `ValidatorError`: error from an individual schema path's validator
32
32
  * - `ValidationError`: error returned from [`validate()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.validate()) or [`validateSync()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.validateSync()). Contains zero or more `ValidatorError` instances in `.errors` property.
33
33
  * - `MissingSchemaError`: You called `mongoose.Document()` without a schema
34
34
  * - `ObjectExpectedError`: Thrown when you set a nested path to a non-object value with [strict mode set](https://mongoosejs.com/docs/guide.html#strict).
35
35
  * - `ObjectParameterError`: Thrown when you pass a non-object value to a function which expects an object as a parameter
36
- * - `OverwriteModelError`: Thrown when you call [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.model()) to re-define a model that was already defined.
36
+ * - `OverwriteModelError`: Thrown when you call [`mongoose.model()`](https://mongoosejs.com/docs/api/mongoose.html#Mongoose.prototype.model()) to re-define a model that was already defined.
37
37
  * - `ParallelSaveError`: Thrown when you call [`save()`](https://mongoosejs.com/docs/api/model.html#Model.prototype.save()) on a document when the same document instance is already saving.
38
38
  * - `StrictModeError`: Thrown when you set a path that isn't the schema and [strict mode](https://mongoosejs.com/docs/guide.html#strict) is set to `throw`.
39
39
  * - `VersionError`: Thrown when the [document is out of sync](https://mongoosejs.com/docs/guide.html#versionKey)
@@ -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
+ }
@@ -81,21 +81,30 @@ function applyHooks(model, schema, options) {
81
81
  // promises and make it so that `doc.save.toString()` provides meaningful
82
82
  // information.
83
83
 
84
- const middleware = schema._getDocumentMiddleware();
84
+ const middleware = schema._getDocumentMiddleware(model.prototype.$isSubdocument);
85
85
 
86
86
  model._middleware = middleware;
87
87
 
88
- objToDecorate.$__init = middleware.
89
- createWrapperSync('init', objToDecorate.$__init, null, {
90
- ...kareemOptions,
91
- getOptions: (args) => {
92
- const opts = args[1];
93
- return {
94
- pre: { filter: buildMiddlewareFilter(opts, 'pre') },
95
- post: { filter: buildMiddlewareFilter(opts, 'post') }
96
- };
97
- }
98
- });
88
+ // Most subdocuments have no init hooks. Avoid wrapping every hydration in
89
+ // Kareem when there is no middleware to run.
90
+ const $__init = objToDecorate.$__init;
91
+ if (middleware.hasHooks('init')) {
92
+ objToDecorate.$__init = middleware.
93
+ createWrapperSync('init', $__init, null, {
94
+ ...kareemOptions,
95
+ getOptions: (args) => {
96
+ const opts = args[1];
97
+ return {
98
+ pre: { filter: buildMiddlewareFilter(opts, 'pre') },
99
+ post: { filter: buildMiddlewareFilter(opts, 'post') }
100
+ };
101
+ }
102
+ });
103
+ } else {
104
+ // Still define an own `$__init` so top-level discriminators, whose prototype is later set to
105
+ // the base model's prototype, don't inherit the base model's wrapped `$__init`.
106
+ objToDecorate.$__init = $__init;
107
+ }
99
108
 
100
109
  // Support hooks for custom methods
101
110
  const customMethods = Object.keys(schema.methods);
@@ -6,6 +6,7 @@ const applyTimestampsToChildren = require('../update/applyTimestampsToChildren')
6
6
  const applyTimestampsToUpdate = require('../update/applyTimestampsToUpdate');
7
7
  const cast = require('../../cast');
8
8
  const castUpdate = require('../query/castUpdate');
9
+ const { castArrayFiltersForUpdate } = require('../update/castArrayFilters');
9
10
  const clone = require('../clone');
10
11
  const decorateUpdateWithVersionKey = require('../update/decorateUpdateWithVersionKey');
11
12
  const { inspect } = require('util');
@@ -153,6 +154,10 @@ module.exports.castUpdateOne = function castUpdateOne(originalModel, updateOne,
153
154
  strict: strict,
154
155
  upsert: updateOne.upsert
155
156
  });
157
+ if (Array.isArray(updateOne.arrayFilters)) {
158
+ updateOne.arrayFilters = clone(updateOne.arrayFilters);
159
+ castArrayFiltersForUpdate(updateOne.arrayFilters, update, schema, model, options);
160
+ }
156
161
  updateOne['update'] = castUpdate(model.schema, update, {
157
162
  strict: strict,
158
163
  upsert: updateOne.upsert,
@@ -214,6 +219,10 @@ module.exports.castUpdateMany = function castUpdateMany(originalModel, updateMan
214
219
  strict: strict,
215
220
  upsert: updateMany.upsert
216
221
  });
222
+ if (Array.isArray(updateMany.arrayFilters)) {
223
+ updateMany.arrayFilters = clone(updateMany.arrayFilters);
224
+ castArrayFiltersForUpdate(updateMany.arrayFilters, update, schema, model, options);
225
+ }
217
226
 
218
227
  updateMany['update'] = castUpdate(model.schema, update, {
219
228
  strict: strict,
@@ -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;
@@ -7,30 +7,53 @@ const updatedPathsByArrayFilter = require('./updatedPathsByArrayFilter');
7
7
  const utils = require('../../utils');
8
8
 
9
9
  module.exports = function castArrayFilters(query) {
10
- const arrayFilters = query.options.arrayFilters;
10
+ castArrayFiltersForUpdate(
11
+ query.options.arrayFilters,
12
+ query.getUpdate(),
13
+ query.schema,
14
+ query.model,
15
+ query._mongooseOptions,
16
+ query
17
+ );
18
+ };
19
+
20
+ /**
21
+ * Casts `arrayFilters` in place against the schema paths that `update` modifies.
22
+ * Shared by queries and `bulkWrite()`.
23
+ *
24
+ * @param {Array} arrayFilters
25
+ * @param {object} update the update that the array filters apply to, before casting
26
+ * @param {Schema} schema
27
+ * @param {Model} [model]
28
+ * @param {object} options the `strict` and `strictQuery` options of the query or bulk write
29
+ * @param {object} [context] passed to `castForQuery()`
30
+ * @api private
31
+ */
32
+
33
+ function castArrayFiltersForUpdate(arrayFilters, update, schema, model, options, context) {
11
34
  if (!Array.isArray(arrayFilters)) {
12
35
  return;
13
36
  }
14
- const update = query.getUpdate();
15
- const schema = query.schema;
16
37
  const updatedPathsByFilter = updatedPathsByArrayFilter(update);
17
38
 
18
39
  let strictQuery = schema.options.strict;
19
- if (query._mongooseOptions.strict != null) {
20
- strictQuery = query._mongooseOptions.strict;
40
+ if (options.strict != null) {
41
+ strictQuery = options.strict;
21
42
  }
22
- if (query.model?.base.options.strictQuery != null) {
23
- strictQuery = query.model.base.options.strictQuery;
43
+ if (model?.base.options.strictQuery != null) {
44
+ strictQuery = model.base.options.strictQuery;
24
45
  }
25
46
  if (schema._userProvidedOptions.strictQuery != null) {
26
47
  strictQuery = schema._userProvidedOptions.strictQuery;
27
48
  }
28
- if (query._mongooseOptions.strictQuery != null) {
29
- strictQuery = query._mongooseOptions.strictQuery;
49
+ if (options.strictQuery != null) {
50
+ strictQuery = options.strictQuery;
30
51
  }
31
52
 
32
- _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilter, query);
33
- };
53
+ _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilter, context);
54
+ }
55
+
56
+ module.exports.castArrayFiltersForUpdate = castArrayFiltersForUpdate;
34
57
 
35
58
  function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilter, query) {
36
59
  // Map to store discriminator values for embedded documents in the array filters.
@@ -92,7 +115,7 @@ function _castArrayFilters(arrayFilters, schema, strictQuery, updatedPathsByFilt
92
115
 
93
116
  if (schematype == null) {
94
117
  if (!strictQuery) {
95
- return;
118
+ continue;
96
119
  }
97
120
  const filterPath = filterPathRelativeToBase == null ?
98
121
  baseFilterPath + '.0' :
package/lib/model.js CHANGED
@@ -615,7 +615,7 @@ function generateVersionError(doc, modifiedPaths, defaultPaths) {
615
615
  * newProduct === product; // true
616
616
  *
617
617
  * @param {object} [options] options optional options
618
- * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.session()).
618
+ * @param {Session} [options.session=null] the [session](https://www.mongodb.com/docs/manual/reference/server-sessions/) associated with this save operation. If not specified, defaults to the [document's associated session](https://mongoosejs.com/docs/api/document.html#Document.prototype.$session()).
619
619
  * @param {object} [options.safe] (DEPRECATED) overrides [schema's safe option](https://mongoosejs.com/docs/guide.html#safe). Use the `w` option instead.
620
620
  * @param {boolean} [options.validateBeforeSave] set to false to save without validating.
621
621
  * @param {boolean} [options.validateModifiedOnly=false] if `true`, Mongoose will only validate modified paths, as opposed to modified paths and `required` paths.
@@ -2391,7 +2391,7 @@ Model.where = function where(path, val) {
2391
2391
  * @method $where
2392
2392
  * @memberOf Model
2393
2393
  * @return {Query}
2394
- * @see Query.$where https://mongoosejs.com/docs/api/query.html#Query.prototype.$where
2394
+ * @see Query.$where https://mongoosejs.com/docs/api/query.html#Query.prototype.$where()
2395
2395
  * @api public
2396
2396
  */
2397
2397
 
package/lib/mongoose.js CHANGED
@@ -240,7 +240,7 @@ Mongoose.prototype.setDriver = function setDriver(driver) {
240
240
  * - `selectPopulatedPaths`: `true` by default. Set to false to opt out of Mongoose adding all fields that you `populate()` to your `select()`. The schema-level option `selectPopulatedPaths` overwrites this one.
241
241
  * - `strictQuery`: `false` by default. May be `false`, `true`, or `'throw'`. Sets the default [strictQuery](https://mongoosejs.com/docs/guide.html#strictQuery) mode for schemas.
242
242
  * - `strict`: `true` by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas.
243
- * - `timestamps.createdAt.immutable`: `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.immutable) which means you can update the `createdAt`
243
+ * - `timestamps.createdAt.immutable`: `true` by default. If `false`, it will change the `createdAt` field to be [`immutable: false`](https://mongoosejs.com/docs/api/schematype.html#SchemaType.prototype.immutable()) which means you can update the `createdAt`
244
244
  * - `toJSON`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toJSON()), for determining how Mongoose documents get serialized by `JSON.stringify()`
245
245
  * - `toObject`: `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](https://mongoosejs.com/docs/api/document.html#Document.prototype.toObject())
246
246
  * - `transactionAsyncLocalStorage`: `false` by default. If `true`, Mongoose will automatically pass the `session` to any operation executing within a transaction executor function. See [transaction AsyncLocalStorage documentation](https://mongoosejs.com/docs/transactions.html#asynclocalstorage)
@@ -1067,7 +1067,7 @@ Mongoose.prototype.Query = Query;
1067
1067
  Mongoose.prototype.Model = Model;
1068
1068
 
1069
1069
  /**
1070
- * The Mongoose [Document](https://mongoosejs.com/docs/api/document.html#Document()) constructor.
1070
+ * The Mongoose [Document](https://mongoosejs.com/docs/api/document.html) constructor.
1071
1071
  *
1072
1072
  * @method Document
1073
1073
  * @api public
@@ -92,6 +92,10 @@ function saveSubdocsPostDeleteOne() {
92
92
 
93
93
 
94
94
  saveSubdocsPreSave[symbols.builtInMiddleware] = true;
95
+ saveSubdocsPreSave[symbols.skipOnSubdocuments] = true;
95
96
  saveSubdocsPostSave[symbols.builtInMiddleware] = true;
97
+ saveSubdocsPostSave[symbols.skipOnSubdocuments] = true;
96
98
  saveSubdocsPreDeleteOne[symbols.builtInMiddleware] = true;
99
+ saveSubdocsPreDeleteOne[symbols.skipOnSubdocuments] = true;
97
100
  saveSubdocsPostDeleteOne[symbols.builtInMiddleware] = true;
101
+ saveSubdocsPostDeleteOne[symbols.skipOnSubdocuments] = true;
@@ -95,7 +95,17 @@ function storeShard() {
95
95
  }
96
96
 
97
97
  shardingPluginPostInit[symbols.builtInMiddleware] = true;
98
+ shardingPluginPostInit[symbols.skipOnSubdocuments] = true;
99
+ shardingPluginPostInit[symbols.requiresShardKey] = true;
98
100
  shardingPluginPreSave[symbols.builtInMiddleware] = true;
101
+ shardingPluginPreSave[symbols.skipOnSubdocuments] = true;
102
+ shardingPluginPreSave[symbols.requiresShardKey] = true;
99
103
  shardingPluginPostSave[symbols.builtInMiddleware] = true;
104
+ shardingPluginPostSave[symbols.skipOnSubdocuments] = true;
105
+ shardingPluginPostSave[symbols.requiresShardKey] = true;
100
106
  shardingPluginPreDeleteOne[symbols.builtInMiddleware] = true;
107
+ shardingPluginPreDeleteOne[symbols.skipOnSubdocuments] = true;
108
+ shardingPluginPreDeleteOne[symbols.requiresShardKey] = true;
101
109
  shardingPluginPreUpdateOne[symbols.builtInMiddleware] = true;
110
+ shardingPluginPreUpdateOne[symbols.skipOnSubdocuments] = true;
111
+ shardingPluginPreUpdateOne[symbols.requiresShardKey] = true;
package/lib/query.js CHANGED
@@ -893,7 +893,7 @@ Query.prototype.mod = function() {
893
893
  *
894
894
  * #### Note:
895
895
  *
896
- * As of Mongoose 3.7, `$geoWithin` is always used for queries. To change this behavior, see [Query.use$geoWithin](https://mongoosejs.com/docs/api/query.html#Query.prototype.use$geoWithin).
896
+ * As of Mongoose 3.7, `$geoWithin` is always used for queries. To change this behavior, see [Query.use$geoWithin](https://mongoosejs.com/docs/api/query.html#Query.use$geoWithin).
897
897
  *
898
898
  * #### Note:
899
899
  *
@@ -4232,7 +4232,7 @@ Query.prototype.validate = async function validate(castedDoc, options, isOverwri
4232
4232
  /**
4233
4233
  * Execute an updateMany query
4234
4234
  *
4235
- * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
4235
+ * @see Model.updateMany https://mongoosejs.com/docs/api/model.html#Model.updateMany()
4236
4236
  * @method _updateMany
4237
4237
  * @memberOf Query
4238
4238
  * @instance
@@ -4278,7 +4278,7 @@ Query.prototype._updateMany = async function _updateMany() {
4278
4278
  /**
4279
4279
  * Execute an updateOne query
4280
4280
  *
4281
- * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
4281
+ * @see Model.updateOne https://mongoosejs.com/docs/api/model.html#Model.updateOne()
4282
4282
  * @method _updateOne
4283
4283
  * @memberOf Query
4284
4284
  * @instance
@@ -4389,7 +4389,7 @@ Query.prototype._replaceOne = async function _replaceOne() {
4389
4389
  * @param {boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
4390
4390
  * @param {boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
4391
4391
  * @return {Query} this
4392
- * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
4392
+ * @see Model.updateMany https://mongoosejs.com/docs/api/model.html#Model.updateMany()
4393
4393
  * @see Query docs https://mongoosejs.com/docs/queries.html
4394
4394
  * @see update https://www.mongodb.com/docs/manual/reference/method/db.collection.update/
4395
4395
  * @see UpdateResult https://mongodb.github.io/node-mongodb-native/7.0/interfaces/UpdateResult.html
@@ -4465,7 +4465,7 @@ Query.prototype.updateMany = function(conditions, doc, options, callback) {
4465
4465
  * @param {boolean} [options.overwriteImmutable=false] Mongoose removes updated immutable properties from `update` by default (excluding $setOnInsert). Set `overwriteImmutable` to `true` to allow updating immutable properties using other update operators.
4466
4466
  * @param {boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
4467
4467
  * @return {Query} this
4468
- * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
4468
+ * @see Model.updateOne https://mongoosejs.com/docs/api/model.html#Model.updateOne()
4469
4469
  * @see Query docs https://mongoosejs.com/docs/queries.html
4470
4470
  * @see update https://www.mongodb.com/docs/manual/reference/method/db.collection.update/
4471
4471
  * @see UpdateResult https://mongodb.github.io/node-mongodb-native/7.0/interfaces/UpdateResult.html
@@ -4533,7 +4533,7 @@ Query.prototype.updateOne = function(conditions, doc, options, callback) {
4533
4533
  * @param {boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
4534
4534
  * @param {boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
4535
4535
  * @return {Query} this
4536
- * @see Model.update https://mongoosejs.com/docs/api/model.html#Model.update()
4536
+ * @see Model.replaceOne https://mongoosejs.com/docs/api/model.html#Model.replaceOne()
4537
4537
  * @see Query docs https://mongoosejs.com/docs/queries.html
4538
4538
  * @see update https://www.mongodb.com/docs/manual/reference/method/db.collection.update/
4539
4539
  * @see UpdateResult https://mongodb.github.io/node-mongodb-native/7.0/interfaces/UpdateResult.html
@@ -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
  }
@@ -484,10 +484,10 @@ SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) {
484
484
  }
485
485
 
486
486
  subdoc = new Constructor(null, value, initDocumentOptions, selected, i);
487
- // Don't pass `path` to $init - it's only for this DocumentArray itself, not its element fields.
488
- // Element subdocuments use relative paths internally for change tracking.
489
- if (options.path != null) {
490
- 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;
491
491
  options = initOptions;
492
492
  }
493
493
  rawArray[i] = subdoc.$init(rawArray[i], options);
@@ -222,7 +222,9 @@ SchemaNumber.prototype.min = function(value, message) {
222
222
 
223
223
  if (value != null) {
224
224
  let msg = message || MongooseError.messages.Number.min;
225
- msg = msg.replace(/{MIN}/, value);
225
+ if (typeof msg === 'string') {
226
+ msg = msg.replace(/{MIN}/, value);
227
+ }
226
228
  this.validators.push({
227
229
  validator: this.minValidator = function(v) {
228
230
  return v == null || v >= value;
@@ -276,7 +278,9 @@ SchemaNumber.prototype.max = function(value, message) {
276
278
 
277
279
  if (value != null) {
278
280
  let msg = message || MongooseError.messages.Number.max;
279
- msg = msg.replace(/{MAX}/, value);
281
+ if (typeof msg === 'string') {
282
+ msg = msg.replace(/{MAX}/, value);
283
+ }
280
284
  this.validators.push({
281
285
  validator: this.maxValidator = function(v) {
282
286
  return v == null || v <= value;
@@ -417,7 +417,9 @@ SchemaString.prototype.minlength = function(value, message) {
417
417
 
418
418
  if (value != null) {
419
419
  let msg = message || MongooseError.messages.String.minlength;
420
- msg = msg.replace(/{MINLENGTH}/, value);
420
+ if (typeof msg === 'string') {
421
+ msg = msg.replace(/{MINLENGTH}/, value);
422
+ }
421
423
  this.validators.push({
422
424
  validator: this.minlengthValidator = function(v) {
423
425
  return v === null || v.length >= value;
@@ -3,3 +3,5 @@
3
3
  exports.schemaMixedSymbol = Symbol.for('mongoose:schema_mixed');
4
4
 
5
5
  exports.builtInMiddleware = Symbol.for('mongoose:built-in-middleware');
6
+ exports.skipOnSubdocuments = Symbol('mongoose:skip-on-subdocuments');
7
+ exports.requiresShardKey = Symbol.for('mongoose:requires-shard-key');
package/lib/schema.js CHANGED
@@ -700,9 +700,18 @@ Schema.prototype.discriminator = function(name, schema, options) {
700
700
  /*!
701
701
  * Get the document middleware for this schema, filtering out any hooks that are specific to queries.
702
702
  */
703
- Schema.prototype._getDocumentMiddleware = function _getDocumentMiddleware() {
703
+ Schema.prototype._getDocumentMiddleware = function _getDocumentMiddleware(isSubdocument) {
704
+ // Check `shardKey` at compile time rather than when the sharding plugin is applied, because
705
+ // discriminators apply plugins before inheriting the base schema's options.
706
+ const hasShardKey = !!(this.options.shardKey || this.options.shardkey);
704
707
  return this.s.hooks.
705
708
  filter(hook => {
709
+ if (isSubdocument && hook.fn[symbols.skipOnSubdocuments]) {
710
+ return false;
711
+ }
712
+ if (!hasShardKey && hook.fn[symbols.requiresShardKey]) {
713
+ return false;
714
+ }
706
715
  if (hook.name === 'updateOne' || hook.name === 'deleteOne') {
707
716
  return !!hook['document'];
708
717
  }
package/lib/schemaType.js CHANGED
@@ -645,7 +645,7 @@ SchemaType.prototype.sparse = function(bool) {
645
645
 
646
646
  /**
647
647
  * Defines this path as immutable. Mongoose prevents you from changing
648
- * immutable paths unless the parent document has [`isNew: true`](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()).
648
+ * immutable paths unless the parent document has [`isNew: true`](https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew).
649
649
  *
650
650
  * #### Example:
651
651
  *
@@ -682,7 +682,7 @@ SchemaType.prototype.sparse = function(bool) {
682
682
  *
683
683
  * @param {boolean} bool
684
684
  * @return {SchemaType} this
685
- * @see isNew https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew()
685
+ * @see isNew https://mongoosejs.com/docs/api/document.html#Document.prototype.isNew
686
686
  * @api public
687
687
  */
688
688
 
@@ -1131,7 +1131,7 @@ SchemaType.prototype.validate = function(obj, message, type) {
1131
1131
  * @see SchemaBoolean#checkRequired https://mongoosejs.com/docs/api/schemaboolean.html#SchemaBoolean.prototype.checkRequired()
1132
1132
  * @see SchemaBuffer#checkRequired https://mongoosejs.com/docs/api/schemabuffer.html#SchemaBuffer.prototype.checkRequired()
1133
1133
  * @see SchemaNumber#checkRequired https://mongoosejs.com/docs/api/schemanumber.html#SchemaNumber.prototype.checkRequired()
1134
- * @see SchemaObjectId#checkRequired https://mongoosejs.com/docs/api/schemaobjectid.html#ObjectId.prototype.checkRequired()
1134
+ * @see SchemaObjectId#checkRequired https://mongoosejs.com/docs/api/schemaobjectid.html#SchemaObjectId.prototype.checkRequired()
1135
1135
  * @see SchemaString#checkRequired https://mongoosejs.com/docs/api/schemastring.html#SchemaString.prototype.checkRequired()
1136
1136
  * @api public
1137
1137
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mongoose",
3
3
  "description": "Mongoose MongoDB ODM",
4
- "version": "9.11.0",
4
+ "version": "9.11.2",
5
5
  "author": "Guillermo Rauch <guillermo@learnboost.com>",
6
6
  "keywords": [
7
7
  "mongodb",
@@ -21,7 +21,7 @@
21
21
  "license": "MIT",
22
22
  "dependencies": {
23
23
  "@standard-schema/spec": "^1.1.0",
24
- "kareem": "3.4.0",
24
+ "kareem": "3.4.1",
25
25
  "mongodb": "~7.7",
26
26
  "mpath": "0.9.0",
27
27
  "mquery": "6.0.0",
package/types/index.d.ts CHANGED
@@ -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;
package/types/models.d.ts CHANGED
@@ -158,13 +158,19 @@ declare module 'mongoose' {
158
158
  MongooseBulkUpdatePerOperationOptions;
159
159
 
160
160
  export type UpdateOneModel<TSchema extends mongodb.Document = mongodb.Document> =
161
- Omit<mongodb.UpdateOneModel<TSchema>, 'filter'> &
162
- { filter: QueryFilter<TSchema> } &
161
+ Omit<mongodb.UpdateOneModel<TSchema>, 'filter' | 'update'> &
162
+ {
163
+ filter: QueryFilter<TSchema>;
164
+ update: UpdateQuery<TSchema> | UpdateWithAggregationPipeline;
165
+ } &
163
166
  MongooseBulkUpdatePerOperationOptions;
164
167
 
165
168
  export type UpdateManyModel<TSchema extends mongodb.Document = mongodb.Document> =
166
- Omit<mongodb.UpdateManyModel<TSchema>, 'filter'> &
167
- { filter: QueryFilter<TSchema> } &
169
+ Omit<mongodb.UpdateManyModel<TSchema>, 'filter' | 'update'> &
170
+ {
171
+ filter: QueryFilter<TSchema>;
172
+ update: UpdateQuery<TSchema> | UpdateWithAggregationPipeline;
173
+ } &
168
174
  MongooseBulkUpdatePerOperationOptions;
169
175
 
170
176
  export type DeleteOneModel<TSchema extends mongodb.Document = mongodb.Document> =
@@ -23,7 +23,9 @@ declare module 'mongoose' {
23
23
  ? { [K in keyof T]: PopulatePathToRawDocType<T[K]> }
24
24
  : T;
25
25
 
26
- type PopulatedPathsDocumentType<RawDocType, Paths> = UnpackedIntersection<RawDocType, PopulatePathToRawDocType<Paths>>;
26
+ type PopulatedPathsDocumentType<RawDocType, Paths> = Paths extends Record<string, never>
27
+ ? RawDocType
28
+ : UnpackedIntersection<RawDocType, PopulatePathToRawDocType<Paths>>;
27
29
 
28
30
  type PopulatedDocumentMarker<
29
31
  PopulatedRawDocType,
@@ -193,18 +193,18 @@ declare module 'mongoose' {
193
193
  min?:
194
194
  | number
195
195
  | NativeDate
196
- | [number, string]
196
+ | [number, string | ValidatorMessageFn]
197
197
  | [NativeDate, string]
198
- | readonly [number, string]
198
+ | readonly [number, string | ValidatorMessageFn]
199
199
  | readonly [NativeDate, string];
200
200
 
201
201
  /** The maximum value allowed for this path. Only allowed for numbers and dates. */
202
202
  max?:
203
203
  | number
204
204
  | NativeDate
205
- | [number, string]
205
+ | [number, string | ValidatorMessageFn]
206
206
  | [NativeDate, string]
207
- | readonly [number, string]
207
+ | readonly [number, string | ValidatorMessageFn]
208
208
  | readonly [NativeDate, string];
209
209
 
210
210
  /** Set to false to disable minimizing empty single nested subdocuments by default */
@@ -241,12 +241,12 @@ declare module 'mongoose' {
241
241
  uppercase?: boolean;
242
242
 
243
243
  /** If set, Mongoose will add a custom validator that ensures the given string's `length` is at least the given number. */
244
- minlength?: number | [number, string] | readonly [number, string];
245
- minLength?: number | [number, string] | readonly [number, string];
244
+ minlength?: number | [number, string | ValidatorMessageFn] | readonly [number, string | ValidatorMessageFn];
245
+ minLength?: number | [number, string | ValidatorMessageFn] | readonly [number, string | ValidatorMessageFn];
246
246
 
247
247
  /** If set, Mongoose will add a custom validator that ensures the given string's `length` is at most the given number. */
248
- maxlength?: number | [number, string] | readonly [number, string];
249
- maxLength?: number | [number, string] | readonly [number, string];
248
+ maxlength?: number | [number, string | ValidatorMessageFn] | readonly [number, string | ValidatorMessageFn];
249
+ maxLength?: number | [number, string | ValidatorMessageFn] | readonly [number, string | ValidatorMessageFn];
250
250
 
251
251
  [other: string]: any;
252
252
 
@@ -562,10 +562,10 @@ declare module 'mongoose' {
562
562
  enum(vals: number[]): this;
563
563
 
564
564
  /** Sets a maximum number validator. */
565
- max(value: number, message?: string): this;
565
+ max(value: number, message?: string | ValidatorMessageFn): this;
566
566
 
567
567
  /** Sets a minimum number validator. */
568
- min(value: number, message?: string): this;
568
+ min(value: number, message?: string | ValidatorMessageFn): this;
569
569
 
570
570
  /** Default options for this SchemaType */
571
571
  static defaultOptions: Record<string, any>;
@@ -632,10 +632,10 @@ declare module 'mongoose' {
632
632
  match(value: RegExp, message: string): this;
633
633
 
634
634
  /** Sets a maximum length validator. */
635
- maxlength(value: number, message: string): this;
635
+ maxlength(value: number, message: string | ValidatorMessageFn): this;
636
636
 
637
637
  /** Sets a minimum length validator. */
638
- minlength(value: number, message: string): this;
638
+ minlength(value: number, message: string | ValidatorMessageFn): this;
639
639
 
640
640
  /** Adds a trim [setter](http://mongoosejs.com/docs/api/schematype.html#schematype_SchemaType-set). */
641
641
  trim(shouldTrim?: boolean): this;
@@ -143,7 +143,13 @@ declare module 'mongoose' {
143
143
  ? T
144
144
  : T extends any ? (Omit<T, keyof U> & U) : never;
145
145
 
146
- type MergeType<A, B> = A extends unknown ? Omit<A, keyof B> & B : never;
146
+ type MergeTypeKeys<T> = T extends unknown ? keyof T : never;
147
+
148
+ type MergeType<A, B> = [B] extends [object]
149
+ ? [MergeTypeKeys<B>] extends [never]
150
+ ? A
151
+ : A extends unknown ? Omit<A, keyof B> & B : never
152
+ : A extends unknown ? Omit<A, keyof B> & B : never;
147
153
 
148
154
  /**
149
155
  * @summary Converts Unions to one record "object".