diff --git a/.github/workflows/tsd.yml b/.github/workflows/tsd.yml index 7a47d134117..68e4ef1e81a 100644 --- a/.github/workflows/tsd.yml +++ b/.github/workflows/tsd.yml @@ -45,7 +45,7 @@ jobs: - name: Setup node uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 with: - node-version: 12 + node-version: 14 - run: npm install diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ee1586f2a2..a592555f122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +7.2.2 / 2023-05-30 +================== + * fix(schema): make bulkWrite updateOne() and updateMany() respect timestamps option when set by merging schemas #13445 + * fix(schema): recursively copy schemas from different modules when calling new Schema() #13441 #13275 + * fix(update): allow setting paths with dots under non-strict paths #13450 #13434 + * types: improve function parameter types for ToObjectOptions transform option #13446 #13421 + * docs: add nextjs page with link to next starter app and couple FAQs #13444 #13430 + * docs(connections): add section on multi tenant #13449 #11187 + * docs(connection+model): expand docs on accessors for underlying collections #13448 #13334 + 7.2.1 / 2023-05-24 ================== * fix(array): track correct changes when setting nested array of primitives #13422 #13372 diff --git a/docs/connections.md b/docs/connections.md index 44b5dc4a9e6..87c5b9c54b4 100644 --- a/docs/connections.md +++ b/docs/connections.md @@ -31,6 +31,7 @@ See the [mongodb connection string spec](http://www.mongodb.com/docs/manual/refe
  • Multi-mongos support
  • Multiple connections
  • Connection Pools
  • +
  • Multi Tenant Connections
  • Operation Buffering

    @@ -449,6 +450,91 @@ const uri = 'mongodb://127.0.0.1:27017/test?maxPoolSize=10'; mongoose.createConnection(uri); ``` +The connection pool size is important because [MongoDB currently can only process one operation per socket](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). +So `maxPoolSize` functions as a cap on the number of concurrent operations. + +

    Multi Tenant Connections

    + +In the context of Mongoose, a multi-tenant architecture typically means a case where multiple different clients talk to MongoDB through a single Mongoose application. +This typically means each client makes queries and executes updates through a single Mongoose application, but has a distinct MongoDB database within the same MongoDB cluster. + +We recommend reading [this article about multi-tenancy with Mongoose](https://medium.com/brightlab-techblog/multitenant-node-js-application-with-mongoose-mongodb-f8841a285b4f); it has a good description of how we define multi-tenancy and a more detailed overview of our recommended patterns. + +There are two patterns we recommend for multi-tenancy in Mongoose: + +1. Maintain one connection pool, switch between tenants using the [`Connection.prototype.useDb()` method](https://mongoosejs.com/docs/api/connection.html#Connection.prototype.useDb()). +2. Maintain a separate connection pool per tenant, store connections in a map or [POJO](https://masteringjs.io/tutorials/fundamentals/pojo). + +The following is an example of pattern (1). +We recommend pattern (1) for cases where you have a small number of tenants, or if each individual tenant's workload is light (approximately < 1 request per second, all requests take < 10ms of database processing time). +Pattern (1) is simpler to implement and simpler to manage in production, because there is only 1 connection pool. +But, under high load, you will likely run into issues where some tenants' operations slow down other tenants' operations due to [slow trains](https://thecodebarbarian.com/slow-trains-in-mongodb-and-nodejs). + +```javascript +const express = require('express'); +const mongoose = require('mongoose'); + +mongoose.connect('mongodb://127.0.0.1:27017/main'); +mongoose.set('debug', true); + +mongoose.model('User', mongoose.Schema({ name: String })); + +const app = express(); + +app.get('/users/:tenantId', function(req, res) { + const db = mongoose.connection.useDb(`tenant_${req.params.tenantId}`, { + // `useCache` tells Mongoose to cache connections by database name, so + // `mongoose.connection.useDb('foo', { useCache: true })` returns the + // same reference each time. + useCache: true + }); + // Need to register models every time a new connection is created + if (!db.models['User']) { + db.model('User', mongoose.Schema({ name: String })); + } + console.log('Find users from', db.name); + db.model('User').find(). + then(users => res.json({ users })). + catch(err => res.status(500).json({ message: err.message })); +}); + +app.listen(3000); +``` + +The following is an example of pattern (2). +Pattern (2) is more flexible and better for use cases with > 10k tenants and > 1 requests/second. +Because each tenant has a separate connection pool, one tenants' slow operations will have minimal impact on other tenants. +However, this pattern is harder to implement and manage in production. +In particular, [MongoDB does have a limit on the number of open connections](https://www.mongodb.com/blog/post/tuning-mongodb--linux-to-allow-for-tens-of-thousands-connections), and [MongoDB Atlas has separate limits on the number of open connections](https://www.mongodb.com/docs/atlas/reference/atlas-limits), so you need to make sure the total number of sockets in your connection pools doesn't go over MongoDB's limits. + +```javascript +const express = require('express'); +const mongoose = require('mongoose'); + +mongoose.connect('mongodb://127.0.0.1:27017/main'); + +const tenantIdToConnection = {}; + +const app = express(); + +app.get('/users/:tenantId', function(req, res) { + let initialConnection = Promise.resolve(); + const { tenantId } = req.params; + if (!tenantIdToConnection[tenantId]) { + tenantIdToConnection[tenantId] = mongoose.createConnection(`mongodb://127.0.0.1:27017/tenant_${tenantId}`); + tenantIdToConnection[tenantId].model('User', mongoose.Schema({ name: String })); + initialConnection = tenantIdToConnection[tenantId].asPromise(); + } + const db = tenantIdToConnection[tenantId]; + initialConnection. + then(() => db.model('User').find()). + then(users => res.json({ users })). + catch(err => res.status(500).json({ message: err.message })); +}); + +app.listen(3000); +``` +

    Next Up

    Now that we've covered connections, let's take a look at [models](models.html). diff --git a/docs/guides.md b/docs/guides.md index d24aff91797..aac2bed5396 100644 --- a/docs/guides.md +++ b/docs/guides.md @@ -35,6 +35,7 @@ integrating Mongoose with external tools and frameworks. * [Promises](promises.html) * [Lodash](lodash.html) * [AWS Lambda](lambda.html) +* [Next.js](nextjs.html) * [Browser Library](browser.html) * [GeoJSON](geojson.html) * [Transactions](transactions.html) diff --git a/docs/nextjs.md b/docs/nextjs.md new file mode 100644 index 00000000000..9699a14967c --- /dev/null +++ b/docs/nextjs.md @@ -0,0 +1,38 @@ +# Using Mongoose With [Next.js](https://nextjs.org/) + +Next.js is a popular framework for building full stack applications with React. +Mongoose works out of the box with Next.js. +If you're looking to get started, please use [Next.js' official Mongoose sample app](https://github.com/vercel/next.js/tree/canary/examples/with-mongodb-mongoose). +Furthermore, if you are using Next.js with [Vercel Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions), please review [Mongoose's AWS Lambda docs](https://vercel.com/docs/concepts/functions/serverless-functions). + +There are a few common issues when working with Next.js that you should be aware of. + +### TypeError: Cannot read properties of undefined (reading 'prototype') + +You can fix this issue by adding the following to your `next.config.js`: + +``` +const nextConfig = { + experimental: { + esmExternals: "loose", // <-- add this + serverComponentsExternalPackages: ["mongoose"] // <-- and this + }, + // and the following to enable top-level await support for Webpack + webpack: (config) => { + config.experiments = { + topLevelAwait: true + }; + return config; + }, +} +``` + +This issue is caused by [this change in MongoDB's bson parser](https://github.com/mongodb/js-bson/pull/564/files). +MongoDB's bson parser uses top-level await and dynamic import in ESM mode to avoid some Webpack bundling issues. +And Next.js forces ESM mode. + +### Next.js Edge Runtime + +Mongoose does **not** currently support [Next.js Edge Runtime](https://nextjs.org/docs/app/building-your-application/rendering/edge-and-nodejs-runtimes#edge-runtime). +While you can import Mongoose in Edge Runtime, you'll get [Mongoose's browser library](browser.html). +There is no way for Mongoose to connect to MongoDB in Edge Runtime, because [Edge Runtime currently doesn't support Node.js `net` API](https://edge-runtime.vercel.app/features/available-apis#unsupported-apis), which is what the MongoDB Node Driver uses to connect to MongoDB. \ No newline at end of file diff --git a/docs/source/index.js b/docs/source/index.js index de0ca191cef..6f2c1e0060b 100644 --- a/docs/source/index.js +++ b/docs/source/index.js @@ -72,6 +72,7 @@ docs['docs/transactions.md'] = { guide: true, title: 'Transactions', acquit: tru docs['docs/deprecations.md'] = { guide: true, title: 'Deprecation Warnings', markdown: true }; docs['docs/further_reading.md'] = { title: 'Further Reading', markdown: true }; docs['docs/jest.md'] = { title: 'Testing Mongoose with Jest', markdown: true }; +docs['docs/nextjs.md'] = { title: 'Using Mongoose With Next.js', markdown: true }; docs['docs/faq.md'] = { guide: true, title: 'FAQ', markdown: true }; docs['docs/typescript.md'] = { guide: true, title: 'Using TypeScript with Mongoose', markdown: true }; docs['docs/compatibility.md'] = { diff --git a/lib/connection.js b/lib/connection.js index 31d486d2c61..6a4f268b536 100644 --- a/lib/connection.js +++ b/lib/connection.js @@ -1128,9 +1128,10 @@ Connection.prototype.onClose = function(force) { }; /** - * Retrieves a collection, creating it if not cached. - * - * Not typically needed by applications. Just talk to your collection through your model. + * Retrieves a raw collection instance, creating it if not cached. + * This method returns a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)). + * Using a Collection bypasses Mongoose middleware, validation, and casting, + * letting you use [MongoDB Node.js driver](https://mongodb.github.io/node-mongodb-native/) functionality directly. * * @param {String} name of the collection * @param {Object} [options] optional collection options @@ -1582,7 +1583,7 @@ Connection.prototype.syncIndexes = async function syncIndexes(options = {}) { }; /** - * Switches to a different database using the same connection pool. + * Switches to a different database using the same [connection pool](https://mongoosejs.com/docs/api/connectionshtml#connection_pools). * * Returns a new connection object, with the new db. * diff --git a/lib/cursor/QueryCursor.js b/lib/cursor/QueryCursor.js index 59db17ce112..13f5e320a40 100644 --- a/lib/cursor/QueryCursor.js +++ b/lib/cursor/QueryCursor.js @@ -42,7 +42,6 @@ function QueryCursor(query, options) { this.cursor = null; this.skipped = false; this.query = query; - const _this = this; const model = query.model; this._mongooseOptions = {}; this._transforms = []; @@ -59,16 +58,16 @@ function QueryCursor(query, options) { util.inspect(resultValue) + '".' ); - _this._markError(err); - _this.listeners('error').length > 0 && _this.emit('error', err); + this._markError(err); + this.listeners('error').length > 0 && this.emit('error', err); return; } this.skipped = true; - _this.emit('cursor', null); + this.emit('cursor', null); return; } - _this._markError(err); - _this.listeners('error').length > 0 && _this.emit('error', err); + this._markError(err); + this.listeners('error').length > 0 && this.emit('error', err); return; } this._transforms = this._transforms.concat(query._transforms.slice()); @@ -87,17 +86,17 @@ function QueryCursor(query, options) { Object.assign(this.options, query._optionsForExec()); model.collection.find(query._conditions, this.options, (err, cursor) => { if (err != null) { - _this._markError(err); - _this.listeners('error').length > 0 && _this.emit('error', _this._error); + this._markError(err); + this.listeners('error').length > 0 && this.emit('error', this._error); return; } - if (_this._error) { + if (this._error) { cursor.close(function() {}); - _this.listeners('error').length > 0 && _this.emit('error', _this._error); + this.listeners('error').length > 0 && this.emit('error', this._error); } - _this.cursor = cursor; - _this.emit('cursor', cursor); + this.cursor = cursor; + this.emit('cursor', cursor); }); }); } @@ -113,21 +112,20 @@ util.inherits(QueryCursor, Readable); */ QueryCursor.prototype._read = function() { - const _this = this; - _next(this, function(error, doc) { + _next(this, (error, doc) => { if (error) { - return _this.emit('error', error); + return this.emit('error', error); } if (!doc) { - _this.push(null); - _this.cursor.close(function(error) { + this.push(null); + this.cursor.close(function(error) { if (error) { - return _this.emit('error', error); + return this.emit('error', error); } }); return; } - _this.push(doc); + this.push(doc); }); }; @@ -223,9 +221,8 @@ QueryCursor.prototype.close = async function close() { */ QueryCursor.prototype.rewind = function() { - const _this = this; - _waitForCursor(this, function() { - _this.cursor.rewind(); + _waitForCursor(this, () => { + this.cursor.rewind(); }); return this; }; @@ -281,14 +278,13 @@ QueryCursor.prototype.next = async function next() { */ QueryCursor.prototype.eachAsync = function(fn, opts, callback) { - const _this = this; if (typeof opts === 'function') { callback = opts; opts = {}; } opts = opts || {}; - return eachAsync(function(cb) { return _next(_this, cb); }, fn, opts, callback); + return eachAsync((cb) => _next(this, cb), fn, opts, callback); }; /** @@ -312,9 +308,8 @@ QueryCursor.prototype.options; */ QueryCursor.prototype.addCursorFlag = function(flag, value) { - const _this = this; - _waitForCursor(this, function() { - _this.cursor.addCursorFlag(flag, value); + _waitForCursor(this, () => { + this.cursor.addCursorFlag(flag, value); }); return this; }; @@ -521,13 +516,12 @@ function _populateBatch() { if (!this.ctx._batchDocs.length) { return this.callback(null, null); } - const _this = this; this.ctx.query.model.populate(this.ctx._batchDocs, this.ctx._pop).then( () => { - _nextDoc(_this.ctx, _this.ctx._batchDocs.shift(), _this.ctx._pop, _this.callback); + _nextDoc(this.ctx, this.ctx._batchDocs.shift(), this.ctx._pop, this.callback); }, err => { - _this.callback(err); + this.callback(err); } ); } diff --git a/lib/helpers/query/castUpdate.js b/lib/helpers/query/castUpdate.js index 607cc918041..36cff19e8e4 100644 --- a/lib/helpers/query/castUpdate.js +++ b/lib/helpers/query/castUpdate.js @@ -360,7 +360,7 @@ function walkUpdatePath(schema, obj, op, options, context, filter, pref) { try { if (prefix.length === 0 || key.indexOf('.') === -1) { obj[key] = castUpdateVal(schematype, val, op, key, context, prefix + key); - } else { + } else if (isStrict !== false || schematype != null) { // Setting a nested dotted path that's in the schema. We don't allow paths with '.' in // a schema, so replace the dotted path with a nested object to avoid ending up with // dotted properties in the updated object. See (gh-10200) diff --git a/lib/helpers/schema/merge.js b/lib/helpers/schema/merge.js index aeb8c453b00..e7bb91f6ae0 100644 --- a/lib/helpers/schema/merge.js +++ b/lib/helpers/schema/merge.js @@ -9,7 +9,9 @@ module.exports = function merge(s1, s2, skipConflictingPaths) { } pathsToAdd[key] = s2.tree[key]; } - s1.add(pathsToAdd); + s1.options._isMerging = true; + s1.add(pathsToAdd, null); + delete s1.options._isMerging; s1.callQueue = s1.callQueue.concat(s2.callQueue); s1.method(s2.methods); @@ -17,8 +19,7 @@ module.exports = function merge(s1, s2, skipConflictingPaths) { for (const [option, value] of Object.entries(s2._userProvidedOptions)) { if (!(option in s1._userProvidedOptions)) { - s1._userProvidedOptions[option] = value; - s1.options[option] = value; + s1.set(option, value); } } diff --git a/lib/model.js b/lib/model.js index 2f5d3a6ec3a..65e0211e715 100644 --- a/lib/model.js +++ b/lib/model.js @@ -145,7 +145,9 @@ Model.prototype.$isMongooseModelPrototype = true; Model.prototype.db; /** - * Collection the model uses. + * The collection instance this model uses. + * A Mongoose collection is a thin wrapper around a [MongoDB Node.js driver collection]([MongoDB Node.js driver collection](https://mongodb.github.io/node-mongodb-native/Next/classes/Collection.html)). + * Using `Model.collection` means you bypass Mongoose middleware, validation, and casting. * * This property is read-only. Modifying this property is a no-op. * diff --git a/lib/schema.js b/lib/schema.js index e9cb24b0f54..25a5f773639 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -1285,12 +1285,16 @@ Schema.prototype.interpretAsType = function(path, obj, options) { // new Schema({ path: [new Schema({ ... })] }) if (cast && cast.instanceOfSchema) { if (!(cast instanceof Schema)) { - throw new TypeError('Schema for array path `' + path + - '` is from a different copy of the Mongoose module. ' + - 'Please make sure you\'re using the same version ' + - 'of Mongoose everywhere with `npm list mongoose`. If you are still ' + - 'getting this error, please add `new Schema()` around the path: ' + - `${path}: new Schema(...)`); + if (this.options._isMerging) { + cast = new Schema(cast); + } else { + throw new TypeError('Schema for array path `' + path + + '` is from a different copy of the Mongoose module. ' + + 'Please make sure you\'re using the same version ' + + 'of Mongoose everywhere with `npm list mongoose`. If you are still ' + + 'getting this error, please add `new Schema()` around the path: ' + + `${path}: new Schema(...)`); + } } return new MongooseTypes.DocumentArray(path, cast, obj); } @@ -1298,12 +1302,16 @@ Schema.prototype.interpretAsType = function(path, obj, options) { cast[options.typeKey] && cast[options.typeKey].instanceOfSchema) { if (!(cast[options.typeKey] instanceof Schema)) { - throw new TypeError('Schema for array path `' + path + - '` is from a different copy of the Mongoose module. ' + - 'Please make sure you\'re using the same version ' + - 'of Mongoose everywhere with `npm list mongoose`. If you are still ' + - 'getting this error, please add `new Schema()` around the path: ' + - `${path}: new Schema(...)`); + if (this.options._isMerging) { + cast[options.typeKey] = new Schema(cast[options.typeKey]); + } else { + throw new TypeError('Schema for array path `' + path + + '` is from a different copy of the Mongoose module. ' + + 'Please make sure you\'re using the same version ' + + 'of Mongoose everywhere with `npm list mongoose`. If you are still ' + + 'getting this error, please add `new Schema()` around the path: ' + + `${path}: new Schema(...)`); + } } return new MongooseTypes.DocumentArray(path, cast[options.typeKey], obj, cast); } diff --git a/package.json b/package.json index 068e48412b9..5f7da55b605 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mongoose", "description": "Mongoose MongoDB ODM", - "version": "7.2.1", + "version": "7.2.2", "author": "Guillermo Rauch ", "keywords": [ "mongodb", @@ -21,7 +21,7 @@ "dependencies": { "bson": "^5.3.0", "kareem": "2.5.1", - "mongodb": "5.5.0", + "mongodb": "5.6.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", @@ -64,7 +64,7 @@ "q": "1.5.1", "sinon": "15.0.4", "stream-browserify": "3.0.0", - "tsd": "0.25.0", + "tsd": "0.28.1", "typescript": "5.0.4", "uuid": "9.0.0", "webpack": "5.81.0", diff --git a/test/model.findOneAndUpdate.test.js b/test/model.findOneAndUpdate.test.js index 4441b633c0f..8ab0c67244b 100644 --- a/test/model.findOneAndUpdate.test.js +++ b/test/model.findOneAndUpdate.test.js @@ -2111,12 +2111,36 @@ describe('model: findOneAndUpdate:', function() { { $set: { name: 'Test' } }, { projection: '+nickName', returnDocument: 'after' } ); + assert.equal(res.name, 'Test'); assert.equal(res.nickName, 'Quiz'); res = await Test.findOneAndDelete( { _id: entry._id }, { projection: '+nickName', returnDocument: 'before' } ); + assert.equal(res.name, 'Test'); assert.equal(res.nickName, 'Quiz'); }); + + it('allows setting paths with dots in non-strict paths (gh-13434) (gh-10200)', async function() { + const testSchema = new mongoose.Schema({ + name: String, + info: Object + }, { strict: false }); + const Test = db.model('Test', testSchema); + + const doc = await Test.findOneAndUpdate( + {}, + { + name: 'Test Testerson', + info: { 'second.name': 'Quiz' }, + info2: { 'second.name': 'Quiz' } + }, + { new: true, upsert: true } + ).lean(); + + assert.ok(doc); + assert.equal(doc.info['second.name'], 'Quiz'); + assert.equal(doc.info2['second.name'], 'Quiz'); + }); }); diff --git a/test/model.test.js b/test/model.test.js index 0abf895aa5c..2042b5e8b2b 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -3960,7 +3960,6 @@ describe('Model', function() { const M = db.model('Test', schema); - await M.create({ num: 42 }); await new Promise((resolve) => setTimeout(resolve, 10)); @@ -3978,7 +3977,29 @@ describe('Model', function() { assert.ok(doc.createdAt.valueOf() >= now.valueOf()); assert.ok(doc.updatedAt); assert.ok(doc.updatedAt.valueOf() >= now.valueOf()); + }); + + it('with timestamps from merged schema (gh-13409)', async function() { + const schema = new Schema({ num: Number }); + schema.add(new Schema({}, { timestamps: true })); + + const M = db.model('Test', schema); + + await M.create({ num: 42 }); + + await new Promise((resolve) => setTimeout(resolve, 10)); + const now = Date.now(); + await M.bulkWrite([{ + updateOne: { + filter: { num: 42 }, + update: { num: 100 } + } + }]); + + const doc = await M.findOne({ num: 100 }); + assert.ok(doc.updatedAt); + assert.ok(doc.updatedAt.valueOf() >= now.valueOf()); }); it('with child timestamps (gh-7032)', async function() { diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 00382a48993..f2de7232ea0 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -519,30 +519,24 @@ export function autoTypedSchema() { }, { statics: { staticFn() { - expectType>(this); + expectType>>(this); return 'Returned from staticFn' as const; } }, methods: { instanceFn() { - expectType>(this); + expectType>>(this); return 'Returned from DocumentInstanceFn' as const; } }, query: { byUserName(userName) { - expectAssignable>(this); + expectAssignable>>(this); return this.where({ userName }); } } }); - type InferredSchemaType = InferSchemaType; - - expectType({} as InferredSchemaType); - - expectError({} as InferredSchemaType); - return AutoTypedSchema; } diff --git a/types/index.d.ts b/types/index.d.ts index 921051536c1..55e2aa2360b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -161,7 +161,7 @@ declare module 'mongoose' { [k: string]: string; } - export interface ToObjectOptions { + export interface ToObjectOptions> { /** apply all getters (path and virtual getters) */ getters?: boolean; /** apply virtual getters (can override getters option) */ @@ -171,7 +171,11 @@ declare module 'mongoose' { /** remove empty objects (defaults to true) */ minimize?: boolean; /** if set, mongoose will call this function to allow you to transform the returned object */ - transform?: boolean | ((doc: any, ret: any, options: any) => any); + transform?: boolean | (( + doc: THydratedDocumentType, + ret: Record, + options: ToObjectOptions + ) => any); /** if true, replace any conventionally populated paths with the original id in the output. Has no affect on virtual populated paths. */ depopulate?: boolean; /** if false, exclude the version key (`__v` by default) from the output */ diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index c2db1180874..997d79054c9 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -133,14 +133,14 @@ declare module 'mongoose' { */ strictQuery?: boolean | 'throw'; /** Exactly the same as the toObject option but only applies when the document's toJSON method is called. */ - toJSON?: ToObjectOptions; + toJSON?: ToObjectOptions; /** * Documents have a toObject method which converts the mongoose document into a plain JavaScript object. * This method accepts a few options. Instead of applying these options on a per-document basis, we may * declare the options at the schema level and have them applied to all of the schema's documents by * default. */ - toObject?: ToObjectOptions; + toObject?: ToObjectOptions; /** * By default, if you have an object with key 'type' in your schema, mongoose will interpret it as a * type declaration. However, for applications like geoJSON, the 'type' property is important. If you want to diff --git a/types/types.d.ts b/types/types.d.ts index 4cc3097574c..f63a0b05ee3 100644 --- a/types/types.d.ts +++ b/types/types.d.ts @@ -42,8 +42,8 @@ declare module 'mongoose' { shift(): T; /** Returns a native js Array. */ - toObject(options?: ToObjectOptions): any; - toObject(options?: ToObjectOptions): T; + toObject(options?: ToObjectOptions): any; + toObject(options?: ToObjectOptions): T; /** Wraps [`Array#unshift`](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/unshift) with proper change tracking. */ unshift(...args: any[]): number;