From ee47b1f0061b02b89513a5b7e30c5e1a7aa46210 Mon Sep 17 00:00:00 2001 From: Eli Skeggs Date: Thu, 12 Dec 2019 20:23:15 -0800 Subject: [PATCH 1/2] feat(connect): support promise for connectionString There are cases where it's useful to have a synchronous reference to the Database, and where the connection information is not yet available - particularly in test cases where you may be launching a temporary mongo database using mongo-memory-server. To better support this use-case, the Database class can accept and store the connectionString parameter as a Promise, and resolve it to its value in the connect handler which is already well-positioned to wait for asynchronous resolution. --- README.md | 8 ++++++++ lib/database.js | 50 +++++++++++++++++++++++++++++++++++++++--------- test/database.js | 31 ++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 15e87eb..95af20e 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,14 @@ not yet opened database connection: module.exports = mongoist(connectionString); ``` +Along these same lines, connection information may not be available synchronously. The connection +information provided to `mongoist` can be contained in a `Promise`, affording for gathering the +connection information from arbitrary sources (e.g. `mongo-memory-server`): + +```javascript +module.exports = mongoist(Promise.resolve()); +``` + ## Usage _Please note: Any line in the examples that uses the await keyword should be called inside an async function. If you haven't used async/await yet, you'll want to do some research to help you understand how it works. Or take a look at https://ponyfoo.com/articles/understanding-javascript-async-await for a great read about async/await_ diff --git a/lib/database.js b/lib/database.js index 5aea9e3..f89c97a 100644 --- a/lib/database.js +++ b/lib/database.js @@ -8,19 +8,45 @@ function normalizeCommandOpts(opts) { return typeof opts === 'string' ? { [opts]: 1 } : opts; } +function normalizeConnectionString(connectionString) { + // Fix short cut connection URLs consisting only of a db name or db + host + if (connectionString.indexOf('/') < 0) { + connectionString = 'localhost:27017/' + connectionString + } + + if (connectionString.indexOf('mongodb://') < 0 && connectionString.indexOf('mongodb+srv://') < 0) { + connectionString = 'mongodb://' + connectionString + } + + return connectionString; +} + +/** + * Check whether the given value has a then method. This check classifies whether the value is a + * thenable per the Promises/A+ spec, which we use as a reasonable test for whether a given value + * is a promise. This method is preferrable to `instanceof Promise` because there are many possible + * implementations of the `Promise` prototype (such as `bluebird`, `Q` etc) that we can't + * exhaustively test for. Given the context/documentation, we don't expect other objects that we + * could confuse for promises. + * + * @param {*} value The value to check. + * @return {boolean} Whether the given value is thenable. + */ +function isThenable(value) { + return !!value && typeof value === 'object' && typeof value.then === 'function'; +} + module.exports = class Database extends EventEmitter { constructor(connectionString, options) { super(); - if (typeof connectionString == 'string') { - // Fix short cut connection URLs consisting only of a db name or db + host - if (connectionString.indexOf('/') < 0) { - connectionString = 'localhost:27017/' + connectionString - } - - if (connectionString.indexOf('mongodb://') < 0 && connectionString.indexOf('mongodb+srv://') < 0) { - connectionString = 'mongodb://' + connectionString - } + if (typeof connectionString === 'string') { + connectionString = normalizeConnectionString(connectionString); + } else if (isThenable(connectionString)) { + connectionString = connectionString.then(connectionString => + typeof connectionString === 'string' + ? normalizeConnectionString(connectionString) + : connectionString); } this.connectionString = connectionString; @@ -161,6 +187,12 @@ module.exports = class Database extends EventEmitter { return this.connection; }); + } else if (isThenable(this.connectionString)) { + return Promise.resolve(this.connectionString) + .then(connectionString => { + this.connectionString = connectionString; + return this.connect(); + }); } } diff --git a/test/database.js b/test/database.js index 1a6c351..4e3f873 100644 --- a/test/database.js +++ b/test/database.js @@ -209,6 +209,25 @@ describe('database', function() { }); + it('should allow passing in a Promise', async () => { + const db = mongoist(Promise.resolve(connectionString)); + + const docs = await db.a.find({}); + + expect(docs).to.have.length(4); + + await db.close(); + }); + + it('should normalize a Promise connectionString', async () => { + const dbShort = mongoist(Promise.resolve('localhost:27017/test')); + const docs = await dbShort.a.find(); + + expect(docs).to.have.length(4); + + await dbShort.close(); + }); + it('should allow passing in a mongojs connection', async() => { const mongojsDb = mongojs(connectionString); const db = mongoist(mongojsDb); @@ -221,6 +240,18 @@ describe('database', function() { await db.close(); }); + it('should allow passing in a Promise that resolves to a mongojs connection', async() => { + const mongojsDb = mongojs(connectionString); + const db = mongoist(Promise.resolve(mongojsDb)); + + const docs = await db.a.find({}); + + expect(docs).to.have.length(4); + + await mongojsDb.close(); + await db.close(); + }); + it('should pass projections to mongojs connections using mongodb 2.x driver', async() => { const mongojsDb = mongojs(connectionString); const db = mongoist(mongojsDb); From a0f3a5911ee50bdf9bb65adb50110adc2505dd27 Mon Sep 17 00:00:00 2001 From: Eli Skeggs Date: Thu, 12 Dec 2019 20:31:44 -0800 Subject: [PATCH 2/2] style: use semicolons consistently --- lib/database.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/database.js b/lib/database.js index f89c97a..e13c2e6 100644 --- a/lib/database.js +++ b/lib/database.js @@ -11,11 +11,11 @@ function normalizeCommandOpts(opts) { function normalizeConnectionString(connectionString) { // Fix short cut connection URLs consisting only of a db name or db + host if (connectionString.indexOf('/') < 0) { - connectionString = 'localhost:27017/' + connectionString + connectionString = 'localhost:27017/' + connectionString; } if (connectionString.indexOf('mongodb://') < 0 && connectionString.indexOf('mongodb+srv://') < 0) { - connectionString = 'mongodb://' + connectionString + connectionString = 'mongodb://' + connectionString; } return connectionString;