Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update mongodb driver 5.6.0 #13455

Merged
merged 25 commits into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
30dab2e
test(findOneAndUpdate): add extra assertions from code review for #13437
vkarpov15 May 25, 2023
eeb7102
refactor(cursor): use arrow functions instead of `_this` throughout
vkarpov15 May 25, 2023
9e12770
Merge pull request #13439 from Automattic/vkarpov15/gh-13413-code-review
AbdelrahmanHafez May 25, 2023
c86b0ff
fix(schema): recursively copy schemas from different modules when cal…
vkarpov15 May 25, 2023
ca9477f
docs: add nextjs page with link to next starter app and couple FAQs
vkarpov15 May 26, 2023
dc11279
Merge pull request #13440 from Automattic/vkarpov15/gh-13436-code-review
vkarpov15 May 26, 2023
09303ad
Merge pull request #13444 from Automattic/vkarpov15/gh-13430
vkarpov15 May 26, 2023
a29b45e
Merge pull request #13441 from Automattic/vkarpov15/gh-13275
vkarpov15 May 26, 2023
c6db830
fix(schema): make bulkWrite updateOne() and updateMany() respect time…
vkarpov15 May 26, 2023
ef62c1d
types: improve function parameter types for `ToObjectOptions` transfo…
vkarpov15 May 26, 2023
fc35851
test: fix tests with tsd@0.28.1
vkarpov15 May 26, 2023
c0b40a2
Merge pull request #13445 from Automattic/vkarpov15/gh-13409
vkarpov15 May 27, 2023
f9e8544
Merge pull request #13446 from Automattic/vkarpov15/gh-13421
vkarpov15 May 27, 2023
c8c9c1a
docs(connection+model): expand docs on accessors for underlying colle…
vkarpov15 May 27, 2023
b09cc83
chore: use node 14 for tsd tests because some upstream dep of tsd use…
vkarpov15 May 27, 2023
72b606f
Merge pull request #13447 from Automattic/vkarpov15/gh-13235
vkarpov15 May 27, 2023
0793df0
docs(connections): add section on multi tenant
vkarpov15 May 28, 2023
3511e23
fix(update): allow setting paths with dots under non-strict paths
vkarpov15 May 28, 2023
15164f2
Merge pull request #13450 from Automattic/vkarpov15/gh-13434
vkarpov15 May 30, 2023
a77e3b6
Update lib/connection.js
vkarpov15 May 30, 2023
e069b6e
Merge pull request #13449 from Automattic/vkarpov15/gh-11187
vkarpov15 May 30, 2023
baa4acc
Merge pull request #13448 from Automattic/vkarpov15/gh-13334
vkarpov15 May 30, 2023
4339ded
chore: release 7.2.2
vkarpov15 May 30, 2023
cdaa9cf
Update mongodb driver 5.6.0
lorand-horvath Jun 1, 2023
b8e7899
Update package.json
vkarpov15 Jun 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tsd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
86 changes: 86 additions & 0 deletions docs/connections.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ See the [mongodb connection string spec](http://www.mongodb.com/docs/manual/refe
<li><a href="#mongos_connections">Multi-mongos support</a></li>
<li><a href="#multiple_connections">Multiple connections</a></li>
<li><a href="#connection_pools">Connection Pools</a></li>
<li><a href="#multi-tenant-connections">Multi Tenant Connections</a></li>
</ul>

<h2 id="buffering"><a href="#buffering">Operation Buffering</a></h2>
Expand Down Expand Up @@ -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.

<h2 id="multi-tenant-connections"><a href="#multi-tenant-connections">Multi Tenant Connections</a></h2>

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);
```

<h2 id="next">Next Up</h2>

Now that we've covered connections, let's take a look at [models](models.html).
1 change: 1 addition & 0 deletions docs/guides.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions docs/nextjs.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions docs/source/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'] = {
Expand Down
9 changes: 5 additions & 4 deletions lib/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand Down
54 changes: 24 additions & 30 deletions lib/cursor/QueryCursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand All @@ -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());
Expand All @@ -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);
});
});
}
Expand All @@ -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);
});
};

Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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);
};

/**
Expand All @@ -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;
};
Expand Down Expand Up @@ -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);
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/helpers/query/castUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions lib/helpers/schema/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ 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);
s1.static(s2.statics);

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);
}
}

Expand Down
4 changes: 3 additions & 1 deletion lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Loading