@mongosh/service-provider-server
Advanced tools
Comparing version 0.11.0 to 0.12.0
@@ -13,3 +13,3 @@ import { MongoClient, ReadPreference, ClientMetadata, Topology, ReadPreferenceFromOptions, ReadPreferenceLike, OperationOptions } from 'mongodb'; | ||
declare type ExtraConnectionInfo = ReturnType<typeof getConnectInfo>; | ||
export declare function connectMongoClient(uri: string, clientOptions: MongoClientOptions, mClient?: typeof MongoClient): Promise<MongoClient>; | ||
export declare function connectMongoClient(uri: string, clientOptions: MongoClientOptions, MClient?: typeof MongoClient): Promise<MongoClient>; | ||
declare class CliServiceProvider extends ServiceProviderCore implements ServiceProvider { | ||
@@ -16,0 +16,0 @@ static connect(this: typeof CliServiceProvider, uri: string, driverOptions?: MongoClientOptions, cliOptions?: { |
@@ -22,2 +22,3 @@ "use strict"; | ||
calculateObjectSize: service_provider_core_1.bson.calculateObjectSize, | ||
EJSON: service_provider_core_1.bson.EJSON | ||
}; | ||
@@ -35,3 +36,22 @@ const DEFAULT_DRIVER_OPTIONS = Object.freeze({}); | ||
}); | ||
async function connectMongoClient(uri, clientOptions, mClient = mongodb_1.MongoClient) { | ||
async function connectWithFailFast(client) { | ||
let failFastErr; | ||
const heartbeatFailureListener = ({ failure }) => { | ||
if (service_provider_core_1.isFastFailureConnectionError(failure)) { | ||
failFastErr = failure; | ||
client.close(); | ||
} | ||
}; | ||
client.addListener('serverHeartbeatFailed', heartbeatFailureListener); | ||
try { | ||
await client.connect(); | ||
} | ||
catch (err) { | ||
throw failFastErr || err; | ||
} | ||
finally { | ||
client.removeListener('serverHeartbeatFailed', heartbeatFailureListener); | ||
} | ||
} | ||
async function connectMongoClient(uri, clientOptions, MClient = mongodb_1.MongoClient) { | ||
var _a, _b; | ||
@@ -42,3 +62,4 @@ if (clientOptions.autoEncryption !== undefined && | ||
delete optionsWithoutFLE.autoEncryption; | ||
const client = await mClient.connect(uri, optionsWithoutFLE); | ||
const client = new MClient(uri, optionsWithoutFLE); | ||
await connectWithFailFast(client); | ||
const buildInfo = await client.db('admin').admin().command({ buildInfo: 1 }); | ||
@@ -52,3 +73,5 @@ if (!((_a = buildInfo.modules) === null || _a === void 0 ? void 0 : _a.includes('enterprise')) && | ||
} | ||
return mClient.connect(uri, clientOptions); | ||
const client = new MClient(uri, clientOptions); | ||
await connectWithFailFast(client); | ||
return client; | ||
} | ||
@@ -113,11 +136,7 @@ exports.connectMongoClient = connectMongoClient; | ||
const { version } = require('../package.json'); | ||
let cmdLineOpts = null; | ||
try { | ||
cmdLineOpts = await this.runCommandWithCheck('admin', { | ||
getCmdLineOpts: 1 | ||
}, this.baseCmdOptions); | ||
} | ||
catch (e) { | ||
} | ||
const extraConnectionInfo = service_provider_core_1.getConnectInfo((_b = (_a = this.uri) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '', version, buildInfo, cmdLineOpts, topology); | ||
const [cmdLineOpts = null, atlasVersion = null] = await Promise.all([ | ||
this.runCommandWithCheck('admin', { getCmdLineOpts: 1 }, this.baseCmdOptions).catch(() => { }), | ||
this.runCommandWithCheck('admin', { atlasVersion: 1 }, this.baseCmdOptions).catch(() => { }) | ||
]); | ||
const extraConnectionInfo = service_provider_core_1.getConnectInfo((_b = (_a = this.uri) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '', version, buildInfo, cmdLineOpts, atlasVersion, topology); | ||
return { | ||
@@ -124,0 +143,0 @@ buildInfo: buildInfo, |
{ | ||
"name": "@mongosh/service-provider-server", | ||
"version": "0.11.0", | ||
"version": "0.12.0", | ||
"description": "MongoDB Shell Server Service Provider Package", | ||
@@ -19,3 +19,3 @@ "main": "lib/index.js", | ||
"prepublish": "npm run compile-ts", | ||
"lint": "eslint \"**/*.{js,ts,tsx}\"", | ||
"lint": "eslint --report-unused-disable-directives \"**/*.{js,ts,tsx}\"", | ||
"check": "npm run lint" | ||
@@ -42,4 +42,4 @@ }, | ||
"dependencies": { | ||
"@mongosh/errors": "0.11.0", | ||
"@mongosh/service-provider-core": "0.11.0", | ||
"@mongosh/errors": "0.12.0", | ||
"@mongosh/service-provider-core": "0.12.0", | ||
"@types/sinon": "^7.5.1", | ||
@@ -57,3 +57,3 @@ "@types/sinon-chai": "^3.2.3", | ||
}, | ||
"gitHead": "3f9e4337ee20219985800d99923a4903a21bfa5a" | ||
"gitHead": "0ede48aaa7f42d18eb81069ac19cc0a5d4751e69" | ||
} |
@@ -5,5 +5,6 @@ import { CommonErrors } from '@mongosh/errors'; | ||
import sinonChai from 'sinon-chai'; | ||
import sinon, { StubbedInstance, stubInterface } from 'ts-sinon'; | ||
import sinon, { StubbedInstance, stubInterface, stubConstructor } from 'ts-sinon'; | ||
import CliServiceProvider, { connectMongoClient } from './cli-service-provider'; | ||
import { ConnectionString } from '@mongosh/service-provider-core'; | ||
import { EventEmitter } from 'events'; | ||
@@ -40,13 +41,17 @@ chai.use(sinonChai); | ||
describe('connectMongoClient', () => { | ||
class FakeMongoClient extends EventEmitter { | ||
connect() {} | ||
db() {} | ||
close() {} | ||
} | ||
it('connects once when no AutoEncryption set', async() => { | ||
const uri = 'localhost:27017'; | ||
const mClientType = stubInterface<typeof MongoClient>(); | ||
const mClient = stubInterface<MongoClient>(); | ||
mClientType.connect.onFirstCall().resolves(mClient); | ||
const result = await connectMongoClient(uri, {}, mClientType); | ||
const calls = mClientType.connect.getCalls(); | ||
expect(calls.length).to.equal(1); | ||
expect(calls[0].args).to.deep.equal([ | ||
uri, {} | ||
]); | ||
const mClient = stubConstructor(FakeMongoClient); | ||
const mClientType = sinon.stub().returns(mClient); | ||
mClient.connect.onFirstCall().resolves(mClient); | ||
const result = await connectMongoClient(uri, {}, mClientType as any); | ||
expect(mClientType.getCalls()).to.have.lengthOf(1); | ||
expect(mClientType.getCalls()[0].args).to.deep.equal([uri, {}]); | ||
expect(mClient.connect.getCalls()).to.have.lengthOf(1); | ||
expect(result).to.equal(mClient); | ||
@@ -57,11 +62,9 @@ }); | ||
const opts = { autoEncryption: { bypassAutoEncryption: true } }; | ||
const mClientType = stubInterface<typeof MongoClient>(); | ||
const mClient = stubInterface<MongoClient>(); | ||
mClientType.connect.onFirstCall().resolves(mClient); | ||
const result = await connectMongoClient(uri, opts, mClientType); | ||
const calls = mClientType.connect.getCalls(); | ||
expect(calls.length).to.equal(1); | ||
expect(calls[0].args).to.deep.equal([ | ||
uri, opts | ||
]); | ||
const mClient = stubConstructor(FakeMongoClient); | ||
const mClientType = sinon.stub().returns(mClient); | ||
mClient.connect.onFirstCall().resolves(mClient); | ||
const result = await connectMongoClient(uri, opts, mClientType as any); | ||
expect(mClientType.getCalls()).to.have.lengthOf(1); | ||
expect(mClientType.getCalls()[0].args).to.deep.equal([uri, opts]); | ||
expect(mClient.connect.getCalls()).to.have.lengthOf(1); | ||
expect(result).to.equal(mClient); | ||
@@ -72,4 +75,5 @@ }); | ||
const opts = { autoEncryption: { bypassAutoEncryption: false } }; | ||
const mClientType = stubInterface<typeof MongoClient>(); | ||
const mClientFirst = stubInterface<MongoClient>(); | ||
const mClientFirst = stubConstructor(FakeMongoClient); | ||
const mClientSecond = stubConstructor(FakeMongoClient); | ||
const mClientType = sinon.stub(); | ||
const commandSpy = sinon.spy(); | ||
@@ -80,7 +84,6 @@ mClientFirst.db.returns({ admin: () => ({ command: (...args) => { | ||
} } as any) } as any); | ||
const mClientSecond = stubInterface<MongoClient>(); | ||
mClientType.connect.onFirstCall().resolves(mClientFirst); | ||
mClientType.connect.onSecondCall().resolves(mClientSecond); | ||
const result = await connectMongoClient(uri, opts, mClientType); | ||
const calls = mClientType.connect.getCalls(); | ||
mClientType.onFirstCall().returns(mClientFirst); | ||
mClientType.onSecondCall().returns(mClientSecond); | ||
const result = await connectMongoClient(uri, opts, mClientType as any); | ||
const calls = mClientType.getCalls(); | ||
expect(calls.length).to.equal(2); | ||
@@ -96,4 +99,5 @@ expect(calls[0].args).to.deep.equal([ | ||
const opts = { autoEncryption: {} }; | ||
const mClientType = stubInterface<typeof MongoClient>(); | ||
const mClientFirst = stubInterface<MongoClient>(); | ||
const mClientFirst = stubConstructor(FakeMongoClient); | ||
const mClientSecond = stubConstructor(FakeMongoClient); | ||
const mClientType = sinon.stub(); | ||
const commandSpy = sinon.spy(); | ||
@@ -104,7 +108,6 @@ mClientFirst.db.returns({ admin: () => ({ command: (...args) => { | ||
} } as any) } as any); | ||
const mClientSecond = stubInterface<MongoClient>(); | ||
mClientType.connect.onFirstCall().resolves(mClientFirst); | ||
mClientType.connect.onSecondCall().resolves(mClientSecond); | ||
mClientType.onFirstCall().returns(mClientFirst); | ||
mClientType.onSecondCall().returns(mClientSecond); | ||
try { | ||
await connectMongoClient(uri, opts, mClientType); | ||
await connectMongoClient(uri, opts, mClientType as any); | ||
} catch (e) { | ||
@@ -118,4 +121,5 @@ return expect(e.message.toLowerCase()).to.include('automatic encryption'); | ||
const opts = { autoEncryption: {} }; | ||
const mClientType = stubInterface<typeof MongoClient>(); | ||
const mClientFirst = stubInterface<MongoClient>(); | ||
const mClientFirst = stubConstructor(FakeMongoClient); | ||
const mClientSecond = stubConstructor(FakeMongoClient); | ||
const mClientType = sinon.stub(); | ||
const commandSpy = sinon.spy(); | ||
@@ -126,7 +130,6 @@ mClientFirst.db.returns({ admin: () => ({ command: (...args) => { | ||
} } as any) } as any); | ||
const mClientSecond = stubInterface<MongoClient>(); | ||
mClientType.connect.onFirstCall().resolves(mClientFirst); | ||
mClientType.connect.onSecondCall().resolves(mClientSecond); | ||
mClientType.onFirstCall().returns(mClientFirst); | ||
mClientType.onSecondCall().returns(mClientSecond); | ||
try { | ||
await connectMongoClient(uri, opts, mClientType); | ||
await connectMongoClient(uri, opts, mClientType as any); | ||
} catch (e) { | ||
@@ -137,2 +140,26 @@ return expect(e.message.toLowerCase()).to.include('automatic encryption'); | ||
}); | ||
it('fails fast if there is a fail-fast connection error', async() => { | ||
const err = Object.assign(new Error('ENOTFOUND'), { name: 'MongoNetworkError' }); | ||
const uri = 'localhost:27017'; | ||
const mClient = new FakeMongoClient(); | ||
const mClientType = sinon.stub().returns(mClient); | ||
let rejectConnect; | ||
mClient.close = sinon.stub().callsFake(() => { | ||
rejectConnect(new Error('discarded error')); | ||
}); | ||
mClient.connect = () => new Promise((resolve, reject) => { | ||
rejectConnect = reject; | ||
setImmediate(() => { | ||
mClient.emit('serverHeartbeatFailed', { failure: err }); | ||
}); | ||
}); | ||
try { | ||
await connectMongoClient(uri, {}, mClientType as any); | ||
} catch (e) { | ||
expect((mClient.close as any).getCalls()).to.have.lengthOf(1); | ||
return expect(e).to.equal(err); | ||
} | ||
expect.fail('Failed to throw expected error'); | ||
}); | ||
}); | ||
@@ -794,3 +821,3 @@ | ||
let dbStub: any; | ||
let firstCall = true; | ||
let firstCall; | ||
@@ -800,2 +827,3 @@ beforeEach(() => { | ||
clientStub = stubInterface<MongoClient>(); | ||
firstCall = true; | ||
dbStub.command.callsFake(() => { | ||
@@ -824,5 +852,5 @@ if (firstCall) { | ||
expect(info.extraInfo.is_localhost).to.equal(true); | ||
expect(dbStub.command).to.have.callCount(3); | ||
expect(dbStub.command).to.have.callCount(4); | ||
}); | ||
}); | ||
}); |
@@ -22,3 +22,4 @@ import { | ||
ReadPreferenceLike, | ||
OperationOptions | ||
OperationOptions, | ||
ServerHeartbeatFailedEvent | ||
} from 'mongodb'; | ||
@@ -79,3 +80,4 @@ | ||
FLE, | ||
AutoEncryptionOptions | ||
AutoEncryptionOptions, | ||
isFastFailureConnectionError | ||
} from '@mongosh/service-provider-core'; | ||
@@ -100,2 +102,3 @@ | ||
calculateObjectSize: BSON.calculateObjectSize, | ||
EJSON: BSON.EJSON | ||
}; | ||
@@ -137,2 +140,25 @@ | ||
/** | ||
* Takes an unconnected MongoClient and connects it, but fails fast for certain | ||
* errors. | ||
*/ | ||
async function connectWithFailFast(client: MongoClient): Promise<void> { | ||
let failFastErr; | ||
const heartbeatFailureListener = ({ failure }: ServerHeartbeatFailedEvent) => { | ||
if (isFastFailureConnectionError(failure)) { | ||
failFastErr = failure; | ||
client.close(); | ||
} | ||
}; | ||
client.addListener('serverHeartbeatFailed', heartbeatFailureListener); | ||
try { | ||
await client.connect(); | ||
} catch (err) { | ||
throw failFastErr || err; | ||
} finally { | ||
client.removeListener('serverHeartbeatFailed', heartbeatFailureListener); | ||
} | ||
} | ||
/** | ||
* Connect a MongoClient. If AutoEncryption is requested, first connect without the encryption options and verify that | ||
@@ -143,5 +169,5 @@ * the connection is to an enterprise cluster. If not, then error, otherwise close the connection and reconnect with the | ||
* @param clientOptions {MongoClientOptions} | ||
* @param mClient {MongoClient} | ||
* @param MClient {MongoClient} | ||
*/ | ||
export async function connectMongoClient(uri: string, clientOptions: MongoClientOptions, mClient = MongoClient): Promise<MongoClient> { | ||
export async function connectMongoClient(uri: string, clientOptions: MongoClientOptions, MClient = MongoClient): Promise<MongoClient> { | ||
if (clientOptions.autoEncryption !== undefined && | ||
@@ -152,3 +178,4 @@ !clientOptions.autoEncryption.bypassAutoEncryption) { | ||
delete optionsWithoutFLE.autoEncryption; | ||
const client = await mClient.connect(uri, optionsWithoutFLE); | ||
const client = new MClient(uri, optionsWithoutFLE); | ||
await connectWithFailFast(client); | ||
const buildInfo = await client.db('admin').admin().command({ buildInfo: 1 }); | ||
@@ -164,3 +191,5 @@ if ( | ||
} | ||
return mClient.connect(uri, clientOptions); | ||
const client = new MClient(uri, clientOptions); | ||
await connectWithFailFast(client); | ||
return client; | ||
} | ||
@@ -269,10 +298,6 @@ | ||
const { version } = require('../package.json'); | ||
let cmdLineOpts = null; | ||
try { | ||
cmdLineOpts = await this.runCommandWithCheck('admin', { | ||
getCmdLineOpts: 1 | ||
}, this.baseCmdOptions); | ||
// eslint-disable-next-line no-empty | ||
} catch (e) { | ||
} | ||
const [cmdLineOpts = null, atlasVersion = null] = await Promise.all([ | ||
this.runCommandWithCheck('admin', { getCmdLineOpts: 1 }, this.baseCmdOptions).catch(() => {}), | ||
this.runCommandWithCheck('admin', { atlasVersion: 1 }, this.baseCmdOptions).catch(() => {}) | ||
]); | ||
@@ -284,2 +309,3 @@ const extraConnectionInfo = getConnectInfo( | ||
cmdLineOpts, | ||
atlasVersion, | ||
topology | ||
@@ -286,0 +312,0 @@ ); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
148069
3041
+ Added@mongosh/errors@0.12.0(transitive)
+ Added@mongosh/i18n@0.12.0(transitive)
+ Added@mongosh/service-provider-core@0.12.0(transitive)
- Removed@mongosh/errors@0.11.0(transitive)
- Removed@mongosh/i18n@0.11.0(transitive)
- Removed@mongosh/service-provider-core@0.11.0(transitive)
Updated@mongosh/errors@0.12.0