diff --git a/History.md b/History.md
index e3f40f5caf3..91130da0e10 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,26 @@
+5.11.15 / 2021-02-03
+====================
+ * fix(document): fix issues with `isSelected` as an path in a nested schema #9884 #9873 [IslandRhythms](https://github.com/IslandRhythms)
+ * fix(index.d.ts): better support for `SchemaDefinition` generics when creating schema #9863 #9862 #9789
+ * fix(index.d.ts): allow required function in array definition #9888 [Ugzuzg](https://github.com/Ugzuzg)
+ * fix(index.d.ts): reorder create typings to allow array desctructuring #9890 [Ugzuzg](https://github.com/Ugzuzg)
+ * fix(index.d.ts): add missing overload to Model.validate #9878 #9877 [jonamat](https://github.com/jonamat)
+ * fix(index.d.ts): throw compiler error if schema says path is a String, but interface says path is a number #9857
+ * fix(index.d.ts): make `Query` a class, allow calling `Query#where()` with object argument and with no arguments #9856
+ * fix(index.d.ts): support calling `Schema#pre()` and `Schema#post()` with options and array of hooked function names #9844
+ * docs(faq): mention other debug options than console #9887 [dandv](https://github.com/dandv)
+ * docs(connections): clarify that Mongoose can emit 'error' both during initial connection and after initial connection #9853
+
+5.11.14 / 2021-01-28
+====================
+ * fix(populate): avoid inferring `justOne` from parent when populating a POJO with a manually populated path #9833 [IslandRhythms](https://github.com/IslandRhythms)
+ * fix(document): apply setters on each element of the array when setting a populated array #9838
+ * fix(map): handle change tracking on maps of subdocs #9811 [IslandRhythms](https://github.com/IslandRhythms)
+ * fix(document): remove dependency on `documentIsSelected` symbol #9841 [IslandRhythms](https://github.com/IslandRhythms)
+ * fix(error): make ValidationError.toJSON to include the error name correctly #9849 [hanzki](https://github.com/hanzki)
+ * fix(index.d.ts): indicate that `Document#remove()` returns a promise, not a query #9826
+ * fix(index.d.ts): allow setting `SchemaType#enum` to TypeScript enum with `required: true` #9546
+
5.11.13 / 2021-01-20
====================
* fix(map): handle change tracking on map of arrays #9813
diff --git a/docs/connections.pug b/docs/connections.pug
index 6be93e02741..c2b92aa0714 100644
--- a/docs/connections.pug
+++ b/docs/connections.pug
@@ -118,7 +118,7 @@ block content
There are two classes of errors that can occur with a Mongoose connection.
- - Error on initial connection. If initial connection fails, Mongoose will **not** attempt to reconnect, it will emit an 'error' event, and the promise `mongoose.connect()` returns will reject.
+ - Error on initial connection. If initial connection fails, Mongoose will emit an 'error' event and the promise `mongoose.connect()` returns will reject. However, Mongoose will **not** automatically try to reconnect.
- Error after initial connection was established. Mongoose will attempt to reconnect, and it will emit an 'error' event.
To handle initial connection errors, you should use `.catch()` or `try/catch` with async/await.
@@ -145,9 +145,8 @@ block content
});
```
- Note that the `error` event in the code above does not fire when mongoose loses
- connection after the initial connection was established. You can listen to the
- `disconnected` event for that purpose.
+ Note that Mongoose does not necessarily emit an 'error' event if it loses connectivity to MongoDB. You should
+ listen to the `disconnected` event to report when Mongoose is disconnected from MongoDB.
diff --git a/docs/faq.pug b/docs/faq.pug
index 54a6636b21d..29ef6affbd2 100644
--- a/docs/faq.pug
+++ b/docs/faq.pug
@@ -293,9 +293,10 @@ block content
**Q**. How can I enable debugging?
- **A**. Set the `debug` option to `true`:
+ **A**. Set the `debug` option:
```javascript
+ // all executed methods log output to console
mongoose.set('debug', true)
// disable colors in debug mode
@@ -305,8 +306,7 @@ block content
mongoose.set('debug', { shell: true })
```
- All executed collection methods will log output of their arguments to your
- console.
+ For more debugging options (streams, callbacks), see the ['debug' option under `.set()`](./api.html#mongoose_Mongoose-set).
diff --git a/docs/images/fortunegames.jpg b/docs/images/fortunegames.jpg
new file mode 100644
index 00000000000..795eca76cf2
Binary files /dev/null and b/docs/images/fortunegames.jpg differ
diff --git a/index.d.ts b/index.d.ts
index 9ebe8855cb0..5df726f4c40 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -630,11 +630,11 @@ declare module 'mongoose' {
countDocuments(filter: FilterQuery, callback?: (err: any, count: number) => void): Query;
/** Creates a new document or documents */
- create>(doc: DocContents): Promise;
create>(docs: DocContents[], options?: SaveOptions): Promise;
+ create>(doc: DocContents): Promise;
create>(...docs: DocContents[]): Promise;
- create>(doc: DocContents, callback: (err: CallbackError, doc: T) => void): void;
create>(docs: DocContents[], callback: (err: CallbackError, docs: T[]) => void): void;
+ create>(doc: DocContents, callback: (err: CallbackError, doc: T) => void): void;
/**
* Create the collection for this model. By default, if no indexes are specified,
@@ -753,6 +753,7 @@ declare module 'mongoose' {
/** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */
validate(callback?: (err: any) => void): Promise;
validate(optional: any, callback?: (err: any) => void): Promise;
+ validate(optional: any, pathsToValidate: string[], callback?: (err: any) => void): Promise;
/** Watches the underlying collection for changes using [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/). */
watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions): mongodb.ChangeStream;
@@ -844,6 +845,8 @@ declare module 'mongoose' {
/** Creates a Query, applies the passed conditions, and returns the Query. */
where(path: string, val?: any): Query, T>;
+ where(obj: object): Query, T>;
+ where(): Query, T>;
}
interface QueryOptions {
@@ -1037,16 +1040,20 @@ declare module 'mongoose' {
useProjection?: boolean;
}
+ type MongooseDocumentMiddleware = 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init';
type MongooseQueryMiddleware = 'count' | 'deleteMany' | 'deleteOne' | 'find' | 'findOne' | 'findOneAndDelete' | 'findOneAndRemove' | 'findOneAndUpdate' | 'remove' | 'update' | 'updateOne' | 'updateMany';
- class Schema = Model> extends events.EventEmitter {
+ type SchemaPreOptions = { document?: boolean, query?: boolean };
+ type SchemaPostOptions = { document?: boolean, query?: boolean };
+
+ class Schema = Model, SchemaDefinitionType = undefined> extends events.EventEmitter {
/**
* Create a new schema
*/
- constructor(definition?: SchemaDefinition, options?: SchemaOptions);
+ constructor(definition?: SchemaDefinition>, options?: SchemaOptions);
/** Adds key path / schema type pairs to this schema. */
- add(obj: SchemaDefinition | Schema, prefix?: string): this;
+ add(obj: SchemaDefinition> | Schema, prefix?: string): this;
/**
* Array of child schemas (from document arrays and single nested subdocs)
@@ -1113,21 +1120,33 @@ declare module 'mongoose' {
plugin(fn: (schema: Schema, opts?: any) => void, opts?: any): this;
/** Defines a post hook for the model. */
- post(method: 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init' | RegExp, fn: (this: T, res: any, next: (err?: CallbackError) => void) => void): this;
- post = Query>(method: MongooseQueryMiddleware | string | RegExp, fn: (this: T, res: any, next: (err: CallbackError) => void) => void): this;
+ post(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: (this: T, res: any, next: (err?: CallbackError) => void) => void): this;
+ post(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPostOptions, fn: (this: T, res: any, next: (err?: CallbackError) => void) => void): this;
+ post = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: (this: T, res: any, next: (err: CallbackError) => void) => void): this;
+ post = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPostOptions, fn: (this: T, res: any, next: (err: CallbackError) => void) => void): this;
post = Aggregate>(method: 'aggregate' | RegExp, fn: (this: T, res: Array, next: (err: CallbackError) => void) => void): this;
+ post = Aggregate>(method: 'aggregate' | RegExp, options: SchemaPostOptions, fn: (this: T, res: Array, next: (err: CallbackError) => void) => void): this;
post = M>(method: 'insertMany' | RegExp, fn: (this: T, res: any, next: (err: CallbackError) => void) => void): this;
+ post = M>(method: 'insertMany' | RegExp, options: SchemaPostOptions, fn: (this: T, res: any, next: (err: CallbackError) => void) => void): this;
- post(method: 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init' | RegExp, fn: (this: T, err: NativeError, res: any, next: (err?: CallbackError) => void) => void): this;
- post = Query>(method: MongooseQueryMiddleware | string | RegExp, fn: (this: T, err: NativeError, res: any, next: (err: CallbackError) => void) => void): this;
+ post(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: (this: T, err: NativeError, res: any, next: (err?: CallbackError) => void) => void): this;
+ post(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPostOptions, fn: (this: T, err: NativeError, res: any, next: (err?: CallbackError) => void) => void): this;
+ post = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: (this: T, err: NativeError, res: any, next: (err: CallbackError) => void) => void): this;
+ post = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPostOptions, fn: (this: T, err: NativeError, res: any, next: (err: CallbackError) => void) => void): this;
post = Aggregate>(method: 'aggregate' | RegExp, fn: (this: T, err: NativeError, res: Array, next: (err: CallbackError) => void) => void): this;
+ post = Aggregate>(method: 'aggregate' | RegExp, options: SchemaPostOptions, fn: (this: T, err: NativeError, res: Array, next: (err: CallbackError) => void) => void): this;
post = M>(method: 'insertMany' | RegExp, fn: (this: T, err: NativeError, res: any, next: (err: CallbackError) => void) => void): this;
+ post = M>(method: 'insertMany' | RegExp, options: SchemaPostOptions, fn: (this: T, err: NativeError, res: any, next: (err: CallbackError) => void) => void): this;
/** Defines a pre hook for the model. */
- pre(method: 'validate' | 'save' | 'remove' | 'updateOne' | 'deleteOne' | 'init' | RegExp, fn: (this: T, next: (err?: CallbackError) => void) => void): this;
- pre = Query>(method: MongooseQueryMiddleware | string | RegExp, fn: (this: T, next: (err: CallbackError) => void) => void): this;
+ pre(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, fn: (this: T, next: (err?: CallbackError) => void) => void): this;
+ pre(method: MongooseDocumentMiddleware | MongooseDocumentMiddleware[] | RegExp, options: SchemaPreOptions, fn: (this: T, next: (err?: CallbackError) => void) => void): this;
+ pre = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, fn: (this: T, next: (err: CallbackError) => void) => void): this;
+ pre = Query>(method: MongooseQueryMiddleware | MongooseQueryMiddleware[] | string | RegExp, options: SchemaPreOptions, fn: (this: T, next: (err: CallbackError) => void) => void): this;
pre = Aggregate>(method: 'aggregate' | RegExp, fn: (this: T, next: (err: CallbackError) => void) => void): this;
+ pre = Aggregate>(method: 'aggregate' | RegExp, options: SchemaPreOptions, fn: (this: T, next: (err: CallbackError) => void) => void): this;
pre = M>(method: 'insertMany' | RegExp, fn: (this: T, next: (err: CallbackError) => void) => void): this;
+ pre = M>(method: 'insertMany' | RegExp, options: SchemaPreOptions, fn: (this: T, next: (err: CallbackError) => void) => void): this;
/** Object of currently defined query helpers on this schema. */
query: any;
@@ -1163,11 +1182,17 @@ declare module 'mongoose' {
virtualpath(name: string): VirtualType | null;
}
+ type SchemaDefinitionWithBuiltInClass = T extends number
+ ? (typeof Number | 'number' | 'Number')
+ : T extends string
+ ? (typeof String | 'string' | 'String')
+ : (Function | string);
+
type SchemaDefinitionProperty = SchemaTypeOptions |
- Function |
- string |
- Schema |
- Schema[] |
+ SchemaDefinitionWithBuiltInClass |
+ typeof SchemaType |
+ Schema> |
+ Schema>[] |
SchemaTypeOptions[] |
Function[] |
SchemaDefinition |
@@ -1175,7 +1200,7 @@ declare module 'mongoose' {
type SchemaDefinition = T extends undefined
? { [path: string]: SchemaDefinitionProperty; }
- : { [path in keyof T]-?: SchemaDefinitionProperty; };
+ : { [path in keyof T]?: SchemaDefinitionProperty; };
interface SchemaOptions {
/**
@@ -1340,7 +1365,7 @@ declare module 'mongoose' {
}
interface SchemaTypeOptions {
- type?: T;
+ type: T;
/** Defines a virtual with the given name that gets/sets this path. */
alias?: string;
@@ -1357,7 +1382,7 @@ declare module 'mongoose' {
* path cannot be set to a nullish value. If a function, Mongoose calls the
* function and only checks for nullish values if the function returns a truthy value.
*/
- required?: boolean | (() => boolean) | [boolean, string];
+ required?: boolean | (() => boolean) | [boolean, string] | [() => boolean, string];
/**
* The default value for this path. If a function, Mongoose executes the function
@@ -1757,7 +1782,7 @@ declare module 'mongoose' {
type ReturnsNewDoc = { new: true } | { returnOriginal: false };
- interface Query {
+ class Query {
_mongooseOptions: MongooseQueryOptions;
/** Executes the query */
@@ -2135,6 +2160,8 @@ declare module 'mongoose' {
/** Specifies a path for use with chaining. */
where(path: string, val?: any): this;
+ where(obj: object): this;
+ where(): this;
/** Defines a `$within` or `$geoWithin` argument for geo-spatial queries. */
within(val?: any): this;
diff --git a/index.pug b/index.pug
index b6c5da11371..b1daf0034b9 100644
--- a/index.pug
+++ b/index.pug
@@ -226,8 +226,8 @@ html(lang='en')
-
-
+
+
diff --git a/lib/aggregate.js b/lib/aggregate.js
index 7e4093f4e97..d4a65b2d261 100644
--- a/lib/aggregate.js
+++ b/lib/aggregate.js
@@ -1099,15 +1099,15 @@ function prepareDiscriminatorPipeline(aggregate) {
// If the first pipeline stage is a match and it doesn't specify a `__t`
// key, add the discriminator key to it. This allows for potential
// aggregation query optimizations not to be disturbed by this feature.
- if (originalPipeline[0] && originalPipeline[0].$match && !originalPipeline[0].$match[discriminatorKey]) {
+ if (originalPipeline[0] != null && originalPipeline[0].$match && !originalPipeline[0].$match[discriminatorKey]) {
originalPipeline[0].$match[discriminatorKey] = discriminatorValue;
// `originalPipeline` is a ref, so there's no need for
// aggregate._pipeline = originalPipeline
- } else if (originalPipeline[0] && originalPipeline[0].$geoNear) {
+ } else if (originalPipeline[0] != null && originalPipeline[0].$geoNear) {
originalPipeline[0].$geoNear.query =
originalPipeline[0].$geoNear.query || {};
originalPipeline[0].$geoNear.query[discriminatorKey] = discriminatorValue;
- } else if (originalPipeline[0] && originalPipeline[0].$search) {
+ } else if (originalPipeline[0] != null && originalPipeline[0].$search) {
if (originalPipeline[1] && originalPipeline[1].$match != null) {
originalPipeline[1].$match[discriminatorKey] = originalPipeline[1].$match[discriminatorKey] || discriminatorValue;
} else {
diff --git a/lib/cast.js b/lib/cast.js
index 1c24bc7fe65..74ebb5200b6 100644
--- a/lib/cast.js
+++ b/lib/cast.js
@@ -259,10 +259,11 @@ module.exports = function cast(schema, obj, options, context) {
}
throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
'schema, strict mode is `true`, and upsert is `true`.');
- } else if (options.strictQuery === 'throw') {
- throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
- 'schema and strictQuery is \'throw\'.');
- } else if (options.strictQuery) {
+ } else if (options.strict) {
+ if (options.strict === 'throw') {
+ throw new StrictModeError(path, 'Path "' + path + '" is not in ' +
+ 'schema and strict is \'throw\'.');
+ }
delete obj[path];
}
} else if (val == null) {
diff --git a/lib/document.js b/lib/document.js
index 453b4a2a9ca..3d7100128ff 100644
--- a/lib/document.js
+++ b/lib/document.js
@@ -139,7 +139,6 @@ function Document(obj, fields, skipId, options) {
});
}
}
-
if (obj) {
// Skip set hooks
if (this.$__original_set) {
@@ -663,7 +662,7 @@ function init(self, obj, doc, opts, prefix) {
// Should still work if not a model-level discriminator, but should not be
// necessary. This is *only* to catch the case where we queried using the
// base model and the discriminated model has a projection
- if (self.schema.$isRootDiscriminator && !self.isSelected(path)) {
+ if (self.schema.$isRootDiscriminator && !self.$__isSelected(path)) {
return;
}
@@ -897,6 +896,7 @@ Document.prototype.overwrite = function overwrite(obj) {
*/
Document.prototype.$set = function $set(path, val, type, options) {
+
if (utils.isPOJO(type)) {
options = type;
type = undefined;
@@ -935,9 +935,13 @@ Document.prototype.$set = function $set(path, val, type, options) {
path = path._doc;
}
}
+ if (path == null) {
+ const _ = path;
+ path = val;
+ val = _;
+ }
prefix = val ? val + '.' : '';
-
keys = Object.keys(path);
const len = keys.length;
@@ -1134,7 +1138,7 @@ Document.prototype.$set = function $set(path, val, type, options) {
// traverse the element ({nested: null})` is not likely. If user gets
// that error, its their fault for now. We should reconsider disallowing
// modifying not selected paths for 6.x
- if (!this.isSelected(curPath)) {
+ if (!this.$__isSelected(curPath)) {
this.unmarkModified(curPath);
}
cur = this.$__getValue(curPath);
@@ -1415,7 +1419,7 @@ Document.prototype.$__shouldModify = function(pathToMark, path, constructing, pa
return false;
}
- if (val === void 0 && !this.isSelected(path)) {
+ if (val === void 0 && !this.$__isSelected(path)) {
// when a path is not selected in a query, its initial
// value will be undefined.
return true;
@@ -2059,7 +2063,7 @@ Document.prototype.isSelected = function isSelected(path) {
path = path.split(' ');
}
if (Array.isArray(path)) {
- return path.some(p => this.isSelected(p));
+ return path.some(p => this.$__isSelected(p));
}
const paths = Object.keys(this.$__.selected);
@@ -2257,7 +2261,7 @@ function _getPathsToValidate(doc) {
// only validate required fields when necessary
let paths = new Set(Object.keys(doc.$__.activePaths.states.require).filter(function(path) {
- if (!doc.isSelected(path) && !doc.isModified(path)) {
+ if (!doc.$__isSelected(path) && !doc.isModified(path)) {
return false;
}
if (path in doc.$__.cachedRequired) {
@@ -3566,7 +3570,7 @@ function applyGetters(self, json, options) {
let part;
cur = self._doc;
- if (!self.isSelected(path)) {
+ if (!self.$__isSelected(path)) {
continue;
}
diff --git a/lib/helpers/timestamps/setupTimestamps.js b/lib/helpers/timestamps/setupTimestamps.js
index 752a565738c..76140099df8 100644
--- a/lib/helpers/timestamps/setupTimestamps.js
+++ b/lib/helpers/timestamps/setupTimestamps.js
@@ -51,7 +51,7 @@ module.exports = function setupTimestamps(schema, timestamps) {
(this.ownerDocument ? this.ownerDocument() : this).constructor.base.now();
const auto_id = this._id && this._id.auto;
- if (!skipCreatedAt && createdAt && !this.get(createdAt) && this.isSelected(createdAt)) {
+ if (!skipCreatedAt && createdAt && !this.get(createdAt) && this.$__isSelected(createdAt)) {
this.$set(createdAt, auto_id ? this._id.getTimestamp() : defaultTimestamp);
}
diff --git a/lib/helpers/update/castArrayFilters.js b/lib/helpers/update/castArrayFilters.js
index 57018d9fb10..8cfd00243f5 100644
--- a/lib/helpers/update/castArrayFilters.js
+++ b/lib/helpers/update/castArrayFilters.js
@@ -10,13 +10,10 @@ module.exports = function castArrayFilters(query) {
if (!Array.isArray(arrayFilters)) {
return;
}
-
const update = query.getUpdate();
const schema = query.schema;
- const strictQuery = schema.options.strictQuery;
-
+ const strict = schema.options.strict;
const updatedPaths = modifiedPaths(update);
-
const updatedPathsByFilter = Object.keys(updatedPaths).reduce((cur, path) => {
const matches = path.match(/\$\[[^\]]+\]/g);
if (matches == null) {
@@ -33,22 +30,23 @@ module.exports = function castArrayFilters(query) {
}
return cur;
}, {});
-
for (const filter of arrayFilters) {
if (filter == null) {
throw new Error(`Got null array filter in ${arrayFilters}`);
}
- for (const key in filter) {
+ for (const key in filter) {
if (filter[key] == null) {
continue;
}
-
+ if (updatedPathsByFilter[key] === null) {
+ continue;
+ }
+ if (Object.keys(updatedPathsByFilter).length === 0) continue;
const dot = key.indexOf('.');
let filterPath = dot === -1 ?
updatedPathsByFilter[key] + '.0' :
updatedPathsByFilter[key.substr(0, dot)] + '.0' + key.substr(dot);
-
if (filterPath == null) {
throw new Error(`Filter path not found for ${key}`);
}
@@ -56,14 +54,13 @@ module.exports = function castArrayFilters(query) {
// If there are multiple array filters in the path being updated, make sure
// to replace them so we can get the schema path.
filterPath = cleanPositionalOperators(filterPath);
-
const schematype = getPath(schema, filterPath);
if (schematype == null) {
- if (!strictQuery) {
+ if (!strict) {
return;
}
- // For now, treat `strictQuery = true` and `strictQuery = 'throw'` as
- // equivalent for casting array filters. `strictQuery = true` doesn't
+ // For now, treat `strict = true` and `strict = 'throw'` as
+ // equivalent for casting array filters. `strict = true` doesn't
// quite work in this context because we never want to silently strip out
// array filters, even if the path isn't in the schema.
throw new Error(`Could not find path "${filterPath}" in schema`);
diff --git a/lib/index.js b/lib/index.js
index 934a4e58568..e0172c5d066 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -159,7 +159,6 @@ Mongoose.prototype.driver = require('./driver');
* - 'toObject': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toObject()`](/docs/api.html#document_Document-toObject)
* - 'toJSON': `{ transform: true, flattenDecimals: true }` by default. Overwrites default objects to [`toJSON()`](/docs/api.html#document_Document-toJSON), for determining how Mongoose documents get serialized by `JSON.stringify()`
* - 'strict': true by default, may be `false`, `true`, or `'throw'`. Sets the default strict mode for schemas.
- * - 'strictQuery': false by default, may be `false`, `true`, or `'throw'`. Sets the default [strictQuery](/docs/guide.html#strictQuery) mode for schemas.
* - '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.
* - 'maxTimeMS': If set, attaches [maxTimeMS](https://docs.mongodb.com/manual/reference/operator/meta/maxTimeMS/) to every query
* - 'autoIndex': true by default. Set to false to disable automatic index creation for all models associated with this Mongoose instance.
diff --git a/lib/model.js b/lib/model.js
index 58545a358d0..d66e9510746 100644
--- a/lib/model.js
+++ b/lib/model.js
@@ -819,7 +819,7 @@ Model.prototype.$__version = function(where, delta) {
// there is no way to select the correct version. we could fail
// fast here and force them to include the versionKey but
// thats a bit intrusive. can we do this automatically?
- if (!this.isSelected(key)) {
+ if (!this.$__isSelected(key)) {
return;
}
diff --git a/lib/query.js b/lib/query.js
index d98501258fd..4fc25dce6cf 100644
--- a/lib/query.js
+++ b/lib/query.js
@@ -1932,7 +1932,6 @@ Query.prototype._unsetCastError = function _unsetCastError() {
* - `populate`: an array representing what paths will be populated. Should have one entry for each call to [`Query.prototype.populate()`](/docs/api.html#query_Query-populate)
* - `lean`: if truthy, Mongoose will not [hydrate](/docs/api.html#model_Model.hydrate) any documents that are returned from this query. See [`Query.prototype.lean()`](/docs/api.html#query_Query-lean) for more information.
* - `strict`: controls how Mongoose handles keys that aren't in the schema for updates. This option is `true` by default, which means Mongoose will silently strip any paths in the update that aren't in the schema. See the [`strict` mode docs](/docs/guide.html#strict) for more information.
- * - `strictQuery`: controls how Mongoose handles keys that aren't in the schema for the query `filter`. This option is `false` by default for backwards compatibility, which means Mongoose will allow `Model.find({ foo: 'bar' })` even if `foo` is not in the schema. See the [`strictQuery` docs](/docs/guide.html#strictQuery) for more information.
* - `nearSphere`: use `$nearSphere` instead of `near()`. See the [`Query.prototype.nearSphere()` docs](/docs/api.html#query_Query-nearSphere)
*
* Mongoose maintains a separate object for internal options because
@@ -4814,9 +4813,7 @@ Query.prototype.cast = function(model, obj) {
upsert: this.options && this.options.upsert,
strict: (this.options && 'strict' in this.options) ?
this.options.strict :
- get(model, 'schema.options.strict', null),
- strictQuery: (this.options && this.options.strictQuery) ||
- get(model, 'schema.options.strictQuery', null)
+ get(model, 'schema.options.strict', null)
}, this);
} catch (err) {
// CastError, assign model
diff --git a/lib/schema.js b/lib/schema.js
index 2d8dffcd6a1..edfbc6b34c6 100644
--- a/lib/schema.js
+++ b/lib/schema.js
@@ -60,7 +60,6 @@ let id = 0;
* - [writeConcern](/docs/guide.html#writeConcern): object - defaults to null, use to override [the MongoDB server's default write concern settings](https://docs.mongodb.com/manual/reference/write-concern/)
* - [shardKey](/docs/guide.html#shardKey): object - defaults to `null`
* - [strict](/docs/guide.html#strict): bool - defaults to true
- * - [strictQuery](/docs/guide.html#strictQuery): bool - defaults to false
* - [toJSON](/docs/guide.html#toJSON) - object - no default
* - [toObject](/docs/guide.html#toObject) - object - no default
* - [typeKey](/docs/guide.html#typeKey) - string - defaults to 'type'
@@ -400,14 +399,13 @@ Schema.prototype.defaultOptions = function(options) {
const baseOptions = get(this, 'base.options', {});
options = utils.options({
strict: 'strict' in baseOptions ? baseOptions.strict : true,
- strictQuery: 'strictQuery' in baseOptions ? baseOptions.strictQuery : false,
bufferCommands: true,
capped: false, // { size, max, autoIndexId }
versionKey: '__v',
optimisticConcurrency: false,
- discriminatorKey: '__t',
minimize: true,
autoIndex: null,
+ discriminatorKey: '__t',
shardKey: null,
read: null,
validateBeforeSave: true,
diff --git a/lib/validoptions.js b/lib/validoptions.js
index 86706c66e8c..0f070b48844 100644
--- a/lib/validoptions.js
+++ b/lib/validoptions.js
@@ -22,7 +22,6 @@ const VALID_OPTIONS = Object.freeze([
'selectPopulatedPaths',
'setDefaultsOnInsert',
'strict',
- 'strictQuery',
'toJSON',
'toObject',
'typePojoToMixed',
diff --git a/package.json b/package.json
index 34a8d05fd29..e9c4d5acb45 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mongoose",
"description": "Mongoose MongoDB ODM",
- "version": "5.11.13",
+ "version": "5.11.15",
"author": "Guillermo Rauch ",
"keywords": [
"mongodb",
diff --git a/test/document.strict.test.js b/test/document.strict.test.js
index 2aa6e235293..9b676f836e5 100644
--- a/test/document.strict.test.js
+++ b/test/document.strict.test.js
@@ -455,7 +455,6 @@ describe('document: strict mode:', function() {
});
nestedSchema.options.strict = 'throw';
- nestedSchema.options.strictQuery = 'throw';
const schema = new mongoose.Schema({
nested: {
diff --git a/test/document.test.js b/test/document.test.js
index 2f4e3daaf3f..7c838573b76 100644
--- a/test/document.test.js
+++ b/test/document.test.js
@@ -9905,4 +9905,99 @@ describe('document', function() {
assert.equal(ss.nested[0].toHexString(), updatedElID);
});
});
+ it('gh9884', function() {
+ return co(function*() {
+
+ const obi = new Schema({
+ eType: {
+ type: String,
+ required: true,
+ uppercase: true
+ },
+ eOrigin: {
+ type: String,
+ required: true
+ },
+ eIds: [
+ {
+ type: String
+ }
+ ]
+ }, { _id: false });
+
+ const schema = new Schema({
+ name: String,
+ description: String,
+ isSelected: {
+ type: Boolean,
+ default: false
+ },
+ wan: {
+ type: [obi],
+ default: undefined,
+ required: true
+ }
+ });
+
+ const newDoc = {
+ name: 'name',
+ description: 'new desc',
+ isSelected: true,
+ wan: [
+ {
+ eType: 'X',
+ eOrigin: 'Y',
+ eIds: ['Y', 'Z']
+ }
+ ]
+ };
+
+ const Model = db.model('Test', schema);
+ yield Model.create(newDoc);
+ const doc = yield Model.findOne();
+ assert.ok(doc);
+ });
+ });
+
+ it('gh9880', function(done) {
+ const testSchema = new Schema({
+ prop: String,
+ nestedProp: {
+ prop: String
+ }
+ });
+ const Test = db.model('Test', testSchema);
+
+ new Test({
+ prop: 'Test',
+ nestedProp: null
+ }).save((err, doc) => {
+ doc.id;
+ doc.nestedProp;
+
+ // let's clone this document:
+ new Test({
+ prop: 'Test 2',
+ nestedProp: doc.nestedProp
+ });
+
+ Test.updateOne({
+ _id: doc._id
+ }, {
+ nestedProp: null
+ }, (err) => {
+ assert.ifError(err);
+ Test.findOne({
+ _id: doc._id
+ }, (err, updatedDoc) => {
+ assert.ifError(err);
+ new Test({
+ prop: 'Test 3',
+ nestedProp: updatedDoc.nestedProp
+ });
+ done();
+ });
+ });
+ });
+ });
});
diff --git a/test/es-next/cast.test.es6.js b/test/es-next/cast.test.es6.js
index efd58eac1ed..07d8280019b 100644
--- a/test/es-next/cast.test.es6.js
+++ b/test/es-next/cast.test.es6.js
@@ -97,10 +97,10 @@ describe('Cast Tutorial', function() {
await query.exec();
});
- it('strictQuery true', async function() {
+ it('strict true', async function() {
mongoose.deleteModel('Character');
const schema = new mongoose.Schema({ name: String, age: Number }, {
- strictQuery: true
+ strict: true
});
Character = mongoose.model('Character', schema);
@@ -113,10 +113,10 @@ describe('Cast Tutorial', function() {
// acquit:ignore:end
});
- it('strictQuery throw', async function() {
+ it('strict throw', async function() {
mongoose.deleteModel('Character');
const schema = new mongoose.Schema({ name: String, age: Number }, {
- strictQuery: 'throw'
+ strict: 'throw'
});
Character = mongoose.model('Character', schema);
@@ -124,12 +124,12 @@ describe('Cast Tutorial', function() {
const err = await query.exec().then(() => null, err => err);
err.name; // 'StrictModeError'
- // Path "notInSchema" is not in schema and strictQuery is 'throw'.
+ // Path "notInSchema" is not in schema and strict is 'throw'.
err.message;
// acquit:ignore:start
assert.equal(err.name, 'StrictModeError');
assert.equal(err.message, 'Path "notInSchema" is not in schema and ' +
- 'strictQuery is \'throw\'.');
+ 'strict is \'throw\'.');
// acquit:ignore:end
});
diff --git a/test/es-next/virtuals.test.es6.js b/test/es-next/virtuals.test.es6.js
index a39b3e999b5..b530d80b14f 100644
--- a/test/es-next/virtuals.test.es6.js
+++ b/test/es-next/virtuals.test.es6.js
@@ -133,7 +133,7 @@ describe('Virtuals', function() {
// acquit:ignore:end
// Will **not** find any results, because `domain` is not stored in
// MongoDB.
- const doc = await User.findOne({ domain: 'gmail.com' });
+ const doc = await User.findOne({ domain: 'gmail.com' }, null, { strict: false });
doc; // undefined
// acquit:ignore:start
assert.equal(doc, null);
diff --git a/test/helpers/update.castArrayFilters.test.js b/test/helpers/update.castArrayFilters.test.js
index fac39ca9fc1..d89fc11a07e 100644
--- a/test/helpers/update.castArrayFilters.test.js
+++ b/test/helpers/update.castArrayFilters.test.js
@@ -35,7 +35,6 @@ describe('castArrayFilters', function() {
arrayFilters: [{ 'x.text': 123 }, { 'y.date': { $gte: '2018-01-01' } }]
});
castArrayFilters(q);
-
assert.strictEqual(q.options.arrayFilters[0]['x.text'], '123');
assert.ok(q.options.arrayFilters[1]['y.date'].$gte instanceof Date);
@@ -163,7 +162,7 @@ describe('castArrayFilters', function() {
assert.strictEqual(q.options.arrayFilters[1]['nArr.nestedId'], 2);
});
- it('respects `strictQuery` option (gh-7728)', function() {
+ it('respects `strict` option (gh-7728)', function() {
const schema = new Schema({
arr: [{
id: Number
@@ -180,13 +179,13 @@ describe('castArrayFilters', function() {
};
q.updateOne({}, p, opts);
-
- castArrayFilters(q);
- assert.strictEqual(q.options.arrayFilters[0]['arr.notInSchema'], '42');
-
- q.schema.options.strictQuery = true;
+ q.schema.options.strict = true;
assert.throws(function() {
castArrayFilters(q);
}, /Could not find path.*in schema/);
+
+ q.schema.options.strict = false;
+ castArrayFilters(q);
+ assert.strictEqual(q.options.arrayFilters[0]['arr.notInSchema'], '42');
});
});
diff --git a/test/model.test.js b/test/model.test.js
index 436abb75d7f..28c47ffd326 100644
--- a/test/model.test.js
+++ b/test/model.test.js
@@ -6602,7 +6602,7 @@ describe('Model', function() {
return Model.create({ name: 'foo' }).
then(() => Model.exists({ name: 'bar' })).
then(res => assert.ok(!res)).
- then(() => Model.exists({ otherProp: 'foo' })).
+ then(() => Model.exists({ otherProp: 'foo' }, { strict: false })).
then(res => assert.ok(!res));
});
diff --git a/test/query.test.js b/test/query.test.js
index 8f0ad74fd8a..7ec98104b9c 100644
--- a/test/query.test.js
+++ b/test/query.test.js
@@ -3260,11 +3260,11 @@ describe('Query', function() {
});
});
- it('strictQuery option (gh-4136) (gh-7178)', function() {
+ it('strict option (gh-4136) (gh-7178)', function() {
const modelSchema = new Schema({
field: Number,
nested: { path: String }
- }, { strictQuery: 'throw' });
+ }, { strict: 'throw' });
const Model = db.model('Test', modelSchema);
@@ -3272,7 +3272,7 @@ describe('Query', function() {
// `find()` on a top-level path not in the schema
let err = yield Model.find({ notInschema: 1 }).then(() => null, e => e);
assert.ok(err);
- assert.ok(err.message.indexOf('strictQuery') !== -1, err.message);
+ assert.ok(err.message.indexOf('strict') !== -1, err.message);
// Shouldn't throw on nested path re: gh-7178
yield Model.create({ nested: { path: 'a' } });
@@ -3282,12 +3282,12 @@ describe('Query', function() {
// `find()` on a nested path not in the schema
err = yield Model.find({ 'nested.bad': 'foo' }).then(() => null, e => e);
assert.ok(err);
- assert.ok(err.message.indexOf('strictQuery') !== -1, err.message);
+ assert.ok(err.message.indexOf('strict') !== -1, err.message);
});
});
- it('strictQuery = true (gh-6032)', function() {
- const modelSchema = new Schema({ field: Number }, { strictQuery: true });
+ it('strict = true (gh-6032)', function() {
+ const modelSchema = new Schema({ field: Number }, { strict: true });
return co(function*() {
const Model = db.model('Test', modelSchema);
diff --git a/test/schema.test.js b/test/schema.test.js
index 9bf6ccc2022..cb9280e2663 100644
--- a/test/schema.test.js
+++ b/test/schema.test.js
@@ -2427,35 +2427,6 @@ describe('schema', function() {
assert.ok(!schema.virtuals.id);
});
- describe('mongoose.set(`strictQuery`, value); (gh-6658)', function() {
- let strictQueryOriginalValue;
-
- this.beforeEach(() => strictQueryOriginalValue = mongoose.get('strictQuery'));
- this.afterEach(() => mongoose.set('strictQuery', strictQueryOriginalValue));
-
- it('setting `strictQuery` on base sets strictQuery to schema (gh-6658)', function() {
- // Arrange
- mongoose.set('strictQuery', 'some value');
-
- // Act
- const schema = new Schema();
-
- // Assert
- assert.equal(schema.get('strictQuery'), 'some value');
- });
-
- it('`strictQuery` set on base gets overwritten by option set on schema (gh-6658)', function() {
- // Arrange
- mongoose.set('strictQuery', 'base option');
-
- // Act
- const schema = new Schema({}, { strictQuery: 'schema option' });
-
- // Assert
- assert.equal(schema.get('strictQuery'), 'schema option');
- });
- });
-
it('treats dotted paths with no parent as a nested path (gh-9020)', function() {
const customerSchema = new Schema({
'card.brand': String,
diff --git a/test/schema.validation.test.js b/test/schema.validation.test.js
index 1e773fea9ee..01db9695c47 100644
--- a/test/schema.validation.test.js
+++ b/test/schema.validation.test.js
@@ -1190,9 +1190,10 @@ describe('schema', function() {
});
const Breakfast = mongoose.model('gh2832', breakfast, 'gh2832');
- Breakfast.create({ description: undefined }, function(error) {
+ const bad = new Breakfast({ description: 'test' });
+ bad.validate(function(error) {
assert.ok(error);
- const errorMessage = 'ValidationError: description: Cast to String failed for value "undefined" at path "description"';
+ const errorMessage = 'ValidationError: description: Cast to String failed for value "test" at path "description"';
assert.equal(errorMessage, error.toString());
assert.ok(error.errors.description);
assert.equal(error.errors.description.reason.toString(), 'Error: oops');
diff --git a/test/typescript/create.ts b/test/typescript/create.ts
index c20f56cc308..cb76ce45172 100644
--- a/test/typescript/create.ts
+++ b/test/typescript/create.ts
@@ -18,4 +18,11 @@ Test.create({ name: 'test' }, { name: 'test2' }).then((docs: ITest[]) => console
Test.insertMany({ name: 'test' }).then((doc: ITest) => console.log(doc.name));
Test.insertMany([{ name: 'test' }], { session: null }).then((docs: ITest[]) => console.log(docs[0].name));
-Test.create([{ name: 'test' }], { validateBeforeSave: true }).then((docs: ITest[]) => console.log(docs[0].name));
\ No newline at end of file
+Test.create([{ name: 'test' }], { validateBeforeSave: true }).then((docs: ITest[]) => console.log(docs[0].name));
+
+(async() => {
+ const [t1] = await Test.create([{ name: 'test' }]);
+ const [t2, t3, t4] = await Test.create({ name: 'test' }, { name: 'test' }, { name: 'test' });
+ (await Test.create([{ name: 'test' }]))[0];
+ (await Test.create({ name: 'test' }))._id;
+})();
diff --git a/test/typescript/main.test.js b/test/typescript/main.test.js
index fc9d2f06f46..2d6b1188074 100644
--- a/test/typescript/main.test.js
+++ b/test/typescript/main.test.js
@@ -178,7 +178,9 @@ describe('typescript syntax', function() {
if (process.env.D && errors.length) {
console.log(errors);
}
- assert.equal(errors.length, 0);
+ assert.equal(errors.length, 1);
+ const messageText = errors[0].messageText.messageText;
+ assert.ok(/Type 'StringConstructor' is not assignable to type.*number/.test(messageText), messageText);
});
it('document', function() {
diff --git a/test/typescript/middleware.ts b/test/typescript/middleware.ts
index 5c941ea887a..7a89a3e34c0 100644
--- a/test/typescript/middleware.ts
+++ b/test/typescript/middleware.ts
@@ -18,6 +18,10 @@ schema.post>('aggregate', async function(res: Array) {
console.log('Pipeline', this.pipeline(), res[0]);
});
+schema.pre(['save', 'validate'], { query: false, document: true }, async function applyChanges() {
+ await Test.findOne({});
+});
+
interface ITest extends Document {
name?: string;
}
diff --git a/test/typescript/queries.ts b/test/typescript/queries.ts
index 699742dd757..54807420698 100644
--- a/test/typescript/queries.ts
+++ b/test/typescript/queries.ts
@@ -1,4 +1,4 @@
-import { Schema, model, Document, Types } from 'mongoose';
+import { Schema, model, Document, Types, Query } from 'mongoose';
const schema: Schema = new Schema({ name: { type: 'String' }, tags: [String] });
@@ -13,9 +13,11 @@ const Test = model('Test', schema);
Test.count({ name: /Test/ }).exec().then((res: number) => console.log(res));
+// ObjectId casting
Test.find({ parent: new Types.ObjectId('0'.repeat(24)) });
Test.find({ parent: '0'.repeat(24) });
+// Operators
Test.find({ name: { $in: ['Test'] } }).exec().then((res: Array) => console.log(res));
Test.find({ name: 'test' }, (err: Error, docs: ITest[]) => {
@@ -45,4 +47,11 @@ Test.findOneAndUpdate({ name: 'test' }, { name: 'test3' }, { new: true, upsert:
Test.findOneAndReplace({ name: 'test' }, { _id: new Types.ObjectId(), name: 'test2' }).exec().then((res: ITest | null) => console.log(res));
Test.findOneAndUpdate({ name: 'test' }, { $addToSet: { tags: 'each' } });
-Test.findOneAndUpdate({ name: 'test' }, { $push: { tags: 'each' } });
\ No newline at end of file
+Test.findOneAndUpdate({ name: 'test' }, { $push: { tags: 'each' } });
+
+const query: Query = Test.findOne();
+query instanceof Query;
+
+// Chaining
+Test.findOne().where({ name: 'test' });
+Test.where().find({ name: 'test' });
\ No newline at end of file
diff --git a/test/typescript/schema.ts b/test/typescript/schema.ts
index 79348e2ba29..4aed0505b31 100644
--- a/test/typescript/schema.ts
+++ b/test/typescript/schema.ts
@@ -1,4 +1,4 @@
-import { Schema } from 'mongoose';
+import { Schema, Document, SchemaDefinition, SchemaDefinitionProperty, Model } from 'mongoose';
enum Genre {
Action,
@@ -23,5 +23,58 @@ const movieSchema: Schema = new Schema({
type: String,
enum: Genre,
required: true
+ },
+ actionIntensity: {
+ type: Number,
+ required: [
+ function(this: { genre: Genre }) {
+ return this.genre === Genre.Action;
+ },
+ 'Action intensity required for action genre'
+ ]
}
});
+
+// Using `SchemaDefinition`
+interface IProfile { age: number; }
+interface ProfileDoc extends Document, IProfile {}
+const ProfileSchemaDef: SchemaDefinition = { age: Number };
+export const ProfileSchema = new Schema, ProfileDoc>(ProfileSchemaDef);
+
+interface IUser {
+ email: string;
+ profile: ProfileDoc;
+}
+
+interface UserDoc extends Document, IUser {}
+
+const ProfileSchemaDef2: SchemaDefinition = {
+ age: Schema.Types.Number
+};
+
+const ProfileSchema2: Schema> = new Schema(ProfileSchemaDef2);
+
+const UserSchemaDef: SchemaDefinition = {
+ email: String,
+ profile: ProfileSchema2
+};
+
+async function gh9857() {
+ interface User {
+ name: number;
+ active: boolean;
+ points: number;
+ }
+
+ type UserDocument = Document;
+ type UserSchemaDefinition = SchemaDefinition;
+ type UserModel = Model;
+
+ const schemaDefinition: UserSchemaDefinition = {
+ name: String,
+ active: Boolean,
+ points: Number
+ };
+
+ const schema = new Schema(schemaDefinition);
+}