@e22m4u/js-repository-mongodb-adapter
Advanced tools
Comparing version 0.0.15 to 0.0.16
{ | ||
"name": "@e22m4u/js-repository-mongodb-adapter", | ||
"version": "0.0.15", | ||
"version": "0.0.16", | ||
"description": "MongoDB адаптер для @e22m4u/js-repository", | ||
@@ -5,0 +5,0 @@ "type": "module", |
/* eslint no-unused-vars: 0 */ | ||
import {ObjectId} from 'mongodb'; | ||
import {MongoClient} from 'mongodb'; | ||
import {EventEmitter} from 'events'; | ||
import {waitAsync} from './utils/index.js'; | ||
import {isObjectId} from './utils/index.js'; | ||
@@ -72,2 +74,37 @@ import {Adapter} from '@e22m4u/js-repository'; | ||
/** | ||
* Mongo client events. | ||
* 5.8.1 | ||
* | ||
* @type {string[]} | ||
*/ | ||
const MONGO_CLIENT_EVENTS = [ | ||
'connectionPoolCreated', | ||
'connectionPoolReady', | ||
'connectionPoolCleared', | ||
'connectionPoolClosed', | ||
'connectionCreated', | ||
'connectionReady', | ||
'connectionClosed', | ||
'connectionCheckOutStarted', | ||
'connectionCheckOutFailed', | ||
'connectionCheckedOut', | ||
'connectionCheckedIn', | ||
'commandStarted', | ||
'commandSucceeded', | ||
'commandFailed', | ||
'serverOpening', | ||
'serverClosed', | ||
'serverDescriptionChanged', | ||
'topologyOpening', | ||
'topologyClosed', | ||
'topologyDescriptionChanged', | ||
'error', | ||
'timeout', | ||
'close', | ||
'serverHeartbeatStarted', | ||
'serverHeartbeatSucceeded', | ||
'serverHeartbeatFailed', | ||
]; | ||
/** | ||
* Default settings. | ||
@@ -137,2 +174,25 @@ * | ||
/** | ||
* Event emitter. | ||
* | ||
* @private | ||
*/ | ||
_emitter; | ||
/** | ||
* Event emitter. | ||
* | ||
* @returns {EventEmitter} | ||
*/ | ||
get emitter() { | ||
if (this._emitter) return this._emitter; | ||
this._emitter = new EventEmitter(); | ||
const emit = this._emitter.emit; | ||
this._emitter.emit = function (name, ...args) { | ||
emit.call(this, '*', name, ...args); | ||
return emit.call(this, name, ...args); | ||
}; | ||
return this._emitter; | ||
} | ||
/** | ||
* Constructor. | ||
@@ -145,7 +205,7 @@ * | ||
settings = Object.assign({}, DEFAULT_SETTINGS, settings || {}); | ||
super(container, settings); | ||
settings.protocol = settings.protocol || 'mongodb'; | ||
settings.hostname = settings.hostname || settings.host || '127.0.0.1'; | ||
settings.port = settings.port || 27017; | ||
settings.database = settings.database || settings.db || 'test'; | ||
settings.database = settings.database || settings.db || 'database'; | ||
super(container, settings); | ||
} | ||
@@ -161,4 +221,3 @@ | ||
if (this._connecting) { | ||
const tryAgainAfter = 500; | ||
await new Promise(r => setTimeout(() => r(), tryAgainAfter)); | ||
await waitAsync(500); | ||
return this.connect(); | ||
@@ -174,13 +233,24 @@ } | ||
// console.log(`Connecting to ${url}`); | ||
if (this._client) { | ||
this._client.removeAllListeners(); | ||
this._client.close(true); | ||
} | ||
this._client = new MongoClient(url, options); | ||
for (const event of MONGO_CLIENT_EVENTS) { | ||
const listener = (...args) => this.emitter.emit(event, ...args); | ||
this._client.on(event, listener); | ||
} | ||
const {reconnectInterval} = this.settings; | ||
const connectFn = async () => { | ||
if (this._connecting === false) return; | ||
this.emitter.emit('connecting'); | ||
try { | ||
await this._client.connect(); | ||
} catch (e) { | ||
this.emitter.emit('error', e); | ||
console.error(e); | ||
// console.log('MongoDB connection failed!'); | ||
// console.log(`Reconnecting after ${reconnectInterval} ms.`); | ||
await new Promise(r => setTimeout(() => r(), reconnectInterval)); | ||
await waitAsync(reconnectInterval); | ||
return connectFn(); | ||
@@ -191,17 +261,22 @@ } | ||
this._connecting = false; | ||
reconnectOnClose(); | ||
this.emitter.emit('connected'); | ||
}; | ||
await connectFn(); | ||
const reconnectOnClose = () => | ||
this._client.once('serverClosed', event => { | ||
this.emitter.emit('disconnected', event); | ||
if (this._connected) { | ||
this._connected = false; | ||
this._connecting = true; | ||
// console.log('MongoDB lost connection!'); | ||
// console.log(event); | ||
// console.log(`Reconnecting after ${reconnectInterval} ms.`); | ||
setTimeout(() => connectFn(), reconnectInterval); | ||
} else { | ||
// console.log('MongoDB connection closed.'); | ||
} | ||
}); | ||
this._client.once('serverClosed', event => { | ||
if (this._connected) { | ||
this._connected = false; | ||
// console.log('MongoDB lost connection!'); | ||
console.log(event); | ||
// console.log(`Reconnecting after ${reconnectInterval} ms.`); | ||
setTimeout(() => connectFn(), reconnectInterval); | ||
} else { | ||
// console.log('MongoDB connection closed.'); | ||
} | ||
}); | ||
return connectFn(); | ||
} | ||
@@ -212,13 +287,13 @@ | ||
* | ||
* @return {Promise<*|undefined>} | ||
* @return {Promise<undefined>} | ||
*/ | ||
async disconnect() { | ||
if (this._connecting) { | ||
const tryAgainAfter = 500; | ||
await new Promise(r => setTimeout(() => r(), tryAgainAfter)); | ||
return this.disconnect(); | ||
this._connected = false; | ||
this._connecting = false; | ||
if (this._client) { | ||
const client = this._client; | ||
this._client = undefined; | ||
await client.close(); | ||
client.removeAllListeners(); | ||
} | ||
if (!this._connected) return; | ||
this._connected = false; | ||
if (this._client) await this._client.close(); | ||
} | ||
@@ -225,0 +300,0 @@ |
@@ -14,3 +14,3 @@ import {InvalidArgumentError} from '@e22m4u/js-repository'; | ||
throw new InvalidArgumentError( | ||
'MongoDB option "protocol" must be a string, but %v given.', | ||
'MongoDB option "protocol" must be a String, but %v given.', | ||
options.protocol, | ||
@@ -17,0 +17,0 @@ ); |
@@ -11,7 +11,8 @@ import {expect} from 'chai'; | ||
it('throws an error when the first argument is a non-object value', function () { | ||
it('requires the first argument to be an object', function () { | ||
const throwable = v => () => createMongodbUrl(v); | ||
const error = v => | ||
format( | ||
'The first argument of "createMongodbUrl" must be an Object, but %s given.', | ||
'The first argument of "createMongodbUrl" must be ' + | ||
'an Object, but %s given.', | ||
v, | ||
@@ -30,2 +31,255 @@ ); | ||
}); | ||
it('requires the "protocol" option to be a string', function () { | ||
const throwable = v => () => createMongodbUrl({protocol: v}); | ||
const error = v => | ||
format( | ||
'MongoDB option "protocol" must be ' + 'a String, but %s given.', | ||
v, | ||
); | ||
expect(throwable(10)).to.throw(error('10')); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('mongodb')(); | ||
throwable('')(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the "hostname" option to be a string', function () { | ||
const throwable = v => () => createMongodbUrl({hostname: v}); | ||
const error = v => | ||
format( | ||
'MongoDB option "hostname" must be ' + 'a String, but %s given.', | ||
v, | ||
); | ||
expect(throwable(10)).to.throw(error('10')); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('127.0.0.1')(); | ||
throwable('')(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the "host" option to be a string', function () { | ||
const throwable = v => () => createMongodbUrl({host: v}); | ||
const error = v => | ||
format('MongoDB option "host" must be ' + 'a String, but %s given.', v); | ||
expect(throwable(10)).to.throw(error('10')); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('127.0.0.1')(); | ||
throwable('')(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the "port" option to be a number or a string', function () { | ||
const throwable = v => () => createMongodbUrl({port: v}); | ||
const error = v => | ||
format( | ||
'MongoDB option "port" must be a Number ' + | ||
'or a String, but %s given.', | ||
v, | ||
); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('127.0.0.1')(); | ||
throwable('')(); | ||
throwable(10)(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the "database" option to be a string', function () { | ||
const throwable = v => () => createMongodbUrl({database: v}); | ||
const error = v => | ||
format( | ||
'MongoDB option "database" must be ' + 'a String, but %s given.', | ||
v, | ||
); | ||
expect(throwable(10)).to.throw(error('10')); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('database')(); | ||
throwable('')(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the "db" option to be a string', function () { | ||
const throwable = v => () => createMongodbUrl({db: v}); | ||
const error = v => | ||
format('MongoDB option "db" must be ' + 'a String, but %s given.', v); | ||
expect(throwable(10)).to.throw(error('10')); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('database')(); | ||
throwable('')(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the "username" option to be a string', function () { | ||
const throwable = v => () => createMongodbUrl({username: v}); | ||
const error = v => | ||
format( | ||
'MongoDB option "username" must be ' + 'a String, but %s given.', | ||
v, | ||
); | ||
expect(throwable(10)).to.throw(error('10')); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('username')(); | ||
throwable('')(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the "password" option to be a string or a number', function () { | ||
const throwable = v => () => createMongodbUrl({password: v}); | ||
const error = v => | ||
format( | ||
'MongoDB option "password" must be a String ' + | ||
'or a Number, but %s given.', | ||
v, | ||
); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('password')(); | ||
throwable('')(); | ||
throwable(10)(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('requires the "pass" option to be a string or a number', function () { | ||
const throwable = v => () => createMongodbUrl({pass: v}); | ||
const error = v => | ||
format( | ||
'MongoDB option "pass" must be a String ' + | ||
'or a Number, but %s given.', | ||
v, | ||
); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
throwable('pass')(); | ||
throwable('')(); | ||
throwable(10)(); | ||
throwable(0)(); | ||
throwable(false)(); | ||
throwable(undefined)(); | ||
throwable(null)(); | ||
}); | ||
it('sets the given "protocol" option', function () { | ||
const res = createMongodbUrl({protocol: 'value'}); | ||
expect(res).to.be.eq('value://127.0.0.1:27017/database'); | ||
}); | ||
it('sets the given "hostname" option', function () { | ||
const res = createMongodbUrl({hostname: 'value'}); | ||
expect(res).to.be.eq('mongodb://value:27017/database'); | ||
}); | ||
it('sets the given "host" option', function () { | ||
const res = createMongodbUrl({host: 'value'}); | ||
expect(res).to.be.eq('mongodb://value:27017/database'); | ||
}); | ||
it('sets the given "port" option as a number', function () { | ||
const res = createMongodbUrl({port: 8080}); | ||
expect(res).to.be.eq('mongodb://127.0.0.1:8080/database'); | ||
}); | ||
it('sets the given "port" option as a string', function () { | ||
const res = createMongodbUrl({port: '8080'}); | ||
expect(res).to.be.eq('mongodb://127.0.0.1:8080/database'); | ||
}); | ||
it('sets the given "database" option', function () { | ||
const res = createMongodbUrl({database: 'value'}); | ||
expect(res).to.be.eq('mongodb://127.0.0.1:27017/value'); | ||
}); | ||
it('sets the given "db" option', function () { | ||
const res = createMongodbUrl({db: 'value'}); | ||
expect(res).to.be.eq('mongodb://127.0.0.1:27017/value'); | ||
}); | ||
it('does not use the provided "username" option without a password', function () { | ||
const res = createMongodbUrl({username: 'value'}); | ||
expect(res).to.be.eq('mongodb://127.0.0.1:27017/database'); | ||
}); | ||
it('does not use the provided "user" option without a password', function () { | ||
const res = createMongodbUrl({username: 'value'}); | ||
expect(res).to.be.eq('mongodb://127.0.0.1:27017/database'); | ||
}); | ||
it('does not use the provided "password" option without a username', function () { | ||
const res = createMongodbUrl({password: 'value'}); | ||
expect(res).to.be.eq('mongodb://127.0.0.1:27017/database'); | ||
}); | ||
it('does not use the provided "pass" option without a username', function () { | ||
const res = createMongodbUrl({pass: 'value'}); | ||
expect(res).to.be.eq('mongodb://127.0.0.1:27017/database'); | ||
}); | ||
it('sets the given "username" and "password" option', function () { | ||
const res = createMongodbUrl({username: 'usr', password: 'pwd'}); | ||
expect(res).to.be.eq('mongodb://usr:pwd@127.0.0.1:27017/database'); | ||
}); | ||
it('sets the given "username" and "pass" option', function () { | ||
const res = createMongodbUrl({username: 'usr', pass: 'pwd'}); | ||
expect(res).to.be.eq('mongodb://usr:pwd@127.0.0.1:27017/database'); | ||
}); | ||
it('sets the given "user" and "password" option', function () { | ||
const res = createMongodbUrl({user: 'usr', password: 'pwd'}); | ||
expect(res).to.be.eq('mongodb://usr:pwd@127.0.0.1:27017/database'); | ||
}); | ||
it('sets the given "user" and "pass" option', function () { | ||
const res = createMongodbUrl({user: 'usr', pass: 'pwd'}); | ||
expect(res).to.be.eq('mongodb://usr:pwd@127.0.0.1:27017/database'); | ||
}); | ||
it('does not use the default "port" option for "mongodb+srv" protocol', function () { | ||
const res = createMongodbUrl({protocol: 'mongodb+srv'}); | ||
expect(res).to.be.eq('mongodb+srv://127.0.0.1/database'); | ||
}); | ||
it('does not use the provided "port" option for "mongodb+srv" protocol', function () { | ||
const res = createMongodbUrl({protocol: 'mongodb+srv', port: 8080}); | ||
expect(res).to.be.eq('mongodb+srv://127.0.0.1/database'); | ||
}); | ||
}); |
@@ -0,3 +1,4 @@ | ||
export * from './wait-async.js'; | ||
export * from './is-object-id.js'; | ||
export * from './create-mongodb-url.js'; | ||
export * from './transform-values-deep.js'; |
import {expect} from 'chai'; | ||
import {ObjectId} from 'mongodb'; | ||
import {isObjectId} from './is-object-id.js'; | ||
import {ObjectId} from 'mongodb'; | ||
describe('isObjectId', function () { | ||
it('returns true for a valid ObjectId string or an instance', function () { | ||
expect(isObjectId(new ObjectId())).to.be.true; | ||
expect(isObjectId(String(new ObjectId()))).to.be.true; | ||
}); | ||
it('returns false for invalid values', function () { | ||
expect(isObjectId('')).to.be.false; | ||
@@ -20,6 +25,3 @@ expect(isObjectId('123')).to.be.false; | ||
expect(isObjectId(undefined)).to.be.false; | ||
// | ||
expect(isObjectId(new ObjectId())).to.be.true; | ||
expect(isObjectId(String(new ObjectId()))).to.be.true; | ||
}); | ||
}); |
@@ -14,3 +14,3 @@ import {InvalidArgumentError} from '@e22m4u/js-repository'; | ||
'The second argument of "transformValuesDeep" ' + | ||
'must be a Function, but %s given.', | ||
'must be a Function, but %v given.', | ||
transformer, | ||
@@ -17,0 +17,0 @@ ); |
import {expect} from 'chai'; | ||
import {format} from '@e22m4u/js-format'; | ||
import {transformValuesDeep} from './transform-values-deep.js'; | ||
describe('transformValuesDeep', function () { | ||
it('transforms property values of an object', function () { | ||
it('transforms property values of the given object', function () { | ||
const object = { | ||
@@ -27,3 +28,3 @@ foo: 1, | ||
it('transforms elements of an array', function () { | ||
it('transforms elements of the given array', function () { | ||
const object = [1, 2, 3, [4, 5, 6, [7, 8, 9]]]; | ||
@@ -34,3 +35,3 @@ const result = transformValuesDeep(object, String); | ||
it('transforms non-pure objects', function () { | ||
it('transforms the Date instance', function () { | ||
const date = new Date(); | ||
@@ -49,2 +50,24 @@ const str = String(date); | ||
}); | ||
it('requires the second argument to be a function', function () { | ||
const throwable = v => () => transformValuesDeep('val', v); | ||
const error = v => | ||
format( | ||
'The second argument of "transformValuesDeep" ' + | ||
'must be a Function, but %s given.', | ||
v, | ||
); | ||
expect(throwable('str')).to.throw(error('"str"')); | ||
expect(throwable('')).to.throw(error('""')); | ||
expect(throwable(10)).to.throw(error('10')); | ||
expect(throwable(0)).to.throw(error('0')); | ||
expect(throwable(true)).to.throw(error('true')); | ||
expect(throwable(false)).to.throw(error('false')); | ||
expect(throwable([])).to.throw(error('Array')); | ||
expect(throwable({})).to.throw(error('Object')); | ||
expect(throwable(undefined)).to.throw(error('undefined')); | ||
expect(throwable(null)).to.throw(error('null')); | ||
throwable(() => undefined)(); | ||
throwable(function () {})(); | ||
}); | ||
}); |
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
150159
28
3852
1