diff --git a/.eslintrc b/.eslintrc index c07a4ea1..b418bd8c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "env": { - "node": true + "node": true, + "es2020": true }, "extends": "eslint:recommended", "rules": { @@ -10,6 +11,7 @@ "no-console": "off", "no-trailing-spaces": ["error", { "skipBlankLines": true }], "no-unused-vars": "warn", + "no-var": "warn", "quotes": ["warn", "single", "avoid-escape"], "semi": ["error", "always"], "space-before-blocks": "error" diff --git a/.travis.yml b/.travis.yml index 4756ac35..826217cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,9 @@ language: node_js sudo: required dist: trusty node_js: - - "4" - "8" + - "12" services: - - mysql - docker before_script: - npm run lint diff --git a/README.md b/README.md index def570d5..9e3f70e4 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,18 @@ A MySQL binlog listener running on Node.js. ZongJi (踪迹) is pronounced as `zōng jì` in Chinese. -This package is a "pure JS" implementation based on [`node-mysql`](https://github.com/felixge/node-mysql). Since v0.2.0, The native part (which was written in C++) has been dropped. +This package is a pure JS implementation based on [`mysql`](https://github.com/mysqljs/mysql). It has been tested to work in MySQL 5.5, 5.6, and 5.7. -This package has been tested to work in MySQL 5.5, 5.6, and 5.7. +# Latest Release + +The latest release is v0.5.0, only supports Node.js from v8. + +v0.4.7 is the last release which supports Node.js v4.x. ## Quick Start ```javascript -var zongji = new ZongJi({ /* ... MySQL Connection Settings ... */ }); +let zongji = new ZongJi({ /* ... MySQL Connection Settings ... */ }); // Each change to the replication log results in an event zongji.on('binlog', function(evt) { @@ -27,7 +31,7 @@ For a complete implementation see [`example.js`](example.js)... ## Installation -* Requires Node.js v4+ +* Requires Node.js v8+ ```bash $ npm install zongji @@ -59,12 +63,12 @@ For a complete implementation see [`example.js`](example.js)... The `ZongJi` constructor accepts one argument of either: -* An object containing MySQL connection details in the same format as used by `node-mysql` -* Or, a `node-mysql` `Connection` or `Pool` object that will be used for querying column information. +* An object containing MySQL connection details in the same format as used by [package mysql](https://npm.im/mysql) +* Or, a [mysql](https://npm.im/mysql) `Connection` or `Pool` object that will be used for querying column information. If a `Connection` or `Pool` object is passed to the constructor, it will not be destroyed/ended by Zongji's `stop()` method. -If there is a `dateStrings` `node-mysql` configuration option in the connection details or connection, `ZongJi` will follow it. +If there is a `dateStrings` `mysql` configuration option in the connection details or connection, `ZongJi` will follow it. Each instance includes the following methods: @@ -72,17 +76,25 @@ Method Name | Arguments | Description ------------|-----------|------------------------ `start` | `options` | Start receiving replication events, see options listed below `stop` | *None* | Disconnect from MySQL server, stop receiving events -`set` | `options` | Change options after `start()` `on` | `eventName`, `handler` | Add a listener to the `binlog` or `error` event. Each handler function accepts one argument. +Some events can be emitted in different phases: + +Event Name | Description +-----------|------------------------ +`ready` | This event is occurred right after ZongJi successfully established a connection, setup slave status, and set binlog position. +`binlog` | Once a binlog is received and passes the filter, it will bubble up with this event. +`error` | Every error will be caught by this event. +`stopped` | Emitted when ZongJi connection is stopped (ZongJi#stop is called). + **Options available:** Option Name | Type | Description ------------|------|------------------------------- `serverId` | `integer` | [Unique number (1 - 232)](http://dev.mysql.com/doc/refman/5.0/en/replication-options.html#option_mysqld_server-id) to identify this replication slave instance. Must be specified if running more than one instance of ZongJi. Must be used in `start()` method for effect.
**Default:** `1` `startAtEnd` | `boolean` | Pass `true` to only emit binlog events that occur after ZongJi's instantiation. Must be used in `start()` method for effect.
**Default:** `false` -`binlogName` | `string` | Begin reading events from this binlog file. If specified together with `binlogNextPos`, will take precedence over `startAtEnd`. -`binlogNextPos` | `integer` | Begin reading events from this position. Must be included with `binlogName`. +`filename` | `string` | Begin reading events from this binlog file. If specified together with `position`, will take precedence over `startAtEnd`. +`position` | `integer` | Begin reading events from this position. Must be included with `filename`. `includeEvents` | `[string]` | Array of event names to include
**Example:** `['writerows', 'updaterows', 'deleterows']` `excludeEvents` | `[string]` | Array of event names to exclude
**Example:** `['rotate', 'tablemap']` `includeSchema` | `object` | Object describing which databases and tables to include (Only for row events). Use database names as the key and pass an array of table names or `true` (for the entire database).
**Example:** ```{ 'my_database': ['allow_table', 'another_table'], 'another_db': true }``` @@ -98,7 +110,7 @@ Event name | Description `unknown` | Catch any other events `query` | [Insert/Update/Delete Query](http://dev.mysql.com/doc/internals/en/query-event.html) `intvar` | [Autoincrement and LAST_INSERT_ID](https://dev.mysql.com/doc/internals/en/intvar-event.html) -`rotate` | [New Binlog file](http://dev.mysql.com/doc/internals/en/rotate-event.html) Not required to be included to rotate to new files, but it is required to be included in order to keep the `binlogName` and `binlogNextPos` properties updated with current values for [graceful restarting on errors](https://gist.github.com/numtel/5b37b2a7f47b380c1a099596c6f3db2f). +`rotate` | [New Binlog file](http://dev.mysql.com/doc/internals/en/rotate-event.html) Not required to be included to rotate to new files, but it is required to be included in order to keep the `filename` and `position` properties updated with current values for [graceful restarting on errors](https://gist.github.com/numtel/5b37b2a7f47b380c1a099596c6f3db2f). `format` | [Format Description](http://dev.mysql.com/doc/internals/en/format-description-event.html) `xid` | [Transaction ID](http://dev.mysql.com/doc/internals/en/xid-event.html) `tablemap` | Before any row event (must be included for any other row events) @@ -117,8 +129,8 @@ Name | Description ## Important Notes -* :star2: [All types allowed by `node-mysql`](https://github.com/felixge/node-mysql#type-casting) are supported by this package. -* :speak_no_evil: While 64-bit integers in MySQL (`BIGINT` type) allow values in the range of 264 (± ½ × 264 for signed values), Javascript's internal storage of numbers limits values to 253, making the allowed range of `BIGINT` fields only `-9007199254740992` to `9007199254740992`. Unsigned 64-bit integers must also not exceed `9007199254740992`. +* :star2: [All types allowed by `mysql`](https://github.com/mysqljs/mysql#type-casting) are supported by this package. +* :speak_no_evil: 64-bit integer is supported via package big-integer(see #108). If an integer is within the safe range of JS number (-2^53, 2^53), a Number object will returned, otherwise, will return as String. * :point_right: `TRUNCATE` statement does not cause corresponding `DeleteRows` event. Use unqualified `DELETE FROM` for same effect. * When using fractional seconds with `DATETIME` and `TIMESTAMP` data types in MySQL > 5.6.4, only millisecond precision is available due to the limit of Javascript's `Date` object. @@ -131,7 +143,7 @@ Name | Description I learnt many things from following resources while making ZongJi. -* https://github.com/felixge/node-mysql +* https://github.com/mysqljs/mysql * https://github.com/felixge/faster-than-c/ * http://intuitive-search.blogspot.co.uk/2011/07/binary-log-api-and-replication-listener.html * https://github.com/Sannis/node-mysql-libmysqlclient diff --git a/docker-test.sh b/docker-test.sh index 0c4b3cbd..3b21fc5f 100755 --- a/docker-test.sh +++ b/docker-test.sh @@ -2,6 +2,10 @@ MYSQL_HOSTS="mysql55 mysql56 mysql57" for hostname in ${MYSQL_HOSTS}; do - echo $hostname + echo $hostname + node 8 docker run -it --network=zongji_default -e MYSQL_HOST=$hostname -w /build -v $PWD:/build node:8 npm test + echo $hostname + node 10 + docker run -it --network=zongji_default -e MYSQL_HOST=$hostname -w /build -v $PWD:/build node:10 npm test + echo $hostname + node 12 + docker run -it --network=zongji_default -e MYSQL_HOST=$hostname -w /build -v $PWD:/build node:12 npm test done diff --git a/example.js b/example.js index 636dfcf6..361e11f5 100644 --- a/example.js +++ b/example.js @@ -1,7 +1,7 @@ // Client code -var ZongJi = require('./'); +const ZongJi = require('./'); -var zongji = new ZongJi({ +const zongji = new ZongJi({ host : 'localhost', user : 'zongji', password : 'zongji', diff --git a/index.js b/index.js index 16ab104e..db6746f9 100644 --- a/index.js +++ b/index.js @@ -1,210 +1,139 @@ -var mysql = require('mysql'); -var Connection = require('mysql/lib/Connection'); -var Pool = require('mysql/lib/Pool'); - -var util = require('util'); -var EventEmitter = require('events').EventEmitter; -var generateBinlog = require('./lib/sequence/binlog'); - -var alternateDsn = [ - { type: Connection, config: function(obj) { return obj.config; } }, - { type: Pool, config: function(obj) { return obj.config.connectionConfig; } } -]; +const mysql = require('mysql'); +const util = require('util'); +const EventEmitter = require('events').EventEmitter; +const initBinlogClass = require('./lib/sequence/binlog'); + +const ConnectionConfigMap = { + 'Connection': obj => obj.config, + 'Pool': obj => obj.config.connectionConfig, +}; -function ZongJi(dsn, options) { - this.set(options); +const TableInfoQueryTemplate = 'SELECT ' + + 'COLUMN_NAME, COLLATION_NAME, CHARACTER_SET_NAME, ' + + 'COLUMN_COMMENT, COLUMN_TYPE ' + + 'FROM information_schema.columns ' + "WHERE table_schema='%s' AND table_name='%s'"; +function ZongJi(dsn) { EventEmitter.call(this); - var binlogDsn; - - // one connection to send table info query - // Check first argument against possible connection objects - for (var i = 0; i < alternateDsn.length; i++) { - if (dsn instanceof alternateDsn[i].type) { - this.ctrlConnection = dsn; - this.ctrlConnectionOwner = false; - binlogDsn = cloneObjectSimple(alternateDsn[i].config(dsn)); - } - } - - if (!binlogDsn) { - // assuming that the object passed is the connection settings - var ctrlDsn = cloneObjectSimple(dsn); - this.ctrlConnection = mysql.createConnection(ctrlDsn); - this.ctrlConnection.on('error', this._emitError.bind(this)); - this.ctrlConnection.on('unhandledError', this._emitError.bind(this)); - this.ctrlConnection.connect(); - this.ctrlConnectionOwner = true; - - binlogDsn = dsn; - } + this._options({}); + this._filters({}); this.ctrlCallbacks = []; - - this.connection = mysql.createConnection(binlogDsn); - this.connection.on('error', this._emitError.bind(this)); - this.connection.on('unhandledError', this._emitError.bind(this)); - this.tableMap = {}; this.ready = false; this.useChecksum = false; - // Include 'rotate' events to keep these properties updated - this.binlogName = null; - this.binlogNextPos = null; - this._init(); + this._establishConnection(dsn); } -var cloneObjectSimple = function(obj) { - var out = {}; - for (var i in obj) { - if (obj.hasOwnProperty(i)) { - out[i] = obj[i]; - } - } - return out; -}; - util.inherits(ZongJi, EventEmitter); -ZongJi.prototype._init = function() { - var self = this; - var binlogOptions = { - tableMap: self.tableMap, +// dsn - can be one instance of Connection or Pool / object / url string +ZongJi.prototype._establishConnection = function(dsn) { + const createConnection = (options) => { + let connection = mysql.createConnection(options); + connection.on('error', this.emit.bind(this, 'error')); + connection.on('unhandledError', this.emit.bind(this, 'error')); + // don't need to call connection.connect() here + // we use implicitly established connection + // see https://github.com/mysqljs/mysql#establishing-connections + return connection; }; - var asyncMethods = [ - { - name: '_isChecksumEnabled', - callback: function(checksumEnabled) { - self.useChecksum = checksumEnabled; - binlogOptions.useChecksum = checksumEnabled; - } - }, - { - name: '_findBinlogEnd', - callback: function(result) { - if (result && self.options.startAtEnd) { - binlogOptions.filename = result.Log_name; - binlogOptions.position = result.File_size; - } - } - } - ]; - - var methodIndex = 0; - var nextMethod = function() { - var method = asyncMethods[methodIndex]; - self[method.name](function(/* args */) { - method.callback.apply(this, arguments); - methodIndex++; - if (methodIndex < asyncMethods.length) { - nextMethod(); - } - else { - ready(); - } - }); - }; - nextMethod(); + const configFunc = ConnectionConfigMap[dsn.constructor.name]; + let binlogDsn; - var ready = function() { - // Run asynchronously from _init(), as serverId option set in start() - if (self.options.serverId !== undefined) { - binlogOptions.serverId = self.options.serverId; - } + if (typeof dsn === 'object' && configFunc) { + // dsn is a pool or connection object + let conn = dsn; // reuse as ctrlConnection + this.ctrlConnection = conn; + this.ctrlConnectionOwner = false; + binlogDsn = Object.assign({}, configFunc(conn)); + } - if (('binlogName' in self.options) && ('binlogNextPos' in self.options)) { - binlogOptions.filename = self.options.binlogName; - binlogOptions.position = self.options.binlogNextPos; - } + if (!binlogDsn) { + // assuming that the object passed is the connection settings + this.ctrlConnectionOwner = true; + this.ctrlConnection = createConnection(dsn); + binlogDsn = dsn; + } - self.binlog = generateBinlog.call(self, binlogOptions); - self.ready = true; - self._executeCtrlCallbacks(); - }; + this.connection = createConnection(binlogDsn); }; ZongJi.prototype._isChecksumEnabled = function(next) { - var self = this; - var sql = 'select @@GLOBAL.binlog_checksum as checksum'; - var ctrlConnection = self.ctrlConnection; - var connection = self.connection; - - ctrlConnection.query(sql, function(err, rows) { - if (err) { - if (err.toString().match(/ER_UNKNOWN_SYSTEM_VARIABLE/)) { - // MySQL < 5.6.2 does not support @@GLOBAL.binlog_checksum - return next(false); - } else { - // Any other errors should be emitted - self.emit('error', err); - return; + const SelectChecksumParamSql = 'select @@GLOBAL.binlog_checksum as checksum'; + const SetChecksumSql = 'set @master_binlog_checksum=@@global.binlog_checksum'; + + const query = (conn, sql) => { + return new Promise( + (resolve, reject) => { + conn.query(sql, (err, result) => { + if (err) { + reject(err); + } + else { + resolve(result); + } + }); } - } + ); + }; - var checksumEnabled = true; - if (rows[0].checksum === 'NONE') { - checksumEnabled = false; - } + let checksumEnabled = true; - var setChecksumSql = 'set @master_binlog_checksum=@@global.binlog_checksum'; - if (checksumEnabled) { - connection.query(setChecksumSql, function(err) { - if (err) { - // Errors should be emitted - self.emit('error', err); - return; - } - next(checksumEnabled); - }); - } else { - next(checksumEnabled); - } - }); + query(this.ctrlConnection, SelectChecksumParamSql) + .then(rows => { + if (rows[0].checksum === 'NONE') { + checksumEnabled = false; + } + + if (checksumEnabled) { + return query(this.connection, SetChecksumSql); + } + }) + .catch(err => { + if (err.toString().match(/ER_UNKNOWN_SYSTEM_VARIABLE/)) { + checksumEnabled = false; + // a simple query to open this.connection + return query(this.connection, 'SELECT 1'); + } + else { + next(err); + } + }) + .then(() => { + next(null, checksumEnabled); + }); }; ZongJi.prototype._findBinlogEnd = function(next) { - var self = this; - self.ctrlConnection.query('SHOW BINARY LOGS', function(err, rows) { + this.ctrlConnection.query('SHOW BINARY LOGS', (err, rows) => { if (err) { // Errors should be emitted - self.emit('error', err); - return; + next(err); + } + else { + next(null, rows.length > 0 ? rows[rows.length - 1] : null); } - next(rows.length > 0 ? rows[rows.length - 1] : null); }); }; -ZongJi.prototype._executeCtrlCallbacks = function() { - if (this.ctrlCallbacks.length > 0) { - this.ctrlCallbacks.forEach(function(cb) { - setImmediate(cb); - }); - } -}; - -var tableInfoQueryTemplate = 'SELECT ' + - 'COLUMN_NAME, COLLATION_NAME, CHARACTER_SET_NAME, ' + - 'COLUMN_COMMENT, COLUMN_TYPE ' + - 'FROM information_schema.columns ' + "WHERE table_schema='%s' AND table_name='%s'"; - ZongJi.prototype._fetchTableInfo = function(tableMapEvent, next) { - var self = this; - var sql = util.format(tableInfoQueryTemplate, + const sql = util.format(TableInfoQueryTemplate, tableMapEvent.schemaName, tableMapEvent.tableName); - this.ctrlConnection.query(sql, function(err, rows) { + this.ctrlConnection.query(sql, (err, rows) => { if (err) { // Errors should be emitted - self.emit('error', err); + this.emit('error', err); // This is a fatal error, no additional binlog events will be // processed since next() will never be called return; } if (rows.length === 0) { - self.emit('error', new Error( + this.emit('error', new Error( 'Insufficient permissions to access: ' + tableMapEvent.schemaName + '.' + tableMapEvent.tableName)); // This is a fatal error, no additional binlog events will be @@ -212,7 +141,7 @@ ZongJi.prototype._fetchTableInfo = function(tableMapEvent, next) { return; } - self.tableMap[tableMapEvent.tableId] = { + this.tableMap[tableMapEvent.tableId] = { columnSchemas: rows, parentSchema: tableMapEvent.schemaName, tableName: tableMapEvent.tableName @@ -222,97 +151,203 @@ ZongJi.prototype._fetchTableInfo = function(tableMapEvent, next) { }); }; -ZongJi.prototype.set = function(options) { - this.options = options || {}; +// #_options will reset all the options. +ZongJi.prototype._options = function({ + serverId, + filename, + position, + startAtEnd, +}) { + this.options = { + serverId, + filename, + position, + startAtEnd, + }; }; -ZongJi.prototype.start = function(options) { - var self = this; - self.set(options); - - var _start = function() { - self.connection._implyConnect(); - self.connection._protocol._enqueue(new self.binlog(function(error, event) { - if (error) return self.emit('error', error); - // Do not emit events that have been filtered out - if (event === undefined || event._filtered === true) return; - - switch (event.getTypeName()) { - case 'TableMap': - var tableMap = self.tableMap[event.tableId]; - - if (!tableMap) { - self.connection.pause(); - self._fetchTableInfo(event, function() { - // merge the column info with metadata - event.updateColumnInfo(); - self.emit('binlog', event); - self.connection.resume(); - }); - return; - } - break; - case 'Rotate': - if (self.binlogName !== event.binlogName) { - self.binlogName = event.binlogName; - } - break; - } - self.binlogNextPos = event.nextPosition; - self.emit('binlog', event); - })); +// #_filters will reset all the filters. +ZongJi.prototype._filters = function({ + includeEvents, + excludeEvents, + includeSchema, + excludeSchema, +}) { + this.filters = { + includeEvents, + excludeEvents, + includeSchema, + excludeSchema, }; +}; - if (this.ready) { - _start(); +ZongJi.prototype.get = function(name) { + let result; + if (typeof name === 'string') { + result = this.options[name]; } - else { - this.ctrlCallbacks.push(_start); + else if (Array.isArray(name)) { + result = name.reduce( + (acc, cur) => { + acc[cur] = this.options[cur]; + return acc; + }, + {} + ); + } + + return result; +}; + +// @options contains a list options +// - `serverId` unique identifier +// - `filename`, `position` the position of binlog to beigin with +// - `startAtEnd` if true, will update filename / postion automatically +// - `includeEvents`, `excludeEvents`, `includeSchema`, `exludeSchema` filter different binlog events bubbling +ZongJi.prototype.start = function(options = {}) { + + this._options(options); + this._filters(options); + + const testChecksum = (resolve, reject) => { + this._isChecksumEnabled((err, checksumEnabled) => { + if (err) { + reject(err); + } + else { + this.useChecksum = checksumEnabled; + resolve(); + } + }); + }; + + + const findBinlogEnd = (resolve, reject) => { + this._findBinlogEnd((err, result) => { + if (err) { + return reject(err); + } + + if (result) { + this._options( + Object.assign({}, options, { + filename: result.Log_name, + position: result.File_size, + }) + ); + } + + resolve(); + }); + }; + + const binlogHandler = (error, event) => { + if (error) { + return this.emit('error', error); + } + + // Do not emit events that have been filtered out + if (event === undefined || event._filtered === true) return; + + switch (event.getTypeName()) { + case 'TableMap': { + const tableMap = this.tableMap[event.tableId]; + if (!tableMap) { + this.connection.pause(); + this._fetchTableInfo(event, () => { + // merge the column info with metadata + event.updateColumnInfo(); + this.emit('binlog', event); + this.connection.resume(); + }); + return; + } + break; + } + case 'Rotate': + if (this.options.filename !== event.binlogName) { + this.options.filename = event.binlogName; + } + break; + } + this.options.position = event.nextPosition; + this.emit('binlog', event); + }; + + let promises = [new Promise(testChecksum)]; + + if (this.options.startAtEnd) { + promises.push(new Promise(findBinlogEnd)); } + + Promise.all(promises) + .then(() => { + this.BinlogClass = initBinlogClass(this); + this.ready = true; + this.emit('ready'); + + this.connection._protocol._enqueue( + new this.BinlogClass(binlogHandler) + ); + }) + .catch(err => { + this.emit('error', err); + }); + }; ZongJi.prototype.stop = function() { - var self = this; // Binary log connection does not end with destroy() - self.connection.destroy(); - self.ctrlConnection.query( - 'KILL ' + self.connection.threadId, - function() { - if (self.ctrlConnectionOwner) - self.ctrlConnection.destroy(); + this.connection.destroy(); + this.ctrlConnection.query( + 'KILL ' + this.connection.threadId, + () => { + if (this.ctrlConnectionOwner) { + this.ctrlConnection.destroy(); + } + this.emit('stopped'); } ); }; -ZongJi.prototype._skipEvent = function(eventName) { - var include = this.options.includeEvents; - var exclude = this.options.excludeEvents; - return !( - (include === undefined || - (include instanceof Array && include.indexOf(eventName) !== -1)) && - (exclude === undefined || - (exclude instanceof Array && exclude.indexOf(eventName) === -1))); -}; +// It includes every events by default. +ZongJi.prototype._skipEvent = function(name) { + const includes = this.filters.includeEvents; + const excludes = this.filters.excludeEvents; -ZongJi.prototype._skipSchema = function(database, table) { - var include = this.options.includeSchema; - var exclude = this.options.excludeSchema; - return !( - (include === undefined || - (database !== undefined && (database in include) && - (include[database] === true || - (include[database] instanceof Array && - include[database].indexOf(table) !== -1)))) && - (exclude === undefined || - (database !== undefined && - (!(database in exclude) || - (exclude[database] !== true && - (exclude[database] instanceof Array && - exclude[database].indexOf(table) === -1)))))); + let included = (includes === undefined) || + (Array.isArray(includes) && (includes.indexOf(name) > -1)); + let excluded = Array.isArray(excludes) && (excludes.indexOf(name) > -1); + + return excluded || !included; }; -ZongJi.prototype._emitError = function(error) { - this.emit('error', error); +// It doesn't skip any schema by default. +ZongJi.prototype._skipSchema = function(database, table) { + const includes = this.filters.includeSchema; + const excludes = this.filters.excludeSchema || {}; + + let included = (includes === undefined) || + ( + (database in includes) && + ( + includes[database] === true || + ( + Array.isArray(includes[database]) && + includes[database].indexOf(table) > -1 + ) + ) + ); + let excluded = (database in excludes) && + ( + excludes[database] === true || + ( + Array.isArray(excludes[database]) && + excludes[database].indexOf(table) > -1 + ) + ); + + return excluded || !included; }; module.exports = ZongJi; diff --git a/lib/binlog_event.js b/lib/binlog_event.js index 395dfcb2..1a905219 100644 --- a/lib/binlog_event.js +++ b/lib/binlog_event.js @@ -1,6 +1,8 @@ -var util = require('util'); -var Common = require('./common'); +const util = require('util'); +const Common = require('./common'); +//TODO get rid parser from binlog event class +// probably a factory to create them function BinlogEvent(parser, options) { this.timestamp = options.timestamp; this.nextPosition = options.nextPosition; @@ -118,7 +120,7 @@ function IntVar(parser) { } util.inherits(IntVar, BinlogEvent); -var INTVAR_TYPES = ['INVALID_INT', 'LAST_INSERT_ID', 'INSERT_ID']; +const INTVAR_TYPES = ['INVALID_INT', 'LAST_INSERT_ID', 'INSERT_ID']; IntVar.prototype.getIntTypeName = function() { return INTVAR_TYPES[this.type] || 'INVALID_INT'; }; @@ -141,18 +143,18 @@ IntVar.prototype.dump = function() { function TableMap(parser, options, zongji) { BinlogEvent.apply(this, arguments); - this.tableMap = options.tableMap; + this.tableMap = zongji.tableMap; // post-header this._readTableId(parser); this.flags = parser.parseUnsignedNumber(2); // payload - var schemaNameLength = parser.parseUnsignedNumber(1); + const schemaNameLength = parser.parseUnsignedNumber(1); this.schemaName = parser.parseString(schemaNameLength); parser.parseUnsignedNumber(1); - var tableNameLength = parser.parseUnsignedNumber(1); + const tableNameLength = parser.parseUnsignedNumber(1); this.tableName = parser.parseString(tableNameLength); if (zongji._skipSchema(this.schemaName, this.tableName)) { @@ -177,18 +179,18 @@ function TableMap(parser, options, zongji) { util.inherits(TableMap, BinlogEvent); TableMap.prototype.updateColumnInfo = function() { - var columnsMetadata = this.columnsMetadata; - for (var i = 0; i < this.columnCount; i++) { + const columnsMetadata = this.columnsMetadata; + for (let i = 0; i < this.columnCount; i++) { if (columnsMetadata[i] && columnsMetadata[i].type) { this.columnTypes[i] = columnsMetadata[i].type; delete columnsMetadata[i].type; } } - var tableMap = this.tableMap[this.tableId]; + const tableMap = this.tableMap[this.tableId]; - var columnSchemas = tableMap.columnSchemas; - var columns = []; - for (var j = 0; j < this.columnCount; j++) { + const columnSchemas = tableMap.columnSchemas; + const columns = []; + for (let j = 0; j < this.columnCount; j++) { columns.push({ name: columnSchemas[j].COLUMN_NAME, charset: columnSchemas[j].CHARACTER_SET_NAME, @@ -203,7 +205,7 @@ TableMap.prototype.updateColumnInfo = function() { TableMap.prototype._readColumnMetadata = function(parser) { this.columnsMetadata = this.columnTypes.map(function(code) { - var result; + let result; switch (code) { case Common.MysqlTypes.FLOAT: @@ -217,13 +219,14 @@ TableMap.prototype._readColumnMetadata = function(parser) { 'max_length': parser.parseUnsignedNumber(2) }; break; - case Common.MysqlTypes.BIT: - var bits = parser.parseUnsignedNumber(1); - var bytes = parser.parseUnsignedNumber(1); + case Common.MysqlTypes.BIT: { + const bits = parser.parseUnsignedNumber(1); + const bytes = parser.parseUnsignedNumber(1); result = { bits: bytes * 8 + bits }; break; + } case Common.MysqlTypes.NEWDECIMAL: result = { precision: parser.parseUnsignedNumber(1), @@ -238,14 +241,14 @@ TableMap.prototype._readColumnMetadata = function(parser) { }; break; case Common.MysqlTypes.STRING: - case Common.MysqlTypes.VAR_STRING: + case Common.MysqlTypes.VAR_STRING: { // The STRING type sets a 'real_type' field to indicate the // actual type which is fundamentally incompatible with STRING // parsing. Setting a 'type' key in this hash will cause // TableMap event to override the main field 'type' with the // provided 'type' here. - var metadata = (parser.parseUnsignedNumber(1) << 8) + parser.parseUnsignedNumber(1); - var realType = metadata >> 8; + const metadata = (parser.parseUnsignedNumber(1) << 8) + parser.parseUnsignedNumber(1); + const realType = metadata >> 8; if (realType === Common.MysqlTypes.ENUM || realType === Common.MysqlTypes.SET) { result = { @@ -259,6 +262,7 @@ TableMap.prototype._readColumnMetadata = function(parser) { }; } break; + } case Common.MysqlTypes.TIMESTAMP2: case Common.MysqlTypes.DATETIME2: case Common.MysqlTypes.TIME2: diff --git a/lib/capture.js b/lib/capture.js deleted file mode 100644 index 37c16186..00000000 --- a/lib/capture.js +++ /dev/null @@ -1,43 +0,0 @@ -var mysql = require('mysql'); - -var query = mysql.createQuery('select 1', function() {}); - -var Query = query.constructor; -// `super_` is set by calling util.inherits -// see http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor -var Sequence = Query.super_; - -var QueryPrototype = Object.getPrototypeOf(query); -var SequencePrototype = Object.getPrototypeOf(QueryPrototype); - -var classes = { - Query: Query, - Sequence: Sequence, - QueryPrototype: QueryPrototype, - SequencePrototype: SequencePrototype -}; - -var captured = false; - - -var captureIt = function(connection) { - var ConnectionPrototype = Object.getPrototypeOf(connection); - ConnectionPrototype._patch = function() { - classes.ProtocolPrototype = Object.getPrototypeOf(this._protocol); - classes.ConnectionPrototype = ConnectionPrototype; - classes.Connection = connection.constructor; - classes.Protocol = this._protocol.constructor; - captured = true; - }; - - connection._patch(); - delete ConnectionPrototype._patch; -}; - -// connection {Connection} an instance created by mysql.createConnection -module.exports = function(connection) { - if (!captured) { - captureIt(connection); - } - return classes; -}; diff --git a/lib/code_map.js b/lib/code_map.js index ac80d60b..1e66b884 100644 --- a/lib/code_map.js +++ b/lib/code_map.js @@ -1,7 +1,7 @@ -var events = require('./binlog_event'); -var rowsEvents = require('./rows_event'); +const events = require('./binlog_event'); +const rowsEvents = require('./rows_event'); -var CodeEvent = [ +const CodeEvent = [ 'UNKNOWN_EVENT', 'START_EVENT_V3', 'QUERY_EVENT', @@ -40,7 +40,7 @@ var CodeEvent = [ 'PREVIOUS_GTIDS_LOG_EVENT' ]; -var EventClass = { +const EventClass = { UNKNOWN_EVENT: events.Unknown, QUERY_EVENT: events.Query, INTVAR_EVENT: events.IntVar, diff --git a/lib/common.js b/lib/common.js index 355cdae1..e4bdc98f 100644 --- a/lib/common.js +++ b/lib/common.js @@ -1,8 +1,9 @@ -var iconv = require('iconv-lite'); -var decodeJson = require('./json_decode'); -var dtDecode = require('./datetime_decode'); +const iconv = require('iconv-lite'); +const decodeJson = require('./json_decode'); +const dtDecode = require('./datetime_decode'); +const bigInt = require('big-integer'); -var MysqlTypes = exports.MysqlTypes = { +const MysqlTypes = exports.MysqlTypes = { DECIMAL: 0, TINY: 1, SHORT: 2, @@ -38,34 +39,37 @@ var MysqlTypes = exports.MysqlTypes = { GEOMETRY: 255, }; -exports.parseUInt64 = function(parser) { - var low = parser.parseUnsignedNumber(4); - var high = parser.parseUnsignedNumber(4); - - if (this) { - // Pass extra output to context - this.low = low; - this.high = high; +const TWO_TO_POWER_THIRTY_TWO = Math.pow(2, 32); +const TWO_TO_POWER_SIXTY_THREE = '9223372036854775808'; // Math.pow(2, 63) or 1 << 63 +// This function will return a Number +// if the reuslt < Math.MAX_SAFE_INTEGER or reuslt > Math.MIN_SAFE_INTEGER, +// otherwise, will return a string. +const parseUInt64 = exports.parseUInt64 = function(parser) { + const low = parser.parseUnsignedNumber(4); + const high = parser.parseUnsignedNumber(4); + + if (high >>> 21) { // using bigint here + return bigInt(TWO_TO_POWER_THIRTY_TWO).multiply(high).add(low).toString(); } return (high * Math.pow(2,32)) + low; }; exports.parseUInt48 = function(parser) { - var low = parser.parseUnsignedNumber(4); - var high = parser.parseUnsignedNumber(2); + const low = parser.parseUnsignedNumber(4); + const high = parser.parseUnsignedNumber(2); return (high * Math.pow(2, 32)) + low; }; -exports.parseUInt24 = function(parser) { - var low = parser.parseUnsignedNumber(2); - var high = parser.parseUnsignedNumber(1); +const parseUInt24 = exports.parseUInt24 = function(parser) { + const low = parser.parseUnsignedNumber(2); + const high = parser.parseUnsignedNumber(1); return (high << 16) + low; }; exports.parseBytesArray = function(parser, length) { - var result = new Array(length); - for (var i = 0; i < length; i++) { + const result = new Array(length); + for (let i = 0; i < length; i++) { result[i] = parser.parseUnsignedNumber(1); } return result; @@ -75,7 +79,7 @@ exports.parseBytesArray = function(parser, length) { // @param type String Definition of column 'set(...)' or 'enum(...)' // @param prefixLen Integer Number of characters before list starts // (e.g. 'set(': 4, 'enum(': 5) -var parseSetEnumTypeDef = function(type, prefixLen) { +const parseSetEnumTypeDef = function(type, prefixLen) { // listed distinct elements should not include commas return type.substr(prefixLen, type.length - prefixLen - 1) .split(',').map(function(opt) { @@ -84,67 +88,41 @@ var parseSetEnumTypeDef = function(type, prefixLen) { }); }; -var zeroPad = exports.zeroPad = function(num, size) { +const zeroPad = exports.zeroPad = function(num, size) { // Max 32 digits - var s = '00000000000000000000000000000000' + num; + const s = '00000000000000000000000000000000' + num; return s.substr(s.length-size); }; -var sliceBits = exports.sliceBits = function(input, start, end) { +const sliceBits = exports.sliceBits = function(input, start, end) { // ex: start: 10, end: 15 = "111110000000000" - var match = (((1 << end) - 1) ^ ((1 << start) - 1)); + const match = (((1 << end) - 1) ^ ((1 << start) - 1)); return (input & match) >> start; }; -// See information about IEEE-754 Floating point numbers: -// http://www.h-schmidt.net/FloatConverter/IEEE754.html -// http://babbage.cs.qc.cuny.edu/IEEE-754.old/64bit.html +// Use Typed Arrays to convert IEEE 754 numbers // Pass only high for 32-bit float, pass high and low for 64-bit double -var parseIEEE754Float = exports.parseIEEE754Float = function(high, low) { - var lastSignificantBit, sigFigs, expLeading; +const parseIEEE754Float = exports.parseIEEE754Float = function(high, low) { if (low !== undefined) { - // 64-bit: 1 sign, 11 exponent, 52 significand - lastSignificantBit = 20; - sigFigs = 52; - expLeading = 1023; // 2^(11-1) - 1 + let value = new DataView(new ArrayBuffer(8)); + value.setUint32(0, high); + value.setUint32(4, low); + return value.getFloat64(0); } else { - // 32-bit: 1 sign, 8 exponent, 23 significand - lastSignificantBit = 23; - sigFigs = 23; - expLeading = 127; // 2^(8-1) - 1 - } - - var sign = (high & (1 << 31)) !== 0 ? -1 : 1; - var exponent = sliceBits(high, lastSignificantBit, 31) - expLeading; - var significandBits = sliceBits(high, 0, lastSignificantBit); - var significand = 1; // Becomes value between 1, 2 - - for (var i = 0; i < lastSignificantBit; i++) { - if (significandBits & (1 << i)) { - significand += 1 / (1 << (sigFigs - i)); - } - } - - if (low !== undefined) { - for (var j = 0; j < 32; j++) { - if (low & (1 << j)) { - // Bitwise operators only work on up to 32 bits - significand += 1 / Math.pow(2, sigFigs - j); - } - } + let value = new DataView(new ArrayBuffer(4)); + value.setUint32(0, high); + return value.getFloat32(0); } - - return sign * Math.pow(2, exponent) * significand; }; -var getUInt32Value = exports.getUInt32Value = function(input) { +const getUInt32Value = exports.getUInt32Value = function(input) { // Last bit is not sign, it is part of value! if (input & (1 << 31)) return Math.pow(2, 31) + (input & ((1 << 31) -1)); else return input; }; -var parseAnyInt = function(parser, column, columnSchema) { - var result, int64, size; +const parseAnyInt = function(parser, column, columnSchema) { + let result, size; switch (column.type) { case MysqlTypes.TINY: size = 1; @@ -156,7 +134,7 @@ var parseAnyInt = function(parser, column, columnSchema) { break; case MysqlTypes.INT24: size = 3; - result = exports.parseUInt24(parser); + result = parseUInt24(parser); break; case MysqlTypes.LONG: size = 4; @@ -164,33 +142,37 @@ var parseAnyInt = function(parser, column, columnSchema) { break; case MysqlTypes.LONGLONG: size = 8; - int64 = {}; - result = exports.parseUInt64.call(int64, parser); + result = parseUInt64(parser); break; } if (columnSchema.COLUMN_TYPE.indexOf('unsigned') === -1) { - var length = size * 8; + const length = size * 8; + const int64 = (length == 64); // Flip bits on negative signed integer if (!int64 && (result & (1 << (length - 1)))) { result = ((result ^ (Math.pow(2, length) - 1)) * -1) - 1; - } else if (int64 && (int64.high & (1 << 31))) { - // Javascript integers only support 2^53 not 2^64, must trim bits! - // 64-53 = 11, 32-11 = 21, so grab first 21 bits of high word only - var mask = Math.pow(2, 32) - 1; - var high = sliceBits(int64.high ^ mask, 0, 21); - var low = int64.low ^ mask; - result = ((high * Math.pow(2, 32)) * - 1) - getUInt32Value(low) - 1; + } else if (int64 && bigInt(result).greaterOrEquals(bigInt(TWO_TO_POWER_SIXTY_THREE))) { + const Max64BitNumber = bigInt('18446744073709551615'); // 2^64 - 1 + result = bigInt(result).xor(Max64BitNumber).add(1).multiply(-1); + // Javascript integers only support 2^53, if not within the range, return a String + if (result.greater(Number.MAX_SAFE_INTEGER) || result.lesser(Number.MIN_SAFE_INTEGER)) { + result = result.toString(); + } + // Otherwise return a Number + else { + result = result.toJSNumber(); + } } } return result; }; -var readInt24BE = function(buf, offset, noAssert) { +const readInt24BE = function(buf, offset, noAssert) { return (buf.readInt8(offset, noAssert) << 16) + buf.readUInt16BE(offset + 1, noAssert); }; -var readIntBE = function(buf, offset, length, noAssert) { +const readIntBE = function(buf, offset, length, noAssert) { switch (length) { case 1: return buf.readInt8(offset, noAssert); case 2: return buf.readInt16BE(offset, noAssert); @@ -203,26 +185,27 @@ var readIntBE = function(buf, offset, length, noAssert) { // https://github.com/jeremycole/mysql_binlog/blob/master/lib/mysql_binlog/binlog_field_parser.rb // Some more information about DECIMAL types: // http://dev.mysql.com/doc/refman/5.5/en/precision-math-decimal-characteristics.html -var parseNewDecimal = function(parser, column) { +const parseNewDecimal = function(parser, column) { // Constants of format - var digitsPerInteger = 9; - var compressedBytes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4]; + const digitsPerInteger = 9; + const compressedBytes = [0, 1, 1, 2, 2, 3, 3, 4, 4, 4]; - var scale = column.metadata.decimals; - var integral = column.metadata.precision - scale; - var uncompIntegral = Math.floor(integral / digitsPerInteger); - var uncompFractional = Math.floor(scale / digitsPerInteger); - var compIntegral = integral - (uncompIntegral * digitsPerInteger); - var compFractional = scale - (uncompFractional * digitsPerInteger); + const scale = column.metadata.decimals; + const integral = column.metadata.precision - scale; + const uncompIntegral = Math.floor(integral / digitsPerInteger); + const uncompFractional = Math.floor(scale / digitsPerInteger); + const compIntegral = integral - (uncompIntegral * digitsPerInteger); + const compFractional = scale - (uncompFractional * digitsPerInteger); // Grab buffer portion - var size = (uncompIntegral * 4) + compressedBytes[compIntegral] + + const size = (uncompIntegral * 4) + compressedBytes[compIntegral] + (uncompFractional * 4) + compressedBytes[compFractional]; - var buffer = parser._buffer.slice(parser._offset, parser._offset + size); + const buffer = parser._buffer.slice(parser._offset, parser._offset + size); parser._offset += size; // Move binlog parser position forward - var str, mask, pos = 0; - var isPositive = (buffer.readUInt8(0) & (1 << 7)) === 128; + let str, mask; + let pos = 0; + const isPositive = (buffer.readUInt8(0) & (1 << 7)) === 128; buffer.writeUInt8(buffer.readUInt8(0) ^ (1 << 7), 0); if (isPositive) { // Positive number @@ -235,26 +218,26 @@ var parseNewDecimal = function(parser, column) { } // Build integer digits - var compIntegralSize = compressedBytes[compIntegral]; + const compIntegralSize = compressedBytes[compIntegral]; if (compIntegralSize > 0) { str += (readIntBE(buffer, 0, compIntegralSize) ^ mask).toString(10); pos += compIntegralSize; } - for (var i = 0; i < uncompIntegral; i++) { + for (let i = 0; i < uncompIntegral; i++) { str += zeroPad((buffer.readInt32BE(pos) ^ mask).toString(10), 9); pos += 4; } // Build fractional digits - var fractionDigits = ''; + let fractionDigits = ''; - for (var k = 0; k < uncompFractional; k++) { + for (let k = 0; k < uncompFractional; k++) { fractionDigits += zeroPad((buffer.readInt32BE(pos) ^ mask).toString(10), 9); pos += 4; } - var compFractionalSize = compressedBytes[compFractional]; + const compFractionalSize = compressedBytes[compFractional]; if (compFractionalSize > 0) { fractionDigits += zeroPad((readIntBE(buffer, pos, compFractionalSize) ^ mask).toString(10), compFractional); } @@ -267,18 +250,18 @@ var parseNewDecimal = function(parser, column) { // Did not work in place. Function cribbed from lines 311-363 of // https://github.com/felixge/node-mysql/blob/cfd0ce3572d75c3c82103418d1d03cbe67eaf8a1/lib/protocol/Parser.js -var parseGeometryValue = function(buffer) { - var offset = 4; +const parseGeometryValue = function(buffer) { + let offset = 4; if (buffer === null || !buffer.length) { return null; } function parseGeometry() { - var result = null; - var byteOrder = buffer.readUInt8(offset); offset += 1; - var wkbType = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; - var x, y, numPoints, i; + let result = null; + const byteOrder = buffer.readUInt8(offset); offset += 1; + const wkbType = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; + let x, y, numPoints, i; switch (wkbType) { case 1: // WKBPoint @@ -295,13 +278,13 @@ var parseGeometryValue = function(buffer) { result.push({x: x, y: y}); } break; - case 3: // WKBPolygon - var numRings = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; + case 3: {// WKBPolygon + const numRings = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; result = []; for (i = numRings; i > 0; i--) { numPoints = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; - var line = []; - for (var j=numPoints;j>0;j--) { + const line = []; + for (let j = numPoints; j > 0; j--) { x = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8; y = byteOrder? buffer.readDoubleLE(offset) : buffer.readDoubleBE(offset); offset += 8; line.push({x: x, y: y}); @@ -309,16 +292,18 @@ var parseGeometryValue = function(buffer) { result.push(line); } break; + } case 4: // WKBMultiPoint case 5: // WKBMultiLineString case 6: // WKBMultiPolygon - case 7: // WKBGeometryCollection - var num = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; + case 7: {// WKBGeometryCollection + const num = byteOrder? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset); offset += 4; result = []; for (i = num; i > 0; i--) { result.push(parseGeometry()); } break; + } } return result; } @@ -327,15 +312,15 @@ var parseGeometryValue = function(buffer) { // Returns false, or an object describing the fraction of a second part of a // TIME, DATETIME, or TIMESTAMP. -var readTemporalFraction = function(parser, fractionPrecision) { +const readTemporalFraction = function(parser, fractionPrecision) { if (!fractionPrecision) return false; - var fractionSize = Math.ceil(fractionPrecision / 2); - var fraction = readIntBE(parser._buffer, parser._offset, fractionSize); + let fractionSize = Math.ceil(fractionPrecision / 2); + let fraction = readIntBE(parser._buffer, parser._offset, fractionSize); parser._offset += fractionSize; if (fractionPrecision % 2 !== 0) fraction /= 10; // Not using full space if (fraction < 0) fraction *= -1; // Negative time, fraction not negative - var milliseconds; + let milliseconds; if (fractionPrecision > 3) { milliseconds = Math.floor(fraction / Math.pow(10, fractionPrecision - 3)); } else if (fractionPrecision < 3) { @@ -363,15 +348,17 @@ exports.readMysqlValue = function( zongji // the ZongJi instance, used to read options and emit errors ) { - var result; - var high, low; - var raw; - var choices; - var size, lengthSize; - var buffer; - var isNegative; - var fraction; - var hour, minute, second; + let result; + let high, low; + let raw; + let choices; + let size, lengthSize; + let buffer; + let isNegative; + let fraction; + let hour, minute, second; + let date, time, yearMonth; + switch (column.type) { case MysqlTypes.TINY: case MysqlTypes.SHORT: @@ -405,11 +392,11 @@ exports.readMysqlValue = function( // Second argument: prefixLen = 4 'set(' choices = parseSetEnumTypeDef(columnSchema.COLUMN_TYPE, 4); result = ''; - for (var i = 0; low >= Math.pow(2, i); i++) { + for (let i = 0; low >= Math.pow(2, i); i++) { if (low & Math.pow(2, i)) result += choices[i] + ','; } if (high) { - for (i = 0; high >= Math.pow(2, i); i++) { + for (let i = 0; high >= Math.pow(2, i); i++) { if (high & Math.pow(2, i)) result += choices[i + 32] + ','; } } @@ -426,14 +413,14 @@ exports.readMysqlValue = function( result = parser.parseLengthCodedString(); break; case MysqlTypes.VARCHAR: - case MysqlTypes.STRING: - var prefixSize = column.metadata['max_length'] > 255 ? 2 : 1; + case MysqlTypes.STRING: { + const prefixSize = column.metadata['max_length'] > 255 ? 2 : 1; size = parser.parseUnsignedNumber(prefixSize); - var def = columnSchema.COLUMN_TYPE; - var defPrefix = def.substr(0, 6); + const def = columnSchema.COLUMN_TYPE; + const defPrefix = def.substr(0, 6); if (defPrefix === 'binary') { - result = new Buffer(parseInt(def.substr(7, def.length - 2), 10)); - result.fill(0); + const bufsize = parseInt(def.substr(7, def.length - 2), 10); + result = Buffer.alloc(bufsize, 0); parser.parseBuffer(size).copy(result); } else if (defPrefix === 'varbin') { result = parser.parseBuffer(size); @@ -441,6 +428,7 @@ exports.readMysqlValue = function( result = parser.parseString(size); } break; + } case MysqlTypes.TINY_BLOB: case MysqlTypes.MEDIUM_BLOB: case MysqlTypes.LONG_BLOB: @@ -469,7 +457,7 @@ exports.readMysqlValue = function( result = parseGeometryValue(buffer); break; case MysqlTypes.DATE: - raw = exports.parseUInt24(parser); + raw = parseUInt24(parser); result = dtDecode.getDate( zongji.connection.config.dateStrings, // node-mysql dateStrings option sliceBits(raw, 9, 24), // year @@ -478,7 +466,7 @@ exports.readMysqlValue = function( ); break; case MysqlTypes.TIME: - raw = exports.parseUInt24(parser); + raw = parseUInt24(parser); isNegative = (raw & (1 << 23)) !== 0; if (isNegative) raw = raw ^ ((1 << 24) - 1); // flip all bits @@ -519,9 +507,9 @@ exports.readMysqlValue = function( } break; case MysqlTypes.DATETIME: - raw = exports.parseUInt64(parser); - var date = Math.floor(raw / 1000000); - var time = raw % 1000000; + raw = parseUInt64(parser); + date = Math.floor(raw / 1000000); + time = raw % 1000000; result = dtDecode.getDateTime( zongji.connection.config.dateStrings, // node-mysql dateStrings option Math.floor(date / 10000), // year @@ -532,14 +520,14 @@ exports.readMysqlValue = function( time % 100 // seconds ); break; - case MysqlTypes.DATETIME2: + case MysqlTypes.DATETIME2: { // Overlapping high-low to get all data in 32-bit numbers - var rawHigh = readIntBE(parser._buffer, parser._offset, 4); - var rawLow = readIntBE(parser._buffer, parser._offset + 1, 4); + const rawHigh = readIntBE(parser._buffer, parser._offset, 4); + const rawLow = readIntBE(parser._buffer, parser._offset + 1, 4); parser._offset += 5; fraction = readTemporalFraction(parser, column.metadata.decimals); - var yearMonth = sliceBits(rawHigh, 14, 31); + yearMonth = sliceBits(rawHigh, 14, 31); result = dtDecode.getDateTime( zongji.connection.config.dateStrings, // node-mysql dateStrings option Math.floor(yearMonth / 13), // year @@ -551,6 +539,7 @@ exports.readMysqlValue = function( fraction // fraction of a second object ); break; + } case MysqlTypes.TIMESTAMP: raw = parser.parseUnsignedNumber(4); result = dtDecode.getTimeStamp(zongji.connection.config.dateStrings, raw); diff --git a/lib/datetime_decode.js b/lib/datetime_decode.js index a8793f33..c0e9d304 100644 --- a/lib/datetime_decode.js +++ b/lib/datetime_decode.js @@ -3,12 +3,12 @@ // dateStrings option. The dateStrings option should be read from // zongji.connection.config, where zongji is the current instance of the ZongJi // object. The dateStrings option is interpreted the same as in node-mysql. -var common = require('./common'); // used only for common.zeroPad +const common = require('./common'); // used only for common.zeroPad // dateStrings are used only if the dateStrings option is true, or is an array // containing the sql type name string, 'DATE', 'DATETIME', or 'TIMESTAMP'. // This follows the documentation of the dateStrings option in node-mysql. -var useDateStringsForType = function(dateStrings, sqlTypeString) { +const useDateStringsForType = function(dateStrings, sqlTypeString) { return dateStrings && (dateStrings === true || dateStrings.indexOf && dateStrings.indexOf(sqlTypeString) > -1); @@ -16,7 +16,7 @@ var useDateStringsForType = function(dateStrings, sqlTypeString) { // fraction is the fractional second object from readTemporalFraction(). // returns '' or a '.' followed by fraction.precision digits, like '.123' -var getFractionString = exports.getFractionString = function(fraction) { +const getFractionString = exports.getFractionString = function(fraction) { return fraction ? '.' + common.zeroPad(fraction.value, fraction.precision) : ''; @@ -25,7 +25,7 @@ var getFractionString = exports.getFractionString = function(fraction) { // 1950-00-00 and the like are perfectly valid Mysql dateStrings. A 0 portion // of a date is essentially a null part of the date, so we should keep it. // year, month, and date must be integers >= 0. January is month === 1. -var getDateString = exports.getDateString = function(year, month, date) { +const getDateString = exports.getDateString = function(year, month, date) { return common.zeroPad(year, 4) + '-' + common.zeroPad(month, 2) + '-' + common.zeroPad(date, 2); @@ -36,7 +36,7 @@ var getDateString = exports.getDateString = function(year, month, date) { // which is not what it means. It means 2017-NULL-01, but the Date object // cannot handle it, so we want to return an invalid month, rather than a // subtracted month. -var jsMonthFromMysqlMonth = function(month) { +const jsMonthFromMysqlMonth = function(month) { return month > 0 ? month - 1 : undefined; }; @@ -98,8 +98,8 @@ exports.getTimeStamp = function(dateStrings, // node-mysql dateStrings option fraction // optional fraction of second object ) { - var milliseconds = fraction ? fraction.milliseconds : 0; - var dateObject = new Date(secondsFromEpoch * 1000 + milliseconds); + const milliseconds = fraction ? fraction.milliseconds : 0; + const dateObject = new Date(secondsFromEpoch * 1000 + milliseconds); if (!useDateStringsForType(dateStrings, 'TIMESTAMP')) { return dateObject; } diff --git a/lib/json_decode.js b/lib/json_decode.js index 845b20c3..d7a0a5cc 100644 --- a/lib/json_decode.js +++ b/lib/json_decode.js @@ -1,25 +1,25 @@ -var common = require('./common'); -var Parser = require('mysql/lib/protocol/Parser'); +const common = require('./common'); +const Parser = require('mysql/lib/protocol/Parser'); -var JSONB_TYPE_SMALL_OBJECT = 0; -var JSONB_TYPE_LARGE_OBJECT = 1; -var JSONB_TYPE_SMALL_ARRAY = 2; -var JSONB_TYPE_LARGE_ARRAY = 3; -var JSONB_TYPE_LITERAL = 4; -var JSONB_TYPE_INT16 = 5; -var JSONB_TYPE_UINT16 = 6; -var JSONB_TYPE_INT32 = 7; -var JSONB_TYPE_UINT32 = 8; -var JSONB_TYPE_INT64 = 9; -var JSONB_TYPE_UINT64 = 10; -var JSONB_TYPE_DOUBLE = 11; -var JSONB_TYPE_STRING = 12; -var JSONB_TYPE_OPAQUE = 15; +const JSONB_TYPE_SMALL_OBJECT = 0; +const JSONB_TYPE_LARGE_OBJECT = 1; +const JSONB_TYPE_SMALL_ARRAY = 2; +const JSONB_TYPE_LARGE_ARRAY = 3; +const JSONB_TYPE_LITERAL = 4; +const JSONB_TYPE_INT16 = 5; +const JSONB_TYPE_UINT16 = 6; +const JSONB_TYPE_INT32 = 7; +const JSONB_TYPE_UINT32 = 8; +const JSONB_TYPE_INT64 = 9; +const JSONB_TYPE_UINT64 = 10; +const JSONB_TYPE_DOUBLE = 11; +const JSONB_TYPE_STRING = 12; +const JSONB_TYPE_OPAQUE = 15; -var JSONB_LITERALS = [ null, true, false ]; +const JSONB_LITERALS = [ null, true, false ]; // node-mysql prefixes binary string values -var VAR_STRING_PREFIX = 'base64:type253:'; +const VAR_STRING_PREFIX = 'base64:type253:'; module.exports = function(input) { // Value must be JSON string to match node-mysql results @@ -48,13 +48,11 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { // Only used for types which use the value stored at a pointer position // If object is root (offset 0), the value is not offset by pointer - var valueOffset = offset === 0 ? 0 : + const valueOffset = offset === 0 ? 0 : readUInt(offset + 1) + parentValueOffset; - var result = null; - var jsonType = input.readUInt8(offset); - var low, high; - var raw, fraction, yearMonth; + let result = null; + const jsonType = input.readUInt8(offset); switch (jsonType) { // Small enough types are inlined case JSONB_TYPE_INT16: @@ -64,13 +62,14 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { // XXX: No known instance of this type being used result = input.readUInt16LE(offset + 1); break; - case JSONB_TYPE_LITERAL: - var inlineValue = input.readUInt8(offset + 1); + case JSONB_TYPE_LITERAL: { + const inlineValue = input.readUInt8(offset + 1); result = JSONB_LITERALS[inlineValue]; break; + } // All other types are retrieved from pointer - case JSONB_TYPE_STRING: - var strLen, strLenSize = 0, curStrLenByte; + case JSONB_TYPE_STRING: { + let strLen, strLenSize = 0, curStrLenByte; // If the high bit is 1, the string length continues to the next byte while (strLenSize === 0 || (curStrLenByte & 128) === 128) { strLenSize++; @@ -86,6 +85,7 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { valueOffset + strLenSize + 1, valueOffset + strLenSize + 1 + strLen); break; + } case JSONB_TYPE_LARGE_OBJECT: result = readObject(input, valueOffset, true); break; @@ -98,11 +98,12 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { case JSONB_TYPE_SMALL_ARRAY: result = readArray(input, valueOffset, false); break; - case JSONB_TYPE_DOUBLE: - low = input.readUInt32LE(valueOffset + 1); - high = input.readUInt32LE(valueOffset + 5); + case JSONB_TYPE_DOUBLE: { + const low = input.readUInt32LE(valueOffset + 1); + const high = input.readUInt32LE(valueOffset + 5); result = common.parseIEEE754Float(high, low); break; + } case JSONB_TYPE_INT32: result = input.readInt32LE(valueOffset + 1); break; @@ -110,13 +111,13 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { // XXX: No known instance of this type being used result = input.readUInt32LE(valueOffset + 1); break; - case JSONB_TYPE_INT64: - low = input.readUInt32LE(valueOffset + 1); - high = input.readUInt32LE(valueOffset + 5); + case JSONB_TYPE_INT64: { + let low = input.readUInt32LE(valueOffset + 1); + let high = input.readUInt32LE(valueOffset + 5); if (high & (1 << 31)) { // Javascript integers only support 2^53 not 2^64, must trim bits! // 64-53 = 11, 32-11 = 21, so grab first 21 bits of high word only - var mask = Math.pow(2, 32) - 1; + const mask = Math.pow(2, 32) - 1; high = common.sliceBits(high ^ mask, 0, 21); low = low ^ mask; result = @@ -125,14 +126,16 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { result = (high * Math.pow(2,32)) + low; } break; - case JSONB_TYPE_UINT64: - low = input.readUInt32LE(valueOffset + 1); - high = input.readUInt32LE(valueOffset + 5); + } + case JSONB_TYPE_UINT64: { + const low = input.readUInt32LE(valueOffset + 1); + const high = input.readUInt32LE(valueOffset + 5); result = (high * Math.pow(2,32)) + low; break; - case JSONB_TYPE_OPAQUE: - var customType = input.readUInt8(valueOffset + 1); - var dataLen, dataLenSize = 0, curDataLenByte; + } + case JSONB_TYPE_OPAQUE: { + const customType = input.readUInt8(valueOffset + 1); + let dataLen, dataLenSize = 0, curDataLenByte; // If the high bit is 1, the string length continues to the next byte while (dataLenSize === 0 || (curDataLenByte & 128) === 128) { dataLenSize++; @@ -147,28 +150,29 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { // Configure parser and metadata if using standard readMysqlValue // from common.js, otherwise set result for custom decoding - var parser = new Parser(); - var metadata = {}; - var parseType = customType; + const parser = new Parser(); + let metadata = {}; + const parseType = customType; parser.append(input.slice( valueOffset + dataLenSize + 2, valueOffset + dataLenSize + 2 + dataLen)); switch (customType) { - case common.MysqlTypes.DATE: - raw = parser._buffer.readInt32LE(4); - yearMonth = common.sliceBits(raw, 14, 31); + case common.MysqlTypes.DATE: { + const raw = parser._buffer.readInt32LE(4); + const yearMonth = common.sliceBits(raw, 14, 31); result = common.zeroPad(Math.floor(yearMonth / 13), 4) + '-' + // year common.zeroPad(yearMonth % 13, 2) + '-' + // month common.zeroPad(common.sliceBits(raw, 9, 14), 2); // day break; - case common.MysqlTypes.TIME: - raw = parser._buffer.readUInt32LE(3); - fraction = common.sliceBits(parser._buffer.readInt32LE(0), 0, 24); + } + case common.MysqlTypes.TIME: { + let raw = parser._buffer.readUInt32LE(3); + let fraction = common.sliceBits(parser._buffer.readInt32LE(0), 0, 24); - var isNegative = (raw & (1 << 23)) !== 0; + const isNegative = (raw & (1 << 23)) !== 0; if (isNegative) { raw = (raw ^ ((1 << 24) - 1)) + 1; // flip all bits // If fraction exists, last bit adjustment goes to microseconds @@ -178,9 +182,9 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { } } - var hour = common.sliceBits(raw, 12, 22); - var minute = common.sliceBits(raw, 6, 12); - var second = common.sliceBits(raw, 0, 6); + const hour = common.sliceBits(raw, 12, 22); + const minute = common.sliceBits(raw, 6, 12); + const second = common.sliceBits(raw, 0, 6); result = (isNegative ? '-' : '') + common.zeroPad(hour, hour > 99 ? 3 : 2) + ':' + @@ -188,13 +192,14 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { common.zeroPad(second, 2) + '.' + common.zeroPad(fraction, 6); break; - case common.MysqlTypes.DATETIME: + } + case common.MysqlTypes.DATETIME: { // Overlapping high-low to get all data in 32-bit numbers - var rawHigh = parser._buffer.readUInt32LE(3); - var rawLow = parser._buffer.readUInt32LE(4); - fraction = common.sliceBits(parser._buffer.readInt32LE(0), 0, 24); + const rawHigh = parser._buffer.readUInt32LE(3); + const rawLow = parser._buffer.readUInt32LE(4); + const fraction = common.sliceBits(parser._buffer.readInt32LE(0), 0, 24); - yearMonth = common.sliceBits(rawLow, 14, 31); + const yearMonth = common.sliceBits(rawLow, 14, 31); result = common.zeroPad(Math.floor(yearMonth / 13), 4) + '-' + // year common.zeroPad(yearMonth % 13, 2) + '-' + // month @@ -204,6 +209,7 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { common.zeroPad(common.sliceBits(rawHigh, 0, 6), 2) + '.' + // seconds common.zeroPad(fraction, 6); break; + } case common.MysqlTypes.NEWDECIMAL: metadata = { precision: parser.parseUnsignedNumber(1), @@ -226,6 +232,7 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { }); } break; + } default: throw new Error('JSON Type Not Implemented: ' + jsonType); } @@ -233,7 +240,7 @@ function parseBinaryBuffer(input, offset, parentValueOffset, readUInt) { } function readObject(input, valueOffset, isLarge) { - var readUInt, intSize; + let readUInt, intSize; if (isLarge) { readUInt = input.readUInt32LE.bind(input); intSize = 4; @@ -242,26 +249,26 @@ function readObject(input, valueOffset, isLarge) { intSize = 2; } - var result = {}; - var memberCount = readUInt(valueOffset + 1); // +1 = JSON type byte + const result = {}; + const memberCount = readUInt(valueOffset + 1); // +1 = JSON type byte // Position where key entries start // Key entry: Key offset (int16/32) + Key length (int16) - var memberKeyStart = + const memberKeyStart = valueOffset + 1 + // Beginning of definition (intSize * 2); // memberCount + binarySize // Value entries (or pointers to such) begin after key entries - var memberValueStart = memberKeyStart + (memberCount * (intSize + 2)); + const memberValueStart = memberKeyStart + (memberCount * (intSize + 2)); - for (var pointerPos = 0; pointerPos < memberCount; pointerPos++) { - var keyEntryPos = memberKeyStart + (pointerPos * (intSize + 2)); + for (let pointerPos = 0; pointerPos < memberCount; pointerPos++) { + const keyEntryPos = memberKeyStart + (pointerPos * (intSize + 2)); - var keyStart = valueOffset + 1 + readUInt(keyEntryPos); - var keyEnd = keyStart + input.readUInt16LE(keyEntryPos + intSize); + const keyStart = valueOffset + 1 + readUInt(keyEntryPos); + const keyEnd = keyStart + input.readUInt16LE(keyEntryPos + intSize); - var thisKey = input.toString('utf8', keyStart, keyEnd); - var memberValueOffset = memberValueStart + (pointerPos * (intSize + 1)); + const thisKey = input.toString('utf8', keyStart, keyEnd); + const memberValueOffset = memberValueStart + (pointerPos * (intSize + 1)); result[thisKey] = parseBinaryBuffer(input, memberValueOffset, valueOffset, readUInt); @@ -271,7 +278,7 @@ function readObject(input, valueOffset, isLarge) { } function readArray(input, valueOffset, isLarge) { - var readUInt, intSize; + let readUInt, intSize; if (isLarge) { readUInt = input.readUInt32LE.bind(input); intSize = 4; @@ -280,11 +287,11 @@ function readArray(input, valueOffset, isLarge) { intSize = 2; } - var result = []; - var memberCount = readUInt(valueOffset + 1); // +1 = JSON type byte + const result = []; + const memberCount = readUInt(valueOffset + 1); // +1 = JSON type byte - for (var pointerPos = 0; pointerPos < memberCount; pointerPos++) { - var memberValueOffset = + for (let pointerPos = 0; pointerPos < memberCount; pointerPos++) { + let memberValueOffset = valueOffset + 1 + // Beginning of definition (intSize * 2) + // memberCount + binarySize (pointerPos * (1 + intSize)); // value type + value offset diff --git a/lib/packet/binlog.js b/lib/packet/binlog.js new file mode 100644 index 00000000..1db4d531 --- /dev/null +++ b/lib/packet/binlog.js @@ -0,0 +1,67 @@ +const getEventClass = require('../code_map').getEventClass; + +//TODO Don't depend on zongji instance here +module.exports = function initBinlogPacketClass(zongji) { + + class BinlogPacket { + + *_process(parser) { + // uint8_t marker; // always 0 or 0xFF + // uint32_t timestamp; + // uint8_t type_code; + // uint32_t server_id; + // uint32_t event_length; + // uint32_t next_position; + // uint16_t flags; + parser.parseUnsignedNumber(1); + + const timestamp = parser.parseUnsignedNumber(4) * 1000; + const eventType = parser.parseUnsignedNumber(1); + parser.parseUnsignedNumber(4); // serverId + const eventLength = parser.parseUnsignedNumber(4); + const nextPosition = parser.parseUnsignedNumber(4); + parser.parseUnsignedNumber(2); // flags + + const options = { + timestamp: timestamp, + nextPosition: nextPosition, + size: eventLength - BinlogPacket.Length, + eventType: eventType, + }; + + const EventClass = getEventClass(eventType); + this.eventName = EventClass.name; + + yield; + + try { + this._event = new EventClass(parser, options, zongji); + } catch (err) { + // Record error occurence but suppress until handled + this._error = err; + } + } + + // interface will be called, see mysql/lib/protocol/Protocol + parse(parser) { + this._processor = this._process(parser); + this._processor.next(); + } + + getEvent() { + this._processor.next(); + // Ready to handle the error now + if (this._error) throw this._error; + return this._event; + } + } + + // header length doesn't count marker + BinlogPacket.Length = 19; + + if (zongji.useChecksum) { + BinlogPacket.Length = 19 + 4; + } + + return BinlogPacket; +}; diff --git a/lib/packet/binlog_header.js b/lib/packet/binlog_header.js deleted file mode 100644 index 40cc9478..00000000 --- a/lib/packet/binlog_header.js +++ /dev/null @@ -1,63 +0,0 @@ -var getEventClass = require('../code_map').getEventClass; - -module.exports = function generateBinlogHeader(options) { - var zongji = this; - var tableMap = options.tableMap; - var useChecksum = options.useChecksum; - - function BinlogHeader() {} - - BinlogHeader.prototype.parse = function(parser) { - // uint8_t marker; // always 0 or 0xFF - // uint32_t timestamp; - // uint8_t type_code; - // uint32_t server_id; - // uint32_t event_length; - // uint32_t next_position; - // uint16_t flags; - parser.parseUnsignedNumber(1); - - var timestamp = parser.parseUnsignedNumber(4) * 1000; - var eventType = parser.parseUnsignedNumber(1); - var serverId = parser.parseUnsignedNumber(4); // eslint-disable-line - var eventLength = parser.parseUnsignedNumber(4); - var nextPosition = parser.parseUnsignedNumber(4); - var flags = parser.parseUnsignedNumber(2); // eslint-disable-line - - // headerLength doesn't count marker - var headerLength = 19; - // for MySQL 5.6 and binlog-checksum = CRC32 - if (useChecksum) { - headerLength += 4; - } - var eventSize = eventLength - headerLength; - - var options = { - timestamp: timestamp, - nextPosition: nextPosition, - size: eventSize, - eventType: eventType, - tableMap: tableMap, - }; - - var EventClass = getEventClass(eventType); - // Check event filtering - if (!zongji._skipEvent(EventClass.name.toLowerCase())) { - try { - this._event = new EventClass(parser, options, zongji); - } catch (err) { - // Record error occurence but suppress until handled - this._error = err; - } - } - }; - - BinlogHeader.prototype.getEvent = function() { - // Ready to handle the error now - if (this._error) throw this._error; - return this._event; - }; - - return BinlogHeader; -}; - diff --git a/lib/packet/combinlog.js b/lib/packet/combinlog.js index 3ab42e03..f6d7361a 100644 --- a/lib/packet/combinlog.js +++ b/lib/packet/combinlog.js @@ -1,14 +1,13 @@ -function ComBinlog(options) { - options = options || {}; +function ComBinlog({ serverId, nonBlock, filename, position }) { this.command = 0x12; - this.position = options.position || 4; + this.position = position || 4; // will send eof package if there is no more binlog event // https://dev.mysql.com/doc/internals/en/com-binlog-dump.html#binlog-dump-non-block - this.flags = options.nonBlock ? 1 : 0; + this.flags = nonBlock ? 1 : 0; - this.serverId = options.serverId || 1; - this.filename = options.filename || ''; + this.serverId = serverId || 1; + this.filename = filename || ''; } ComBinlog.prototype.write = function(writer) { diff --git a/lib/packet/index.js b/lib/packet/index.js index 44db2d32..3f9f7da4 100644 --- a/lib/packet/index.js +++ b/lib/packet/index.js @@ -60,7 +60,7 @@ ErrorPacket.prototype.write = function(writer) { writer.writeString(this.message); }; -exports.Eof = EofPacket; -exports.Error = ErrorPacket; +exports.EofPacket = EofPacket; +exports.ErrorPacket = ErrorPacket; exports.ComBinlog = require('./combinlog'); -exports.initBinlogHeader = require('./binlog_header'); +exports.initBinlogPacketClass = require('./binlog'); diff --git a/lib/reader.js b/lib/reader.js index 97ad43ea..e9a24039 100644 --- a/lib/reader.js +++ b/lib/reader.js @@ -1,9 +1,9 @@ // Constants for variable length encoded binary -var NULL_COLUMN = 251; -var UNSIGNED_CHAR_COLUMN = 251; -var UNSIGNED_SHORT_COLUMN = 252; -var UNSIGNED_INT24_COLUMN = 253; -var UNSIGNED_INT64_COLUMN = 254; +const NULL_COLUMN = 251; +const UNSIGNED_CHAR_COLUMN = 251; +const UNSIGNED_SHORT_COLUMN = 252; +const UNSIGNED_INT24_COLUMN = 253; +const UNSIGNED_INT64_COLUMN = 254; function BufferReader(buffer) { this.buffer = buffer; @@ -11,34 +11,34 @@ function BufferReader(buffer) { } BufferReader.prototype.readUInt8 = function() { - var pos = this.position; + const pos = this.position; this.position += 1; return this.buffer.readUInt8(pos); }; BufferReader.prototype.readUInt16 = function() { - var pos = this.position; + const pos = this.position; this.position += 2; return this.buffer.readUInt16LE(pos); }; BufferReader.prototype.readUInt32 = function() { - var pos = this.position; + const pos = this.position; this.position += 4; return this.buffer.readUInt32LE(pos); }; BufferReader.prototype.readUInt24 = function() { - var low = this.readUInt16(); - var high = this.readUInt8(); + const low = this.readUInt16(); + const high = this.readUInt8(); return (high << 16) + low; }; BufferReader.prototype.readUInt64 = function() { - var pos = this.position; + const pos = this.position; this.position += 8; // from http://stackoverflow.com/questions/17687307/convert-a-64bit-little-endian-integer-to-number @@ -47,30 +47,30 @@ BufferReader.prototype.readUInt64 = function() { }; BufferReader.prototype.readString = function() { - var strBuf = this.buffer.slice(this.position); + const strBuf = this.buffer.slice(this.position); this.position = this.buffer.length; return strBuf.toString('ascii'); }; BufferReader.prototype.readStringInBytes = function(length) { - var strBuf = this.buffer.slice(this.position, this.position + length); + const strBuf = this.buffer.slice(this.position, this.position + length); this.position += length; return strBuf.toString('ascii'); }; BufferReader.prototype.readHexInBytes = function(length) { - var buf = this.buffer.slice(this.position, this.position + length); + const buf = this.buffer.slice(this.position, this.position + length); this.position += length; return buf.toString('hex'); }; BufferReader.prototype.readBytesArray = function(length) { - var result = []; - var hexString = this.readHexInBytes(length); - for (var i = 0; i < hexString.length; i = i + 2) { + const result = []; + const hexString = this.readHexInBytes(length); + for (let i = 0; i < hexString.length; i = i + 2) { result.push(parseInt(hexString.substr(i, 2), 16)); } return result; @@ -83,8 +83,8 @@ BufferReader.prototype.readBytesArray = function(length) { // used to store the actual value, which can be 2, 3, or 8. It also // includes support for SQL NULL as a special case. BufferReader.prototype.readVariant = function() { - var result = null; - var firstByte = this.readUInt8(); + let result = null; + const firstByte = this.readUInt8(); if (firstByte < UNSIGNED_CHAR_COLUMN) { result = firstByte; @@ -103,11 +103,11 @@ BufferReader.prototype.readVariant = function() { return result; }; -var padWith = function(val, length) { - var bits = val.split(''); +const padWith = function(val, length) { + const bits = val.split(''); if (bits.length < length) { - var left = length - bits.length; - for (var j = left - 1; j >= 0; j--) { + const left = length - bits.length; + for (let j = left - 1; j >= 0; j--) { bits.unshift('0'); } val = bits.join(''); @@ -119,19 +119,19 @@ var padWith = function(val, length) { // Read an arbitrary-length bitmap, provided its length. // Returns an array of true/false values. BufferReader.prototype.readBitArray = function(length) { - var size = Math.floor((length + 7) / 8); + const size = Math.floor((length + 7) / 8); - var bytes = []; - for (var i = size - 1; i >= 0; i--) { + const bytes = []; + for (let i = size - 1; i >= 0; i--) { bytes.unshift(this.readUInt8()); } - var bitmap = []; - var bitmapStr = bytes.map(function(aByte) { + const bitmap = []; + const bitmapStr = bytes.map(function(aByte) { return padWith(aByte.toString(2), 8); }).join(''); - for (var k = bitmapStr.length - 1; k >= 0; k--) { + for (let k = bitmapStr.length - 1; k >= 0; k--) { bitmap.push(bitmapStr[k] === '1'); } diff --git a/lib/rows_event.js b/lib/rows_event.js index 68960fea..3ad35c30 100644 --- a/lib/rows_event.js +++ b/lib/rows_event.js @@ -1,14 +1,35 @@ -var util = require('util'); -var BinlogEvent = require('./binlog_event').BinlogEvent; -var Common = require('./common'); +const util = require('util'); +const BinlogEvent = require('./binlog_event').BinlogEvent; +const Common = require('./common'); -var Version2Events = [ +const Version2Events = [ 0x1e, // WRITE_ROWS_EVENT_V2, 0x1f, // UPDATE_ROWS_EVENT_V2, 0x20, // DELETE_ROWS_EVENT_V2 ]; -var CHECKSUM_SIZE = 4; +const CHECKSUM_SIZE = 4; + +// A quick way to know how many bits set in a given byte +// e.g. Given 3 => 0000 0011, it has 2 bits set +const BIT_COUNT_MAP_IN_ONE_BYTE = [ + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, +]; /** * Generic RowsEvent class @@ -35,21 +56,19 @@ function RowsEvent(parser, options, zongji) { // Body this.numberOfColumns = parser.parseLengthCodedNumber(); - this.tableMap = options.tableMap; + this.tableMap = zongji.tableMap; - var tableData = this.tableMap[this.tableId]; + const tableData = this.tableMap[this.tableId]; if (tableData === undefined) { // TableMap event was filtered parser._offset = parser._packetEnd; this._filtered = true; } else { - var columnsPresentBitmapSize = Math.floor((this.numberOfColumns + 7) / 8); - // Columns present bitmap exceeds 4 bytes with >32 rows - // And is not handled anyways so just skip over its space - parser._offset += columnsPresentBitmapSize; + const columnsPresentBitmapSize = Math.floor((this.numberOfColumns + 7) / 8); + this.columns_present_bitmap = parser.parseBuffer(columnsPresentBitmapSize); if (this._hasTwoRows) { // UpdateRows event slightly different, has new and old rows represented - parser._offset += columnsPresentBitmapSize; + this.columns_present_bitmap2 = parser.parseBuffer(columnsPresentBitmapSize); } if (this.useChecksum) { @@ -90,28 +109,46 @@ RowsEvent.prototype.dump = function() { }; RowsEvent.prototype._fetchOneRow = function(parser) { - return readRow(this.tableMap[this.tableId], parser, this._zongji); + const tablemap = this.tableMap[this.tableId]; + return readRow(parser, tablemap, this.columns_present_bitmap, this._zongji); }; -var readRow = function(tableMap, parser, zongji) { - var row = {}, column, columnSchema; - var nullBitmapSize = Math.floor((tableMap.columns.length + 7) / 8); - var nullBuffer = parser._buffer.slice(parser._offset, - parser._offset + nullBitmapSize); - var curNullByte, curBit; - parser._offset += nullBitmapSize; - - for (var i = 0; i < tableMap.columns.length; i++) { - curBit = i % 8; - if (curBit === 0) curNullByte = nullBuffer.readUInt8(Math.floor(i / 8)); - column = tableMap.columns[i]; - columnSchema = tableMap.columnSchemas[i]; - if ((curNullByte & (1 << curBit)) === 0) { - row[column.name] = - Common.readMysqlValue(parser, column, columnSchema, tableMap, zongji); - } else { +const countBits = function(buff) { + let bits = 0; + for (let i = 0; i < buff.length; i++) { + bits += BIT_COUNT_MAP_IN_ONE_BYTE[buff[i]]; + } + return bits; +}; + +const getBit = function(buff, position) { + let byte = buff[Math.floor(position / 8)]; + return byte & (1 << (position % 8)); +}; + +const readRow = function(parser, tablemap, bitmap, zongji) { + const nullBitmapSize = Math.floor((countBits(bitmap) + 7) / 8); + const nullBitmap = parser.parseBuffer(nullBitmapSize); + + let row = {}; + for (let i = 0, nullBitIndex = 0; i < tablemap.columns.length; i++) { + let column = tablemap.columns[i]; + + if (getBit(bitmap, i) == 0) { row[column.name] = null; + continue; } + + if (getBit(nullBitmap, nullBitIndex) != 0) { + row[column.name] = null; + } else { + let columnSchema = tablemap.columnSchemas[i]; + row[column.name] = Common.readMysqlValue( + parser, column, columnSchema, tablemap, zongji + ); + } + + nullBitIndex += 1; } return row; }; @@ -138,10 +175,10 @@ function UpdateRows(parser, options) { // eslint-disable-line util.inherits(UpdateRows, RowsEvent); UpdateRows.prototype._fetchOneRow = function(parser) { - var tableMap = this.tableMap[this.tableId]; + const tablemap = this.tableMap[this.tableId]; return { - before: readRow(tableMap, parser, this._zongji), - after: readRow(tableMap, parser, this._zongji) + before: readRow(parser, tablemap, this.columns_present_bitmap, this._zongji), + after: readRow(parser, tablemap, this.columns_present_bitmap2, this._zongji), }; }; diff --git a/lib/sequence/binlog.js b/lib/sequence/binlog.js index dde26625..74bff202 100644 --- a/lib/sequence/binlog.js +++ b/lib/sequence/binlog.js @@ -1,11 +1,9 @@ -var Util = require('util'); -var Packet = require('../packet'); -var capture = require('../capture'); +const Util = require('util'); +const { EofPacket, ErrorPacket, ComBinlog, initBinlogPacketClass } = require('../packet'); +const Sequence = require('mysql/lib/protocol/sequences').Sequence; -module.exports = function(options) { - var self = this; // ZongJi instance - var Sequence = capture(self.connection).Sequence; - var BinlogHeader = Packet.initBinlogHeader.call(self, options); +module.exports = function(zongji) { + const BinlogPacket = initBinlogPacketClass(zongji); function Binlog(callback) { Sequence.call(this, callback); @@ -14,17 +12,21 @@ module.exports = function(options) { Util.inherits(Binlog, Sequence); Binlog.prototype.start = function() { - this.emit('packet', new Packet.ComBinlog(options)); + // options include: position / nonBlock / serverId / filename + let options = zongji.get([ + 'serverId', 'position', 'filename', 'nonBlock', + ]); + this.emit('packet', new ComBinlog(options)); }; Binlog.prototype.determinePacket = function(firstByte) { switch (firstByte) { case 0xfe: - return Packet.Eof; + return EofPacket; case 0xff: - return Packet.Error; + return ErrorPacket; default: - return BinlogHeader; + return BinlogPacket; } }; @@ -32,9 +34,15 @@ module.exports = function(options) { console.log('Received one OkPacket ...'); }; - Binlog.prototype['BinlogHeader'] = function(packet) { + Binlog.prototype['BinlogPacket'] = function(packet) { if (this._callback) { - var event, error; + + // Check event filtering + if (zongji._skipEvent(packet.eventName.toLowerCase())) { + return this._callback.call(this); + } + + let event, error; try { event = packet.getEvent(); } catch (err) { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..110321e0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4079 @@ +{ + "name": "zongji", + "version": "0.5.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.3.tgz", + "integrity": "sha512-RpxM252EYsz9qLUIq6F7YJyK1sv0wWDBFuztfDGWaQKzHjqDHysxSiRUpA/X9jmfqo+WzkAVKFaUily5h+gDCQ==", + "dev": true, + "requires": { + "@babel/types": "^7.9.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz", + "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.3.tgz", + "integrity": "sha512-E6SpIDJZ0cZAKoCNk+qSDd0ChfTnpiJN9FfNf3RZ20dzwA2vL2oq5IX1XTVT+4vDmRlta2nGk5HGMMskJAR+4A==", + "dev": true + }, + "@babel/runtime": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz", + "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.0", + "@babel/types": "^7.9.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz", + "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async-hook-domain": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/async-hook-domain/-/async-hook-domain-1.1.3.tgz", + "integrity": "sha512-ZovMxSbADV3+biB7oR1GL5lGyptI24alp0LWHlmz1OFc5oL47pz3EiIF6nXOkDW7yLqih4NtsiYduzdDW0i+Wg==", + "dev": true, + "requires": { + "source-map-support": "^0.5.11" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" + }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "bind-obj-methods": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bind-obj-methods/-/bind-obj-methods-2.0.0.tgz", + "integrity": "sha512-3/qRXczDi2Cdbz6jE+W3IflJOutRVica8frpBn14de1mBOkzDo+6tY33kNhvkw54Kn3PzRRD2VnGbGPcTAk4sw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + }, + "dependencies": { + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + } + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", + "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.3.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "coveralls": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.11.tgz", + "integrity": "sha512-LZPWPR2NyGKyaABnc49dR0fpeP6UqhvGq4B5nUrTQ1UBy55z96+ga7r+/ChMdMJUwBgyJDXBi88UBgz2rs9IiQ==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.0" + } + }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-frag": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/diff-frag/-/diff-frag-1.0.1.tgz", + "integrity": "sha512-6/v2PC/6UTGcWPPetb9acL8foberUg/CtPdALeJUdD1B/weHNvzftoo00gYznqHGRhHEbykUGzqfG9RWOSr5yw==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", + "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "events-to-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", + "integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "findit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findit/-/findit-2.0.0.tgz", + "integrity": "sha1-ZQnwEmr0wXhVHPqZOU4DLhOk1W4=", + "dev": true + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, + "flow-parser": { + "version": "0.121.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz", + "integrity": "sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==", + "dev": true + }, + "flow-remove-types": { + "version": "2.121.0", + "resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.121.0.tgz", + "integrity": "sha512-DbHgYJLD88fMK6CF3Z6wvoZuMb2sqKYP9WLzrZ0SPWbQf61+XyNq6vC8HAJeWJf2DD8z7XhrFHUCH2cJvpAAIQ==", + "dev": true, + "requires": { + "flow-parser": "^0.121.0", + "pirates": "^3.0.2", + "vlq": "^0.2.1" + } + }, + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs-exists-cached": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-exists-cached/-/fs-exists-cached-1.0.0.tgz", + "integrity": "sha1-zyVVTKBQ3EmuZla0HeQiWJidy84=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, + "function-loop": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-1.0.2.tgz", + "integrity": "sha512-Iw4MzMfS3udk/rqxTiDDCllhGwlOrsr50zViTOO/W6lS/9y6B1J0BD2VZzrnWUYBJsl3aeqjgR5v7bWWhZSYbA==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "dev": true, + "requires": { + "is-stream": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "html-escaper": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.1.tgz", + "integrity": "sha512-hNX23TjWwD3q56HpWjUHOKj1+4KKlnjv9PcmBUYKVpga+2cnb9nDx/B1o0yO4n+RZXZdiNxzx6B24C9aNMTkkQ==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.1.tgz", + "integrity": "sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inquirer": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", + "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "istanbul-lib-processinfo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-1.0.0.tgz", + "integrity": "sha512-FY0cPmWa4WoQNlvB8VOcafiRoB5nB+l2Pz2xGuXHRSy1KM8QFOYfz/rN+bGMCAeejrY3mrpF5oJHcN0s/garCg==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^6.0.5", + "istanbul-lib-coverage": "^2.0.3", + "rimraf": "^2.6.3", + "uuid": "^3.3.2" + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0" + } + }, + "jackspeak": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-1.4.0.tgz", + "integrity": "sha512-VDcSunT+wcccoG46FtzuBAyQKlzhHjli4q31e1fIHGOsRspqNUFjVzGb+7eIFDlTvqLygxapDHPHS0ouT2o/tw==", + "dev": true, + "requires": { + "cliui": "^4.1.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "dev": true, + "requires": { + "mime-db": "1.43.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "requires": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "opener": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "own-or": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/own-or/-/own-or-1.0.0.tgz", + "integrity": "sha1-Tod/vtqaLsgAD7wLyuOWRe6L+Nw=", + "dev": true + }, + "own-or-env": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-or-env/-/own-or-env-1.0.1.tgz", + "integrity": "sha512-y8qULRbRAlL6x2+M0vIe7jJbJx/kmUTzYonRAa2ayesR2qWLswninkVyeJe4x3IEXhdgoNodzjQRKAoEs6Fmrw==", + "dev": true, + "requires": { + "own-or": "^1.0.0" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pirates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-3.0.2.tgz", + "integrity": "sha512-c5CgUJq6H2k6MJz72Ak1F5sN9n9wlSlJyEnwvpm9/y3WB4E3pHBDT2c6PEiS1vyJvq2bUxUAIu0EGf8Cx4Ic7Q==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", + "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.7" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", + "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "spawn-wrap": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", + "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "tap": { + "version": "14.10.7", + "resolved": "https://registry.npmjs.org/tap/-/tap-14.10.7.tgz", + "integrity": "sha512-DVx00lfiMxFhofwFDP77pitRCruVQJn8Dcj/6auIU3dErJQWsKT94oG6Yj0MQRuYANhSec8ruIPyUjH/RI9Hrw==", + "dev": true, + "requires": { + "@types/react": "^16.9.16", + "async-hook-domain": "^1.1.3", + "bind-obj-methods": "^2.0.0", + "browser-process-hrtime": "^1.0.0", + "chokidar": "^3.3.0", + "color-support": "^1.1.0", + "coveralls": "^3.0.8", + "diff": "^4.0.1", + "esm": "^3.2.25", + "findit": "^2.0.0", + "flow-remove-types": "^2.112.0", + "foreground-child": "^1.3.3", + "fs-exists-cached": "^1.0.0", + "function-loop": "^1.0.2", + "glob": "^7.1.6", + "import-jsx": "^3.1.0", + "ink": "^2.6.0", + "isexe": "^2.0.0", + "istanbul-lib-processinfo": "^1.0.0", + "jackspeak": "^1.4.0", + "minipass": "^3.1.1", + "mkdirp": "^0.5.1", + "nyc": "^14.1.1", + "opener": "^1.5.1", + "own-or": "^1.0.0", + "own-or-env": "^1.0.1", + "react": "^16.12.0", + "rimraf": "^2.7.1", + "signal-exit": "^3.0.0", + "source-map-support": "^0.5.16", + "stack-utils": "^1.0.2", + "tap-mocha-reporter": "^5.0.0", + "tap-parser": "^10.0.1", + "tap-yaml": "^1.0.0", + "tcompare": "^3.0.0", + "treport": "^1.0.2", + "trivial-deferred": "^1.0.1", + "ts-node": "^8.5.2", + "typescript": "^3.7.2", + "which": "^2.0.2", + "write-file-atomic": "^3.0.1", + "yaml": "^1.7.2", + "yapool": "^1.0.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.8.7", + "bundled": true, + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.7", + "@babel/helpers": "^7.8.4", + "@babel/parser": "^7.8.7", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.0", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.8.8", + "bundled": true, + "dev": true, + "requires": { + "@babel/types": "^7.8.7", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + } + } + }, + "@babel/helper-builder-react-jsx": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/types": "^7.8.3", + "esutils": "^2.0.0" + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "bundled": true, + "dev": true + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helpers": { + "version": "7.8.4", + "bundled": true, + "dev": true, + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.8.8", + "bundled": true, + "dev": true + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.8.8", + "bundled": true, + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-react-jsx": { + "version": "7.8.3", + "bundled": true, + "dev": true, + "requires": { + "@babel/helper-builder-react-jsx": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" + } + }, + "@babel/runtime": { + "version": "7.8.7", + "bundled": true, + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.8.6", + "bundled": true, + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.8.6", + "bundled": true, + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.6", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.8.7", + "bundled": true, + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "@types/prop-types": { + "version": "15.7.3", + "bundled": true, + "dev": true + }, + "@types/react": { + "version": "16.9.23", + "bundled": true, + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "@types/yoga-layout": { + "version": "1.9.1", + "bundled": true, + "dev": true + }, + "ansi-escapes": { + "version": "4.3.1", + "bundled": true, + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "auto-bind": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "caller-callsite": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "cardinal": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, + "chalk": { + "version": "2.4.2", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ci-info": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-truncate": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "color-convert": { + "version": "1.9.3", + "bundled": true, + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + } + } + }, + "csstype": { + "version": "2.6.9", + "bundled": true, + "dev": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true, + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esprima": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "events-to-array": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.1", + "bundled": true, + "dev": true + }, + "globals": { + "version": "11.12.0", + "bundled": true, + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "import-jsx": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "@babel/core": "^7.5.5", + "@babel/plugin-proposal-object-rest-spread": "^7.5.5", + "@babel/plugin-transform-destructuring": "^7.5.0", + "@babel/plugin-transform-react-jsx": "^7.3.0", + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "ink": { + "version": "2.7.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "arrify": "^2.0.1", + "auto-bind": "^4.0.0", + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-truncate": "^2.1.0", + "is-ci": "^2.0.0", + "lodash.throttle": "^4.1.1", + "log-update": "^3.0.0", + "prop-types": "^15.6.2", + "react-reconciler": "^0.24.0", + "scheduler": "^0.18.0", + "signal-exit": "^3.0.2", + "slice-ansi": "^3.0.0", + "string-length": "^3.1.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0", + "yoga-layout-prebuilt": "^1.9.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "is-ci": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "bundled": true, + "dev": true + }, + "json5": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "lodash": { + "version": "4.17.15", + "bundled": true, + "dev": true + }, + "lodash.throttle": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "log-update": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "cli-cursor": "^2.1.0", + "wrap-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "onetime": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "string-width": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + } + } + }, + "loose-envify": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "3.1.1", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "onetime": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "path-parse": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "prop-types": { + "version": "15.7.2", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "punycode": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "react-is": { + "version": "16.13.1", + "bundled": true, + "dev": true + }, + "react-reconciler": { + "version": "0.24.0", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.18.0" + } + }, + "redeyed": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "bundled": true, + "dev": true + }, + "resolve": { + "version": "1.15.1", + "bundled": true, + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "scheduler": { + "version": "0.18.0", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "semver": { + "version": "5.7.1", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true, + "dev": true + } + } + }, + "string-length": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^5.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string-width": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tap-parser": { + "version": "10.0.1", + "bundled": true, + "dev": true, + "requires": { + "events-to-array": "^1.0.1", + "minipass": "^3.0.0", + "tap-yaml": "^1.0.0" + } + }, + "tap-yaml": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "yaml": "^1.5.0" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "treport": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "cardinal": "^2.1.1", + "chalk": "^3.0.0", + "import-jsx": "^3.1.0", + "ink": "^2.6.0", + "ms": "^2.1.2", + "string-length": "^3.1.0", + "tap-parser": "^10.0.1", + "unicode-length": "^2.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "type-fest": { + "version": "0.11.0", + "bundled": true, + "dev": true + }, + "unicode-length": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "punycode": "^2.0.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "widest-line": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true, + "dev": true + } + } + }, + "yaml": { + "version": "1.8.2", + "bundled": true, + "dev": true, + "requires": { + "@babel/runtime": "^7.8.7" + } + }, + "yoga-layout-prebuilt": { + "version": "1.9.5", + "bundled": true, + "dev": true, + "requires": { + "@types/yoga-layout": "1.9.1" + } + } + } + }, + "tap-mocha-reporter": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/tap-mocha-reporter/-/tap-mocha-reporter-5.0.1.tgz", + "integrity": "sha512-1knFWOwd4khx/7uSEnUeaP9IPW3w+sqTgJMhrwah6t46nZ8P25atOKAjSvVDsT67lOPu0nfdOqUwoyKn+3E5pA==", + "dev": true, + "requires": { + "color-support": "^1.1.0", + "debug": "^4.1.1", + "diff": "^4.0.1", + "escape-string-regexp": "^2.0.0", + "glob": "^7.0.5", + "tap-parser": "^10.0.0", + "tap-yaml": "^1.0.0", + "unicode-length": "^2.0.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "tap-parser": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-10.0.1.tgz", + "integrity": "sha512-qdT15H0DoJIi7zOqVXDn9X0gSM68JjNy1w3VemwTJlDnETjbi6SutnqmBfjDJAwkFS79NJ97gZKqie00ZCGmzg==", + "dev": true, + "requires": { + "events-to-array": "^1.0.1", + "minipass": "^3.0.0", + "tap-yaml": "^1.0.0" + } + }, + "tap-yaml": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-1.0.0.tgz", + "integrity": "sha512-Rxbx4EnrWkYk0/ztcm5u3/VznbyFJpyXO12dDBHKWiDVxy7O2Qw6MRrwO5H6Ww0U5YhRY/4C/VzWmFPhBQc4qQ==", + "dev": true, + "requires": { + "yaml": "^1.5.0" + } + }, + "tcompare": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-3.0.4.tgz", + "integrity": "sha512-Q3TitMVK59NyKgQyFh+857wTAUE329IzLDehuPgU4nF5e8g+EUQ+yUbjUy1/6ugiNnXztphT+NnqlCXolv9P3A==", + "dev": true, + "requires": { + "diff-frag": "^1.0.1" + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "trivial-deferred": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trivial-deferred/-/trivial-deferred-1.0.1.tgz", + "integrity": "sha1-N21NKdlR1jaKb3oK6FwvTV4GWPM=", + "dev": true + }, + "ts-node": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.1.tgz", + "integrity": "sha512-10DE9ONho06QORKAaCBpPiFCdW+tZJuY/84tyypGtl6r+/C7Asq0dhqbRZURuUlLQtZxxDvT8eoj8cGW0ha6Bg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "3.1.1" + } + }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, + "unicode-length": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unicode-length/-/unicode-length-2.0.2.tgz", + "integrity": "sha512-Ph/j1VbS3/r77nhoY2WU0GWGjVYOHL3xpKp0y/Eq2e5r0mT/6b649vm7KFO6RdAdrZkYLdxphYVgvODxPB+Ebg==", + "dev": true, + "requires": { + "punycode": "^2.0.0", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vlq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yaml": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.3.tgz", + "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.7" + } + }, + "yapool": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yapool/-/yapool-1.0.0.tgz", + "integrity": "sha1-9pPymjFbUNmp2iZGp6ZkXJaYW2o=", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 6b100c0f..0c2ee900 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "zongji", - "version": "0.4.5", + "version": "0.5.1", "description": "A mysql binlog listener running on Node.js", "main": "index.js", "directories": { "test": "test" }, "scripts": { - "test": "nodeunit --reporter=minimal test", + "test": "tap --bail --no-coverage --jobs=1 test/*.js", "lint": "eslint ." }, "repository": { @@ -28,12 +28,16 @@ "url": "https://github.com/nevill/zongji/issues" }, "homepage": "https://github.com/nevill/zongji", + "engines": { + "node": ">= 8.0" + }, "devDependencies": { - "eslint": "2.13.1", - "nodeunit": "~0.9.1" + "eslint": "6.8.0", + "tap": "14.10.7" }, "dependencies": { - "iconv-lite": "^0.4.13", - "mysql": "2.15.0" + "big-integer": "1.6.48", + "iconv-lite": "0.5.1", + "mysql": "2.18.1" } } diff --git a/test/codemap.js b/test/codemap.js new file mode 100644 index 00000000..91b3639d --- /dev/null +++ b/test/codemap.js @@ -0,0 +1,8 @@ +const tap = require('tap'); +const getEventClass = require('./../lib/code_map').getEventClass; + +tap.test('Codemap', test => { + test.equal(getEventClass(2).name, 'Query'); + test.equal(getEventClass(490).name, 'Unknown'); + test.end(); +}); diff --git a/test/errors.js b/test/errors.js index 9514bcaf..5df5f990 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1,175 +1,188 @@ -var ZongJi = require('./../'); -var getEventClass = require('./../lib/code_map').getEventClass; -var settings = require('./settings/mysql'); -var connector = require('./helpers/connector'); -var querySequence = require('./helpers/querySequence'); - -var conn = process.testZongJi || {}; - -function generateDisconnectionCase(readyKillIdFun, cleanupKillIdFun) { - return function(test) { - var zongji = new ZongJi(settings.connection); - var errorTrapped = false; - var ACCEPTABLE_ERRORS = [ - 'PROTOCOL_CONNECTION_LOST', - // MySQL 5.1 emits a packet sequence error when the binlog disconnected - 'PROTOCOL_INCORRECT_PACKET_SEQUENCE' - ]; - - zongji.on('error', function(error) { - if (!errorTrapped && ACCEPTABLE_ERRORS.indexOf(error.code) > -1) { - errorTrapped = true; - killThread(cleanupKillIdFun); - test.done(); +const tap = require('tap'); + +const ZongJi = require('../'); +const settings = require('./settings/mysql'); +const testDb = require('./helpers'); + +tap.test('Connect to an invalid host', test => { + const zongji = new ZongJi({ + host: 'wronghost', + user: 'wronguser', + password: 'wrongpass' + }); + + zongji.on('error', function(error) { + test.ok(['ENOTFOUND', 'ETIMEDOUT'].indexOf(error.code) !== -1); + test.end(); + }); + + test.tearDown(() => zongji.stop()); + zongji.start(); +}); + +tap.test('Initialise testing db', test => { + testDb.init(err => { + if (err) { + return test.threw(err); + } + test.end(); + }); +}); + +const ACCEPTABLE_ERRORS = [ + 'PROTOCOL_CONNECTION_LOST', + // MySQL 5.1 emits a packet sequence error when the binlog disconnected + 'PROTOCOL_INCORRECT_PACKET_SEQUENCE' +]; + +tap.test('Disconnect binlog connection', test => { + const zongji = new ZongJi(settings.connection); + + zongji.start({ + includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], + serverId: testDb.serverId(), + }); + + zongji.on('ready', () => { + let threadId = zongji.connection.threadId; + test.ok(!isNaN(threadId)); + testDb.execute([`kill ${threadId}`], err => { + if (err) { + test.threw(err); } }); + }); - zongji.start({ - includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], - // Anything other than default (1) as used in helpers/connector - serverId: 12 + zongji.on('error', err => { + if (ACCEPTABLE_ERRORS.indexOf(err.code) > -1) { + zongji.stop(); + test.end(); + } else { + test.threw(err); + } + }); +}); + +tap.test('Disconnect control connection', test => { + const zongji = new ZongJi(settings.connection); + + zongji.start({ + includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], + serverId: testDb.serverId(), + }); + + zongji.on('ready', () => { + let threadId = zongji.ctrlConnection.threadId; + test.ok(!isNaN(threadId)); + testDb.execute([`kill ${threadId}`], err => { + if (err) { + test.threw(err); + } }); + }); - function killThread(argFun) { - var threadId = argFun(zongji); - test.ok(!isNaN(threadId)); - conn.db.query('KILL ' + threadId); + zongji.on('error', err => { + if (ACCEPTABLE_ERRORS.indexOf(err.code) > -1) { + zongji.stop(); + test.end(); + } else { + test.threw(err); } + }); +}); + + +tap.test('Events come through in sequence', test => { + const NEW_INST_TIMEOUT = 1000; + const UPDATE_INTERVAL = 300; + const UPDATE_COUNT = 5; + const TEST_TABLE = 'reconnect_at_pos'; + + test.test(`prepare table ${TEST_TABLE}`, test => { + testDb.execute([ + `DROP TABLE IF EXISTS ${TEST_TABLE}`, + `CREATE TABLE ${TEST_TABLE} (col INT UNSIGNED)`, + `INSERT INTO ${TEST_TABLE} (col) VALUES (10)`, + ], err =>{ + if (err) { + return test.threw(err); + } + test.end(); + }); + }); + + test.test('when reconnect', test => { + const result = []; + + function startPeriodicallyWriting() { + let sequences = Array.from( + {length: UPDATE_COUNT}, + (_, i) => `INSERT INTO ${TEST_TABLE} (col) VALUES (${i})` + ); + + let updateInterval = setInterval(() => { + testDb.execute([sequences.shift()], error => { + if (error) { + clearInterval(updateInterval); + test.threw(error); + } + }); - function isZongjiReady() { - setTimeout(function() { - if (zongji.ready) { - killThread(readyKillIdFun); - } else { - isZongjiReady(); + if (sequences.length === 0) { + clearInterval(updateInterval); } - }, 100); + }, UPDATE_INTERVAL); } - isZongjiReady(); - }; -} + function newInstance(options) { + const zongji = new ZongJi(settings.connection); + + zongji.start({ + ...options, + // Must include rotate events for filename and position properties + includeEvents: [ + 'rotate', 'tablemap', 'writerows', 'updaterows', 'deleterows' + ] + }); -module.exports = { - setUp: function(done) { - if (!conn.db) { - process.testZongJi = connector.call(conn, settings, done); - } else { - conn.incCount(); - done(); - } - }, - tearDown: function(done) { - if (conn) { - conn.eventLog.splice(0, conn.eventLog.length); - conn.errorLog.splice(0, conn.errorLog.length); - conn.closeIfInactive(1000); - } - done(); - }, - binlogConnection_disconnect: generateDisconnectionCase( - function onReady(zongji) { return zongji.connection.threadId; }, - function onCleanup(zongji) { return zongji.ctrlConnection.threadId; }), - ctrlConnection_disconnect: generateDisconnectionCase( - function onReady(zongji) { return zongji.ctrlConnection.threadId; }, - function onCleanup(zongji) { return zongji.connection.threadId; }), - - reconnect_at_pos: function(test) { - // Test that binlog events come through in correct sequence after - // reconnect using the binlogName and binlogNextPos properties - var NEW_INST_TIMEOUT = 1000; - var UPDATE_INTERVAL = 300; - var UPDATE_COUNT = 5; - var TEST_TABLE = 'reconnect_at_pos'; - - var updatesSent = 0, updateEvents = 0; - - // Create a new ZongJi instance using some default options that will count - // using the values in the new rows inserted - function startNewZongJi(options) { - var zongji = new ZongJi(settings.connection); - - zongji.start(Object.keys(options || {}).reduce(function(opts, setKey) { - // Object.assign-like to support node 0.10 - opts[setKey] = options[setKey]; - return opts; - }, { - // Must include rotate events for binlogName and binlogNextPos properties - includeEvents: ['rotate', 'tablemap', 'writerows', 'updaterows', 'deleterows'] - })); zongji.on('binlog', function(event) { if (event.getTypeName() === 'WriteRows') { - if (updateEvents++ !== event.rows[0].col) { - exitTest('Events in the wrong order'); - } else if (updateEvents === UPDATE_COUNT) { - exitTest(); - } + result.push(event.rows[0].col); + } + + if (result.length === UPDATE_COUNT) { + test.strictSame( + result, + Array.from({length: UPDATE_COUNT}, (_, i) => i) + ); + test.end(); } }); + return zongji; } - var firstZongJi; - var secondZongJi; - - querySequence(conn.db, [ - 'DROP TABLE IF EXISTS ' + conn.escId(TEST_TABLE), - 'CREATE TABLE ' + conn.escId(TEST_TABLE) + ' (col INT UNSIGNED)', - 'INSERT INTO ' + conn.escId(TEST_TABLE) + ' (col) VALUES (10)', - ], function(error) { - if (error) - return exitTest(error); + let first = newInstance({ + serverId: testDb.serverId(), + startAtEnd: true, + }); - firstZongJi = startNewZongJi({ - serverId: 14, - startAtEnd: true - }); + first.on('ready', () => { + startPeriodicallyWriting(); - setTimeout(function() { + first.on('stopped', () => { // Start new ZongJi instance where the previous was when stopped - firstZongJi.stop(); - secondZongJi = startNewZongJi({ - serverId: 16, - binlogName: firstZongJi.binlogName, - binlogNextPos: firstZongJi.binlogNextPos + let second = newInstance({ + serverId: testDb.serverId(), + filename: first.get('filename'), + position: first.get('position'), }); - }, NEW_INST_TIMEOUT); + test.tearDown(() => second.stop()); + }); + setTimeout(() => first.stop(), NEW_INST_TIMEOUT); }); + }); - function exitTest(error) { - test.ifError(error); - firstZongJi.stop && firstZongJi.stop(); - secondZongJi.stop && secondZongJi.stop(); - test.done(); - } - - var updateInterval = setInterval(function() { - if (updatesSent++ < UPDATE_COUNT) { - querySequence(conn.db, [ - 'INSERT INTO ' + conn.escId(TEST_TABLE) + ' (col) VALUES (' + updateEvents + ')', - ], function(error) { error && exitTest(error); }); - } else { - clearInterval(updateInterval); - } - }, UPDATE_INTERVAL); - - }, - - invalid_host: function(test) { - var zongji = new ZongJi({ - host: 'wronghost', - user: 'wronguser', - password: 'wrongpass' - }); - zongji.on('error', function(error) { - test.ok([ 'ENOTFOUND', 'ETIMEDOUT' ].indexOf(error.code) !== -1); - test.done(); - }); - }, - code_map: function(test) { - test.equal(getEventClass(2).name, 'Query'); - test.equal(getEventClass(490).name, 'Unknown'); - test.done(); - } -}; + test.end(); +}); diff --git a/test/events.js b/test/events.js index 26bdad2c..c403d88d 100644 --- a/test/events.js +++ b/test/events.js @@ -1,282 +1,413 @@ -var mysql = require('mysql'); -var settings = require('./settings/mysql'); -var connector = require('./helpers/connector'); -var querySequence = require('./helpers/querySequence'); -var expectEvents = require('./helpers/expectEvents'); -var ZongJi = require('./../'); +const tap = require('tap'); -var conn = process.testZongJi || {}; +const ZongJi = require('../'); +const expectEvents = require('./helpers/expectEvents'); +const testDb = require('./helpers'); +const settings = require('./settings/mysql'); -var checkTableMatches = function(tableName) { +const checkTableMatches = function(tableName) { return function(test, event) { - var tableDetails = event.tableMap[event.tableId]; - test.strictEqual(tableDetails.parentSchema, settings.database); + const tableDetails = event.tableMap[event.tableId]; + test.strictEqual(tableDetails.parentSchema, testDb.SCHEMA_NAME); test.strictEqual(tableDetails.tableName, tableName); }; }; // For use with expectEvents() -var tableMapEvent = function(tableName) { +const tableMapEvent = function(tableName) { return { _type: 'TableMap', tableName: tableName, - schemaName: settings.database + schemaName: testDb.SCHEMA_NAME, }; }; -module.exports = { - setUp: function(done) { - if (!conn.db) { - process.testZongJi = connector.call(conn, settings, done); - } else { - conn.incCount(); - done(); +tap.test('Initialise testing db', test => { + testDb.init(err => { + if (err) { + return test.threw(err); } - }, - tearDown: function(done) { - if (conn) { - conn.eventLog.splice(0, conn.eventLog.length); - conn.errorLog.splice(0, conn.errorLog.length); - conn.closeIfInactive(1000); - } - done(); - }, - testStartAtEnd: function(test) { - var testTable = 'start_at_end_test'; - querySequence(conn.db, [ - 'FLUSH LOGS', // Ensure Zongji perserveres through a rotation event - 'DROP TABLE IF EXISTS ' + conn.escId(testTable), - 'CREATE TABLE ' + conn.escId(testTable) + ' (col INT UNSIGNED)', - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (10)', - ], function(error) { - if (error) console.error(error); - // Start second ZongJi instance - var zongji = new ZongJi(settings.connection); - var events = []; - - zongji.on('binlog', function(event) { - events.push(event); - }); + test.end(); + }); +}); + +tap.test('Binlog option startAtEnd', test => { + const TEST_TABLE = 'start_at_end_test'; + + test.test(`prepare new table ${TEST_TABLE}`, test => { + testDb.execute([ + 'FLUSH LOGS', // Ensure ZongJi perserveres through a rotation event + `DROP TABLE IF EXISTS ${TEST_TABLE}`, + `CREATE TABLE ${TEST_TABLE} (col INT UNSIGNED)`, + `INSERT INTO ${TEST_TABLE} (col) VALUES (12)`, + ], err => { + if (err) { + return test.fail(err); + } + test.end(); + }); + }); - zongji.start({ - startAtEnd: true, - serverId: 10, // Second instance must not use same server ID - includeEvents: ['tablemap', 'writerows'] + test.test('start', test => { + const events = []; + + const zongji = new ZongJi(settings.connection); + test.tearDown(() => zongji.stop()); + + zongji.on('binlog', evt => events.push(evt)); + zongji.start({ + startAtEnd: true, + includeEvents: ['tablemap', 'writerows'], + }); + + zongji.on('ready', () => { + testDb.execute([ + `INSERT INTO ${TEST_TABLE} (col) VALUES (9)`, + ], err => { + if (err) { + return test.fail(err); + } + + // Should only have 2 events since ZongJi start + expectEvents(test, events, + [ + { /* do not bother testing anything on first event */ }, + { rows: [ { col: 9 } ] } + ], 1, + () => test.end() + ); }); + }); + + + }); + + test.end(); +}); + +tap.test('Class constructor', test => { + const TEST_TABLE = 'conn_obj_test'; + + test.test(`prepare table ${TEST_TABLE}`, test => { + testDb.execute([ + `DROP TABLE IF EXISTS ${TEST_TABLE}`, + `CREATE TABLE ${TEST_TABLE} (col INT UNSIGNED)`, + `INSERT INTO ${TEST_TABLE} (col) VALUES (10)`, + ], err => { + if (err) { + return test.fail(err); + } + + test.end(); + }); + }); + + function run(test, zongji) { + test.tearDown(() => zongji.stop()); - // Give enough time to initialize - setTimeout(function() { - querySequence(conn.db, [ - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (10)', - ], function(error) { - if (error) console.error(error); + const events = []; + zongji.on('binlog', evt => events.push(evt)); + zongji.start({ + startAtEnd: true, + serverId: testDb.serverId(), + includeEvents: ['tablemap', 'writerows'], + }); + zongji.on('ready', () => { + let value = Math.round(Math.random() * 100); + testDb.execute([ + `INSERT INTO ${TEST_TABLE} (col) VALUES (${value})`, + ], err => { + if (err) { + return test.fail(err); + } // Should only have 2 events since ZongJi start + expectEvents(test, events, [ { /* do not bother testing anything on first event */ }, - { rows: [ { col: 10 } ] } - ], 1, function() { - zongji.stop(); - test.done(); - }); + { rows: [ { col: value } ] } + ], 1, () => test.end()); }); - }, 200); + }); + } + + const mysql = require('mysql'); + + test.test('pass a mysql connection instance', test => { + const conn = mysql.createConnection(settings.connection); + const zongji = new ZongJi(conn); + zongji.on('stopped', () => conn.destroy()); + run(test, zongji); + }); + + test.test('pass a mysql pool', test => { + const pool = mysql.createConnection(settings.connection); + const zongji = new ZongJi(pool); + zongji.on('stopped', () => pool.end()); + run(test, zongji); + }); + + test.end(); +}); + +tap.test('Write events', test => { + const TEST_TABLE = 'write_events_test'; + test.test(`prepare table ${TEST_TABLE}`, test => { + testDb.execute([ + `DROP TABLE IF EXISTS ${TEST_TABLE}`, + `CREATE TABLE ${TEST_TABLE} (col INT UNSIGNED)`, + ], err => { + if (err) { + return test.fail(err); + } + + test.end(); }); - }, - testPassedConnectionObj: function(test) { - var testTable = 'conn_obj_test'; - var connObjs = [ - { create: mysql.createConnection, end: function(obj) { obj.destroy(); } }, - { create: mysql.createPool, end: function(obj) { obj.end(); } } - ]; - querySequence(conn.db, [ - 'DROP TABLE IF EXISTS ' + conn.escId(testTable), - 'CREATE TABLE ' + conn.escId(testTable) + ' (col INT UNSIGNED)', - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (10)', - ], function(error) { - if (error) console.error(error); - // Start second ZongJi instance - connObjs.forEach(function(connObj, index) { - var ctrlConn = connObj.create(settings.connection); - var zongji = new ZongJi(ctrlConn); - var events = []; - - zongji.on('binlog', function(event) { - events.push(event); - }); + }); - zongji.start({ - startAtEnd: true, - serverId: 12 + index, // Second instance must not use same server ID - includeEvents: ['tablemap', 'writerows'] - }); + test.test('write a record', test => { + const events = []; + const zongji = new ZongJi(settings.connection); + test.tearDown(() => zongji.stop()); + + zongji.start({ + startAtEnd: true, + serverId: testDb.serverId(), + includeEvents: ['tablemap', 'writerows'], + }); - connObj.zongji = zongji; - connObj.events = events; + zongji.on('ready', () => { + testDb.execute([ + `INSERT INTO ${TEST_TABLE} (col) VALUES (14)`, + ], err => { + if (err) { + return test.fail(err); + } }); + }); - // Give enough time to initialize - setTimeout(function() { - querySequence(conn.db, [ - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (10)', - ], function(error) { - if (error) console.error(error); - // Should only have 2 events since ZongJi start - var finishedCount = 0; - connObjs.forEach(function(connObj) { - expectEvents(test, connObj.events, [ - { /* do not bother testing anything on first event */ }, - { rows: [ { col: 10 } ] } - ], 1, function() { - connObj.zongji.stop(); - // When passing connection object, connection doesn't end on stop - connObj.end(connObj.zongji.ctrlConnection); - if (++finishedCount === connObjs.length - 1) test.done(); - }); - }); - }); - }, 200); + zongji.on('binlog', evt => { + events.push(evt); + if (events.length == 2) { + expectEvents(test, events, + [ + tableMapEvent(TEST_TABLE), + { + _type: 'WriteRows', + _checkTableMap: checkTableMatches(TEST_TABLE), + rows: [ { col: 14 } ], + } + ], 1, + () => test.end() + ); + } }); - }, - testWriteUpdateDelete: function(test) { - var testTable = 'events_test'; - querySequence(conn.db, [ - 'DROP TABLE IF EXISTS ' + conn.escId(testTable), - 'CREATE TABLE ' + conn.escId(testTable) + ' (col INT UNSIGNED)', - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (10)', - 'UPDATE ' + conn.escId(testTable) + ' SET col = 15', - 'DELETE FROM ' + conn.escId(testTable) - ], function(error) { - if (error) console.error(error); - expectEvents(test, conn.eventLog, [ - tableMapEvent(testTable), - { - _type: 'WriteRows', - _checkTableMap: checkTableMatches(testTable), - rows: [ { col: 10 } ] - }, - tableMapEvent(testTable), - { - _type: 'UpdateRows', - _checkTableMap: checkTableMatches(testTable), - rows: [ { before: { col: 10 }, after: { col: 15 } } ] - }, - tableMapEvent(testTable), - { - _type: 'DeleteRows', - _checkTableMap: checkTableMatches(testTable), - rows: [ { col: 15 } ] + }); + + test.test('update a record', test => { + const events = []; + const zongji = new ZongJi(settings.connection); + test.tearDown(() => zongji.stop()); + + zongji.start({ + startAtEnd: true, + serverId: testDb.serverId(), + includeEvents: ['tablemap', 'updaterows'], + }); + + zongji.on('ready', () => { + testDb.execute([ + `UPDATE ${TEST_TABLE} SET col=15`, + ], err => { + if (err) { + return test.fail(err); } - ], 1, function() { - test.equal(conn.errorLog.length, 0); - test.done(); }); }); - }, - testManyColumns: function(test) { - var testTable = '33_columns'; - querySequence(conn.db, [ - 'DROP TABLE IF EXISTS ' + conn.escId(testTable), - 'CREATE TABLE ' + conn.escId(testTable) + ' (' + - 'col1 INT SIGNED NULL, ' + - 'col2 BIGINT SIGNED NULL, ' + - 'col3 TINYINT SIGNED NULL, ' + - 'col4 SMALLINT SIGNED NULL, ' + - 'col5 MEDIUMINT SIGNED NULL, ' + - 'col6 INT SIGNED NULL, ' + - 'col7 BIGINT SIGNED NULL, ' + - 'col8 TINYINT SIGNED NULL, ' + - 'col9 SMALLINT SIGNED NULL, ' + - 'col10 INT SIGNED NULL, ' + - 'col11 BIGINT SIGNED NULL, ' + - 'col12 TINYINT SIGNED NULL, ' + - 'col13 SMALLINT SIGNED NULL, ' + - 'col14 INT SIGNED NULL, ' + - 'col15 BIGINT SIGNED NULL, ' + - 'col16 TINYINT SIGNED NULL, ' + - 'col17 SMALLINT SIGNED NULL, ' + - 'col18 INT SIGNED NULL, ' + - 'col19 BIGINT SIGNED NULL, ' + - 'col20 TINYINT SIGNED NULL, ' + - 'col21 SMALLINT SIGNED NULL, ' + - 'col22 INT SIGNED NULL, ' + - 'col23 BIGINT SIGNED NULL, ' + - 'col24 TINYINT SIGNED NULL, ' + - 'col25 SMALLINT SIGNED NULL, ' + - 'col26 INT SIGNED NULL, ' + - 'col27 BIGINT SIGNED NULL, ' + - 'col28 TINYINT SIGNED NULL, ' + - 'col29 SMALLINT SIGNED NULL, ' + - 'col30 INT SIGNED NULL, ' + - 'col31 BIGINT SIGNED NULL, ' + - 'col32 TINYINT SIGNED NULL, ' + - 'col33 SMALLINT SIGNED NULL)', - 'INSERT INTO ' + conn.escId(testTable) + - ' (col1, col2, col3, col4, col5, col33) VALUES ' + - '(2147483647, null, 127, 32767, 8388607, 12), ' + - '(-2147483648, -9007199254740992, -128, -32768, -8388608, 10), ' + - '(-2147483645, -9007199254740990, -126, -32766, -8388606, 6), ' + - '(-1, -1, -1, -1, null, -6), ' + - '(123456, 100, 96, 300, 1000, null), ' + - '(-123456, -100, -96, -300, -1000, null)', - 'SELECT * FROM ' + conn.escId(testTable) - ], function(error, results) { - if (error) console.error(error); - expectEvents(test, conn.eventLog, [ - { /* do not bother testing anything on first event */ }, - { rows: results[results.length - 1] } - ], 1, test.done); + + zongji.on('binlog', evt => { + events.push(evt); + + if (events.length == 2) { + expectEvents(test, events, + [ + tableMapEvent(TEST_TABLE), + { + _type: 'UpdateRows', + _checkTableMap: checkTableMatches(TEST_TABLE), + rows: [ { before: { col: 14 }, after: { col: 15 } } ], + } + ], 1, + () => test.end() + ); + } + }); + }); + + test.test('delete a record', test => { + const events = []; + const zongji = new ZongJi(settings.connection); + test.tearDown(() => zongji.stop()); + + zongji.start({ + startAtEnd: true, + serverId: testDb.serverId(), + includeEvents: ['tablemap', 'deleterows'], }); - }, - testIntvar: function(test) { - var testTable = 'intvar_test'; - querySequence(conn.db, [ - 'DROP TABLE IF EXISTS ' + conn.escId(testTable), - 'CREATE TABLE ' + conn.escId(testTable) + ' (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY , col INT)', - ], function(error) { - if (error) console.error(error); - // Start second ZongJi instance - var zongji = new ZongJi(settings.connection); - var events = []; - - zongji.on('binlog', function(event) { - if (event.getTypeName() === 'Query' && event.query === 'BEGIN') - return; - events.push(event); - }); - zongji.start({ - startAtEnd: true, - serverId: 12, // Second instance must not use same server ID - includeEvents: ['intvar', 'query'] + zongji.on('ready', () => { + testDb.execute([ + `DELETE FROM ${TEST_TABLE}`, + ], err => { + if (err) { + return test.fail(err); + } }); + }); - // Give enough time to initialize - setTimeout(function() { - querySequence(conn.db, [ - 'SET SESSION binlog_format=STATEMENT', - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (10)', - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (11)', - 'INSERT INTO ' + conn.escId(testTable) + ' (id, col) VALUES (100, LAST_INSERT_ID())', - // Other tests expect row-based replication, so reset here - 'SET SESSION binlog_format=ROW', - ], function(error) { - if (error) console.error(error); - expectEvents(test, events, [ + zongji.on('binlog', evt => { + events.push(evt); + + if (events.length == 2) { + expectEvents(test, events, + [ + tableMapEvent(TEST_TABLE), + { + _type: 'DeleteRows', + _checkTableMap: checkTableMatches(TEST_TABLE), + rows: [ { col: 15 } ], + } + ], 1, + () => test.end() + ); + } + }); + }); + + test.end(); +}); + +tap.test('Intvar / Query event', test => { + const TEST_TABLE = 'intvar_test'; + + test.test(`prepare table ${TEST_TABLE}`, test => { + testDb.execute([ + `DROP TABLE IF EXISTS ${TEST_TABLE}`, + `CREATE TABLE ${TEST_TABLE} (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, col INT)`, + ], err => { + if (err) { + return test.fail(err); + } + + test.end(); + }); + }); + + test.test('begin', test => { + const events = []; + const zongji = new ZongJi(settings.connection); + test.tearDown(() => zongji.stop()); + + zongji.on('binlog', event => { + if (event.getTypeName() === 'Query' && event.query === 'BEGIN') { + return; + } + events.push(event); + + if (events.length === 6) { + expectEvents(test, events, [ { _type: 'IntVar', type: 2, value: 1 }, { _type: 'Query' }, { _type: 'IntVar', type: 2, value: 2 }, { _type: 'Query' }, { _type: 'IntVar', type: 1, value: 2 }, { _type: 'Query' }, - ], 1, function() { - zongji.stop(); - test.done(); - }); - }); - }, 200); + ], 1, () => test.end() + ); + } + }); + + zongji.start({ + startAtEnd: true, + serverId: testDb.serverId(), + includeEvents: ['intvar', 'query'], }); - }, -}; + zongji.on('ready', () => { + testDb.execute([ + 'SET SESSION binlog_format=STATEMENT', + `INSERT INTO ${TEST_TABLE} (col) VALUES (10)`, + `INSERT INTO ${TEST_TABLE} (col) VALUES (11)`, + `INSERT INTO ${TEST_TABLE} (id, col) VALUES (100, LAST_INSERT_ID())`, + // Other tests expect row-based replication, so reset here + 'SET SESSION binlog_format=ROW', + ], err => { + if (err) { + test.fail(err); + } + }); + }); + + }); + + test.end(); +}); + +tap.test('With many columns', test => { + const TEST_TABLE = '33_columns'; + const events = []; + + const zongji = new ZongJi(settings.connection); + + test.tearDown(() => zongji.stop()); + zongji.on('binlog', evt => events.push(evt)); + zongji.start({ + startAtEnd: true, + serverId: testDb.serverId(), + includeEvents: ['tablemap', 'writerows'], + }); + + zongji.on('ready', () => { + testDb.execute([ + `DROP TABLE IF EXISTS ${TEST_TABLE}`, + `CREATE TABLE ${TEST_TABLE} ( + col1 INT SIGNED NULL, col2 BIGINT SIGNED NULL, + col3 TINYINT SIGNED NULL, col4 SMALLINT SIGNED NULL, + col5 MEDIUMINT SIGNED NULL, col6 INT SIGNED NULL, + col7 BIGINT SIGNED NULL, col8 TINYINT SIGNED NULL, + col9 SMALLINT SIGNED NULL, col10 INT SIGNED NULL, + col11 BIGINT SIGNED NULL, col12 TINYINT SIGNED NULL, + col13 SMALLINT SIGNED NULL, col14 INT SIGNED NULL, + col15 BIGINT SIGNED NULL, col16 TINYINT SIGNED NULL, + col17 SMALLINT SIGNED NULL, col18 INT SIGNED NULL, + col19 BIGINT SIGNED NULL, col20 TINYINT SIGNED NULL, + col21 SMALLINT SIGNED NULL, col22 INT SIGNED NULL, + col23 BIGINT SIGNED NULL, col24 TINYINT SIGNED NULL, + col25 SMALLINT SIGNED NULL, col26 INT SIGNED NULL, + col27 BIGINT SIGNED NULL, col28 TINYINT SIGNED NULL, + col29 SMALLINT SIGNED NULL, col30 INT SIGNED NULL, + col31 BIGINT SIGNED NULL, col32 TINYINT SIGNED NULL, + col33 SMALLINT SIGNED NULL)`, + `INSERT INTO ${TEST_TABLE} (col1, col2, col3, col4, col5, col33) VALUES + (null, null, null, null, null, null), + (-1, -1, -1, -1, -1, -1), + (2147483647, 9007199254740993, 127, 32767, 8388607, 12), + (-2147483648, -9007199254740993, -128, -32768, -8388608, 10), + (-2147483645, -1, -126, -32766, -8388606, 6), + (-1, 9223372036854775809, -1, -1, null, -6), + (123456, -9223372036854775809, 96, 300, 1000, null), + (-123456, 9223372036854775807, -96, -300, -1000, null)`, + `SELECT * FROM ${TEST_TABLE}`, + ], (err, result) => { + if (err) { + return test.fail(err); + } + + expectEvents(test, events, [ + { _type: 'TableMap' }, + { rows: result[result.length - 1], _type: 'WriteRows' } + ], 1, test.end); + }); + }); +}); diff --git a/test/filtering.js b/test/filtering.js index 66f08882..dc3826bf 100644 --- a/test/filtering.js +++ b/test/filtering.js @@ -1,149 +1,189 @@ -var settings = require('./settings/mysql'); -var connector = require('./helpers/connector'); -var querySequence = require('./helpers/querySequence'); - -var conn = process.testZongJi || {}; - -module.exports = { - setUp: function(done) { - if (!conn.db) { - process.testZongJi = connector.call(conn, settings, done); - } else { - conn.incCount(); - done(); - } - }, - tearDown: function(done) { - if (conn) { - conn.eventLog.splice(0, conn.eventLog.length); - conn.errorLog.splice(0, conn.errorLog.length); - conn.closeIfInactive(1000); +const tap = require('tap'); +const ZongJi = require('../'); +const settings = require('./settings/mysql'); +const testDb = require('./helpers'); + +// this test is only used for initialization +tap.test('Initialise testing db', test => { + testDb.init(err => { + if (err) { + return test.fail(err); } - done(); - }, - unitTestFilter: function(test) { - var origOptions = conn.zongji.options; - - conn.zongji.set({ - includeEvents: ['tablemap', 'writerows', 'updaterows', 'rotate'], - excludeEvents: ['rotate'], - includeSchema: {db1: true, db2: ['one_table'], db3: true}, - excludeSchema: {db3: true} - }); - // Check that exclude overrides include - test.ok(!conn.zongji._skipEvent('tablemap')); - test.ok(conn.zongji._skipEvent('rotate')); - test.ok(!conn.zongji._skipSchema('db1', 'any_table')); - test.ok(!conn.zongji._skipSchema('db2', 'one_table')); - test.ok(conn.zongji._skipSchema('db2', 'another_table')); - test.ok(conn.zongji._skipSchema('db3', 'any_table')); + test.end(); + }); +}); + +tap.test('Unit test', test => { + const zongji = new ZongJi(settings.connection); + + test.test('Check that exclude overrides include', test => { + zongji._filters({ + includeEvents: ['tablemap', 'writerows', 'updaterows', 'rotate'], + excludeEvents: ['rotate'], + includeSchema: {db1: true, db2: ['one_table'], db3: true}, + excludeSchema: {db3: true} + }); + test.ok(!zongji._skipEvent('tablemap')); + test.ok(zongji._skipEvent('rotate')); + test.ok(!zongji._skipSchema('db1', 'any_table')); + test.ok(!zongji._skipSchema('db2', 'one_table')); + test.ok(zongji._skipSchema('db2', 'another_table')); + test.ok(zongji._skipSchema('db3', 'any_table')); - conn.zongji.set({ - includeSchema: {db1: ['just_me']} + test.end(); }); - test.ok(!conn.zongji._skipSchema('db1', 'just_me')); - test.ok(conn.zongji._skipSchema('db2', 'anything_else')); - test.ok(conn.zongji._skipSchema('db1', 'not_me')); + test.test(test => { + zongji._filters({ + includeSchema: {db1: ['just_me']} + }); + test.ok(!zongji._skipSchema('db1', 'just_me')); + test.ok(zongji._skipSchema('db2', 'anything_else')); + test.ok(zongji._skipSchema('db1', 'not_me')); - conn.zongji.set({ - excludeSchema: {db1: ['not_me']} + test.end(); }); - test.ok(!conn.zongji._skipSchema('db1', 'anything_else')); - test.ok(!conn.zongji._skipSchema('db2', 'anything_else')); - test.ok(conn.zongji._skipSchema('db1', 'not_me')); + test.test(test => { + zongji._filters({ + excludeSchema: {db1: ['not_me']} + }); - conn.zongji.set({ - excludeEvents: ['rotate'] + test.ok(!zongji._skipSchema('db1', 'anything_else')); + test.ok(!zongji._skipSchema('db2', 'anything_else')); + test.ok(zongji._skipSchema('db1', 'not_me')); + + test.end(); }); - test.ok(!conn.zongji._skipEvent('tablemap')); - test.ok(conn.zongji._skipEvent('rotate')); + test.test(test =>{ + zongji._filters({ + excludeEvents: ['rotate'] + }); + test.ok(!zongji._skipEvent('tablemap')); + test.ok(zongji._skipEvent('rotate')); - conn.zongji.set({ - includeEvents: ['rotate'] + test.end(); }); - test.ok(conn.zongji._skipEvent('tablemap')); - test.ok(!conn.zongji._skipEvent('rotate')); - - // Restore original emitter - delete conn.zongji.emit; - conn.zongji.set(origOptions); - - test.done(); - }, - integrationTestFilter: function(test) { - // Set includeSchema to not include anything, recieve no row events - // Ensure that filters are applied - var origOptions = conn.zongji.options; - var testTable = 'filter_test'; - var includeSchema = {}; - // Uncomment the following line to manually test this test: - // includeSchema[settings.database] = [ testTable ]; - conn.zongji.set({ - includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], - includeSchema: includeSchema + + test.test(test =>{ + test.plan(2); + zongji._filters({ + includeEvents: ['rotate'], + }); + test.ok(zongji._skipEvent('tablemap')); + test.ok(!zongji._skipEvent('rotate')); }); - querySequence(conn.db, [ - 'DROP TABLE IF EXISTS ' + conn.escId(testTable), - 'CREATE TABLE ' + conn.escId(testTable) + ' (col INT UNSIGNED)', - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (10)', - 'UPDATE ' + conn.escId(testTable) + ' SET col = 15', - 'DELETE FROM ' + conn.escId(testTable) - ], function(error) { - if (error) console.error(error); + + test.end(); +}); + +tap.test('Exclue all the schema', test => { + const zongji = new ZongJi(settings.connection); + + const eventLog = []; + const errorLog = []; + + zongji.on('binlog', event => eventLog.push(event)); + zongji.on('error', error => errorLog.push(error)); + + test.tearDown(() => zongji.stop()); + + // Set includeSchema to not include anything, recieve no row events + // Ensure that filters are applied + const includeSchema = {}; + zongji.start({ + includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], + includeSchema: includeSchema + }); + + zongji.on('ready', () => { + const testTable = 'filter_test'; + testDb.execute([ + `DROP TABLE IF EXISTS ${testTable}`, + `CREATE TABLE ${testTable} (col INT UNSIGNED)`, + `INSERT INTO ${testTable} (col) VALUES (10)`, + `UPDATE ${testTable} SET col = 15`, + `DELETE FROM ${testTable}`, + ], (error) => { + if (error) { + return test.fail(error); + } + // Give 1 second to see if any events are emitted, they should not be! - setTimeout(function() { - conn.zongji.set(origOptions); - test.equal(conn.eventLog.length, 0); - test.equal(conn.errorLog.length, 0); - test.done(); + setTimeout(() => { + test.equal(eventLog.length, 0); + test.equal(errorLog.length, 0); + test.end(); }, 1000); }); - }, - changeAfterInit: function(test) { - // Set includeSchema to skip table after the tableMap has already been - // cached once, recieve no row events afterwards - var origOptions = conn.zongji.options; - var testTable = 'after_init_test'; - var includeSchema = {}; - includeSchema[settings.database] = [ testTable ]; - conn.zongji.set({ - includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], - includeSchema: includeSchema - }); - querySequence(conn.db, [ - 'DROP TABLE IF EXISTS ' + conn.escId(testTable), - 'CREATE TABLE ' + conn.escId(testTable) + ' (col INT UNSIGNED)', - 'INSERT INTO ' + conn.escId(testTable) + ' (col) VALUES (10)', - ], function(error) { - if (error) console.error(error); - // Give 1 second to see if any events are emitted, they should not be! - setTimeout(function() { - // Expect 2 events, TableMap and WriteRows from the INSERT query - test.equal(conn.eventLog.length, 2); - // Reset eventLog - conn.eventLog.splice(0, conn.eventLog.length); - // Skip all events from all tables - conn.zongji.set({ - includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], - includeSchema: {} - }); - querySequence(conn.db, [ - 'UPDATE ' + conn.escId(testTable) + ' SET col = 15', - 'DELETE FROM ' + conn.escId(testTable) - ], function(error) { - if (error) console.error(error); - setTimeout(function() { - conn.zongji.set(origOptions); - test.equal(conn.eventLog.length, 0); - test.equal(conn.errorLog.length, 0); - test.done(); - }, 500); + }); +}); + +tap.test('Change filter when ZongJi is running', test => { + // Set includeSchema to skip table after the tableMap has already been + // cached once, recieve no row events afterwards + const testTable = 'after_init_test'; + const includeSchema = {}; + includeSchema[settings.connection.database] = [ testTable ]; + + const zongji = new ZongJi(settings.connection); + const eventLog = []; + + zongji.on('binlog', event => eventLog.push(event)); + zongji.on('error', error => test.fail(error)); + + zongji.start({ + includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], + includeSchema: includeSchema + }); + + test.tearDown(() => zongji.stop()); + + testDb.execute( + [ + `DROP TABLE IF EXISTS ${testTable}`, + `CREATE TABLE ${testTable} (col INT UNSIGNED)`, + `INSERT INTO ${testTable} (col) VALUES (10)`, + ], + err => { + if (err) { + return test.fail(err); + } + + setTimeout(() => { + test.equal(eventLog.length, 2); + + test.test('update filter', test => { + // reset for next test + eventLog.splice(0, eventLog.length); + + zongji._filters({ + includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], + includeSchema: {}, + }); + + testDb.execute( + [ + `UPDATE ${testTable} SET col = 15`, + `DELETE FROM ${testTable}`, + ], + (error) => { + if (error) { + return test.fail(error); + } + + setTimeout(() => { + test.equal(eventLog.length, 0); + test.end(); + }, 1000); + } + ); }); - }, 500); - }); - } -}; + + test.end(); + }, 1000); + } + ); +}); diff --git a/test/helpers/connector.js b/test/helpers/connector.js deleted file mode 100644 index 15c54989..00000000 --- a/test/helpers/connector.js +++ /dev/null @@ -1,66 +0,0 @@ -var ZongJi = require('./../../'); -var mysql = require('mysql'); -var querySequence = require('./querySequence'); - -module.exports = function(settings, callback) { - var self = this; - var db = self.db = mysql.createConnection(settings.connection); - var escId = self.escId = db.escapeId; - var eventLog = self.eventLog = []; - var errorLog = self.errorLog = []; - - self.dbName = settings.database; - self.testCount = 0; - - // Perform initialization queries sequentially - querySequence(db, [ - 'SET GLOBAL sql_mode = \'' + settings.sessionSqlMode + '\'', - 'DROP DATABASE IF EXISTS ' + escId(settings.database), - 'CREATE DATABASE ' + escId(settings.database), - 'USE ' + escId(settings.database), - 'RESET MASTER', - 'SELECT VERSION() AS version' - ], function(error, results) { - if (error) console.error(error); - - self.mysqlVersion = results[results.length - 1][0].version - .split('-')[0] - .split('.') - .map(function(part) { - return parseInt(part, 10); - }); - - var zongji = self.zongji = new ZongJi(settings.connection); - - zongji.on('binlog', function(event) { - eventLog.push(event); - }); - - zongji.on('error', function(error) { - errorLog.push(error); - }); - - zongji.start({ - includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'] - }); - - callback(); - }); - - // Extra methods on connector object - self.incCount = function() { - self.testCount++; - }; - - self.closeIfInactive = function(interval) { - var startCount = self.testCount; - setTimeout(function() { - if (startCount === self.testCount) { - self.zongji.stop(); - self.db.destroy(); - } - }, interval); - }; - - return self; -}; diff --git a/test/helpers/expectEvents.js b/test/helpers/expectEvents.js index 0ffb101d..a65069a6 100644 --- a/test/helpers/expectEvents.js +++ b/test/helpers/expectEvents.js @@ -1,4 +1,4 @@ -var MAX_WAIT = 3000; +const MAX_WAIT = 3000; // Check an array of events against an array of expectations // @param {object} test - Pass-thru from nodeunit test case @@ -20,15 +20,15 @@ function expectEvents(test, events, expected, multiplier, callback, waitIndex) { } else { test.strictEqual(events.length, expected.length * multiplier); events.forEach(function(event, index) { - var exp = expected[index % expected.length]; - for (var i in exp) { - if (exp.hasOwnProperty(i)) { + const exp = expected[index % expected.length]; + for (const i in exp) { + if (Object.prototype.hasOwnProperty.call(exp, i)) { if (i === '_type') { - test.strictEqual(exp[i], event.getTypeName()); + test.strictEqual(event.getTypeName(), exp[i]); } else if (String(i).substr(0, 1) === '_') { exp[i](test, event); } else { - test.deepEqual(exp[i], event[i]); + test.same(event[i], exp[i]); } } } diff --git a/test/helpers/index.js b/test/helpers/index.js new file mode 100644 index 00000000..6c5a77ae --- /dev/null +++ b/test/helpers/index.js @@ -0,0 +1,94 @@ +const mysql = require('mysql'); + +const settings = require('../settings/mysql'); +const querySequence = require('./querySequence'); + +const SCHEMA_NAME = settings.connection.database; + +exports.SCHEMA_NAME = SCHEMA_NAME; + +exports.init = function(done) { + const connObj = {...settings.connection}; + // database doesn't exist at this time + delete connObj.database; + const conn = mysql.createConnection(connObj); + + querySequence( + conn, + [ + 'SET GLOBAL sql_mode = \'' + settings.sessionSqlMode + '\'', + `DROP DATABASE IF EXISTS ${SCHEMA_NAME}`, + `CREATE DATABASE ${SCHEMA_NAME}`, + `USE ${SCHEMA_NAME}`, + 'RESET MASTER', + // 'SELECT VERSION() AS version' + ], + error => { + conn.destroy(); + done(error); + } + ); +}; + +exports.execute = function(queries, done) { + const conn = mysql.createConnection(settings.connection); + querySequence( + conn, + queries, + (error, result) => { + conn.destroy(); + done(error, result); + } + ); +}; + +const checkVersion = function(expected, actual) { + const parts = expected.split('.').map(part => parseInt(part, 10)); + for (let i = 0; i < parts.length; i++) { + if (actual[i] == parts[i]) { + continue; + } + return actual[i] > parts[i]; + } + return true; +}; + +exports.requireVersion = function(expected, done) { + const connObj = {...settings.connection}; + // database doesn't exist at this time + delete connObj.database; + const conn = mysql.createConnection(connObj); + querySequence(conn, ['SELECT VERSION() AS version'], (err, results) => { + conn.destroy(); + + if (err) { + throw err; + } + + let ver = results[results.length - 1][0] + .version.split('-')[0] + .split('.') + .map(part => parseInt(part, 10)); + + if (checkVersion(expected, ver)) { + done(); + } + }); +}; + +let id = 100; +exports.serverId = function() { + id ++; + return id; +}; + +exports.strRepeat = function (pattern, count) { + if (count < 1) return ''; + let result = ''; + let pos = 0; + while (pos < count) { + result += pattern.replace(/##/g, pos); + pos++; + } + return result; +}; diff --git a/test/helpers/querySequence.js b/test/helpers/querySequence.js index 75312c06..9a748258 100644 --- a/test/helpers/querySequence.js +++ b/test/helpers/querySequence.js @@ -9,8 +9,8 @@ module.exports = function(connection, debug, queries, callback) { queries = debug; debug = false; } - var results = []; - var sequence = queries.map(function(queryStr, index) { + const results = []; + const sequence = queries.map(function(queryStr, index) { return function() { debug && console.log('Query Sequence', index, queryStr); connection.query(queryStr, function(err, rows) { diff --git a/test/helpers/strRepeat.js b/test/helpers/strRepeat.js deleted file mode 100644 index 3a070493..00000000 --- a/test/helpers/strRepeat.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = function (pattern, count) { - if (count < 1) return ''; - var result = ''; - var pos = 0; - while (pos < count) { - result += pattern.replace(/##/g, pos); - pos++; - } - return result; -}; diff --git a/test/rowimage.js b/test/rowimage.js new file mode 100644 index 00000000..f3256457 --- /dev/null +++ b/test/rowimage.js @@ -0,0 +1,162 @@ +const tap = require('tap'); + +const ZongJi = require('../'); +const testDb = require('./helpers'); +const expectEvents = require('./helpers/expectEvents'); +const settings = require('./settings/mysql'); + +tap.test('Initialise testing db', test => { + testDb.init(err => { + if (err) { + return test.threw(err); + } + test.end(); + }); +}); + +testDb.requireVersion('5.6.2', () => { + + tap.test('Update with binlog_row_image=minmal', test => { + const TEST_TABLE = 'row_image_minimal_test'; + + test.test(`prepare table ${TEST_TABLE}`, test => { + testDb.execute([ + 'SET GLOBAL binlog_row_image=minimal', + `DROP TABLE IF EXISTS ${TEST_TABLE}`, + `CREATE TABLE ${TEST_TABLE} ( + id int primary key auto_increment, + name varchar(20), + age tinyint, + height mediumint + )`, + `INSERT INTO ${TEST_TABLE} (name, age) VALUES ('Tom', 2)`, + ], err => { + if (err) { + return test.fail(err); + } + + test.end(); + }); + }); + + test.test('update a record', test => { + const events = []; + const zongji = new ZongJi(settings.connection); + test.tearDown(() => zongji.stop()); + + zongji.on('ready', () => { + testDb.execute([ + `UPDATE ${TEST_TABLE} SET age=age+1 WHERE id=1`, + ], err => { + if (err) { + test.fail(err); + } + }); + }); + + zongji.on('binlog', evt => { + events.push(evt); + + if (events.length == 2) { + expectEvents(test, events, + [ + { + _type: 'TableMap', + tableName: TEST_TABLE, + schemaName: testDb.SCHEMA_NAME, + }, + { + _type: 'UpdateRows', + rows: [ + { + before: { id: 1, age: null, name: null, height: null }, + after: { id: null, age: 3, name: null, height: null }, + }, + ], + } + ], 1, () => test.end() + ); + } + }); + + zongji.start({ + startAtEnd: true, + serverId: testDb.serverId(), + includeEvents: ['tablemap', 'updaterows'], + }); + }); + + test.end(); + }); + + tap.test('Update with binlog_row_image=noblob', test => { + const TEST_TABLE = 'row_image_noblob_test'; + + test.test(`prepare table ${TEST_TABLE}`, test => { + testDb.execute([ + 'SET GLOBAL binlog_row_image=noblob', + `DROP TABLE IF EXISTS ${TEST_TABLE}`, + `CREATE TABLE ${TEST_TABLE} ( + id int primary key auto_increment, + summary text + )`, + `INSERT INTO ${TEST_TABLE} (summary) VALUES ('Hello world')`, + ], err => { + if (err) { + return test.fail(err); + } + + test.end(); + }); + }); + + test.test('update a record', test => { + const events = []; + const zongji = new ZongJi(settings.connection); + test.tearDown(() => zongji.stop()); + + zongji.on('ready', () => { + testDb.execute([ + `UPDATE ${TEST_TABLE} SET summary='hello again' WHERE id=1`, + ], err => { + if (err) { + test.fail(err); + } + }); + }); + + zongji.on('binlog', evt => { + events.push(evt); + + if (events.length == 2) { + expectEvents(test, events, + [ + { + _type: 'TableMap', + tableName: TEST_TABLE, + schemaName: testDb.SCHEMA_NAME, + }, + { + _type: 'UpdateRows', + rows: [ + { + before: { id: 1, summary: null }, + after: { id: 1, summary: 'hello again' }, + }, + ], + } + ], 1, () => test.end() + ); + } + }); + + zongji.start({ + startAtEnd: true, + serverId: testDb.serverId(), + includeEvents: ['tablemap', 'updaterows'], + }); + }); + + test.end(); + }); +}); diff --git a/test/settings/mysql.js b/test/settings/mysql.js index a4705931..e3ef27a0 100644 --- a/test/settings/mysql.js +++ b/test/settings/mysql.js @@ -8,8 +8,8 @@ module.exports = { charset : 'utf8mb4_unicode_ci', port : process.env.TEST_MYSQL_PORT, dateStrings : process.env.TEST_DATE_STRINGS === 'true', + database: 'zongji_test', // debug: true }, - database: 'zongji_test', - sessionSqlMode: process.env.TEST_SESSION_SQL_MODE || '' + sessionSqlMode: process.env.TEST_SESSION_SQL_MODE || '', }; diff --git a/test/types.js b/test/types.js index 38cc5543..7f808187 100644 --- a/test/types.js +++ b/test/types.js @@ -1,125 +1,100 @@ -var settings = require('./settings/mysql'); -var connector = require('./helpers/connector'); -var querySequence = require('./helpers/querySequence'); -var expectEvents = require('./helpers/expectEvents'); -var strRepeat = require('./helpers/strRepeat'); - -var conn = process.testZongJi || {}; - -module.exports = { - setUp: function(done) { - if (!conn.db) { - process.testZongJi = connector.call(conn, settings, done); - } else { - conn.incCount(); - done(); - } - }, - tearDown: function(done) { - if (conn) { - conn.eventLog.splice(0, conn.eventLog.length); - conn.errorLog.splice(0, conn.errorLog.length); - conn.closeIfInactive(1000); - } - done(); - } -}; +const tap = require('tap'); +const ZongJi = require('../'); +const expectEvents = require('./helpers/expectEvents'); +const testDb = require('./helpers'); +const settings = require('./settings/mysql'); +const strRepeat = testDb.strRepeat; + // @param {string} name - unique identifier of this test [a-zA-Z0-9] // @param {[string]} fields - MySQL field description e.g. `BIGINT NULL` // @param {[[any]]} testRows - 2D array of rows and fields to insert and test // @param {func} customTest - optional, instead of exact row check -// @param {string} minVersion - optional, e.g. '5.6.4' -var defineTypeTest = function(name, fields, testRows, customTest, minVersion) { - // Allow skipping customTest argument and passing minVersion in its place - if (typeof customTest === 'string') { - minVersion = customTest; - customTest = undefined; - } - - module.exports[name] = function(test) { - var testTable = 'type_' + name; - var fieldText = fields.map(function(field, index) { - return 'col' + index + ' ' + field; - }).join(', '); - var insertColumns = fields.map(function(field, index) { - return 'col' + index; - }).join(', '); - var testQueries = [ - 'DROP TABLE IF EXISTS ' + conn.escId(testTable), - 'CREATE TABLE ' + conn.escId(testTable) + ' (' + fieldText + ')', - 'SET @@session.time_zone = "+00:00"'] - .concat(testRows.map(function(row) { - return 'INSERT INTO ' + conn.escId(testTable) + - ' (' + insertColumns + ') VALUES (' + - row.map(function(field) { - return field === null ? 'null' : field; - }).join(', ') + ')'; - })) - .concat([ - 'SET @@session.time_zone = "SYSTEM"', - 'SELECT * FROM ' + conn.escId(testTable) - ]); - - if (!minVersion || checkVersion(minVersion, conn.mysqlVersion)) { - querySequence(conn.db, testQueries, function(error, results) { - if (error) console.error(error); - var selectResult = results[results.length - 1]; - var expectedWrite = { - _type: 'WriteRows', - _checkTableMap: function(test, event) { - var tableDetails = event.tableMap[event.tableId]; - test.strictEqual(tableDetails.parentSchema, settings.database); - test.strictEqual(tableDetails.tableName, testTable); - } - }; - - expectEvents(test, conn.eventLog, [ - { - _type: 'TableMap', - tableName: testTable, - schemaName: settings.database - }, - expectedWrite - ], testRows.length, function() { - test.equal(conn.errorLog.length, 0); - conn.errorLog.length && - console.log('Type Test Error: ', name, conn.errorLog); - if (conn.errorLog.length) { - throw conn.errorLog[0]; +function defineTypeTest(name, fields, testRows, customTest) { + const TEST_TABLE = 'type_' + name; + const fieldText = fields.map((field, index) => `col${index} ${field}`).join(','); + const insertColumns = fields.map((field, index) => 'col' + index).join(','); + const testQueries = [ + `CREATE TABLE ${TEST_TABLE} (${fieldText})`, + 'SET @@session.time_zone = "+00:00"'] + .concat( + testRows.map(row => `INSERT INTO ${TEST_TABLE} + (${insertColumns}) VALUES + (${row.map(field => field === null ? 'null' : field).join(',')})` + ) + ) + .concat([ + 'SET @@session.time_zone = "SYSTEM"', + `SELECT * FROM ${TEST_TABLE}`, + ]); + + tap.test('Initialise testing db', test => { + testDb.init(err => { + if (err) { + return test.fail(err); + } + + test.end(); + }); + }); + + tap.test(name, test => { + const eventLog = []; + const errorLog = []; + + const zongji = new ZongJi(settings.connection); + test.tearDown(() => zongji.stop()); + + zongji.start({ + includeEvents: ['tablemap', 'writerows', 'updaterows', 'deleterows'], + serverId: testDb.serverId(), + }); + zongji.on('binlog', event => eventLog.push(event)); + zongji.on('error', error => errorLog.push(error)); + zongji.on('ready', () => { + testDb.execute(testQueries, (error, results) => { + if (error) { + return test.fail(error); } - var binlogRows = conn.eventLog.reduce(function(prev, curr) { - if (curr.getTypeName() === 'WriteRows') { - prev = prev.concat(curr.rows); + const selectResult = results[results.length - 1]; + const expectedWrite = { + _type: 'WriteRows', + _checkTableMap: (test, event) => { + const tableDetails = event.tableMap[event.tableId]; + test.same(tableDetails.parentSchema, testDb.SCHEMA_NAME); + test.same(tableDetails.tableName, TEST_TABLE); } - return prev; - }, []); + }; - if (customTest) { - customTest.bind(selectResult)(test, { rows: binlogRows }); - } else { - test.deepEqual(selectResult, binlogRows); - } + expectEvents(test, eventLog, [ + { + _type: 'TableMap', + tableName: TEST_TABLE, + schemaName: testDb.SCHEMA_NAME, + }, + expectedWrite + ], testRows.length, () => { + test.equal(errorLog.length, 0); + + const binlogRows = eventLog.reduce((prev, curr) => { + if (curr.getTypeName() === 'WriteRows') { + prev = prev.concat(curr.rows); + } + return prev; + }, []); + + if (customTest) { + customTest.bind(selectResult)(test, { rows: binlogRows }); + } else { + test.deepEqual(selectResult, binlogRows); + } - test.done(); + test.end(); + }); }); }); - } else { - // Skip running test when version doesn't meet minVersion - test.done(); - } - }; -}; - -var checkVersion = function(check, actual) { - var parts = check.split('.').map(function(part) { - return parseInt(part, 10); - }); - for (var i = 0; i < parts.length; i++) { - if (actual[i] > parts[i]) return true; - else if (actual[i] < parts[i]) return false; - } -}; + }); +} // Begin test case definitions @@ -201,17 +176,17 @@ defineTypeTest('int_unsigned', [ defineTypeTest('double', [ 'DOUBLE NULL' ], [ - [1.0], [-1.0], [123.456], [-13.47], [0.00005], [-0.00005], + [0], [1.0], [-1.0], [123.456], [-13.47], [0.00005], [-0.00005], [8589934592.123], [-8589934592.123], [null] ]); defineTypeTest('float', [ 'FLOAT NULL' ], [ - [1.0], [-1.0], [123.456], [-13.47], [3999.12] + [0], [1.0], [-1.0], [123.456], [-13.47], [3999.12] ], function(test, event) { // Ensure sum of differences is very low - var diff = event.rows.reduce(function(prev, cur, index) { + const diff = event.rows.reduce(function(prev, cur, index) { return prev + Math.abs(cur.col0 - this[index].col0); }.bind(this), 0); test.ok(diff < 0.001); @@ -261,20 +236,6 @@ defineTypeTest('time_no_fraction', [ ["'-01:27:28'"], ]); -defineTypeTest('time_fraction', [ - 'TIME(0) NULL', - 'TIME(1) NULL', - 'TIME(3) NULL', - 'TIME(6) NULL' -], [ - ["'-00:00:01'", "'-00:00:01.1'", "'-00:00:01.002'", "'-00:00:01.123456'"], - ["'00:00:00'", "'00:00:00.2'", "'00:00:00.123'", "'-00:00:00.000001'"], - ["'00:07:00'", "'00:07:00.3'", "'00:07:00.654'", "'00:07:00.010203'"], - ["'20:00:00'", "'20:00:00.4'", "'20:00:00.090'", "'20:00:00.987654'"], - ["'19:00:00'", "'19:00:00.5'", "'19:00:00.999'", "'19:00:00.000001'"], - ["'04:00:00'", "'04:00:00.0'", "'04:00:00.01'", "'04:00:00.1'"], -], '5.6.4'); - defineTypeTest('datetime_no_fraction', [ 'DATETIME NULL' ], [ @@ -283,30 +244,6 @@ defineTypeTest('datetime_no_fraction', [ ["'2014-12-27 01:07:08'"] ]); -defineTypeTest('datetime_fraction', [ - 'DATETIME(0) NULL', - 'DATETIME(1) NULL', - 'DATETIME(4) NULL', - 'DATETIME(6) NULL' -], [ - ["'1000-01-01 00:00:00'", "'1000-01-01 00:00:00.5'", - "'1000-01-01 00:00:00.9999'", "'1000-01-01 00:00:00.123456'"], - ["'9999-12-31 23:59:59'", "'9999-12-31 23:59:59.9'", - "'9999-12-31 23:59:59.6543'", "'9999-12-31 23:59:59.000001'"], - ["'9999-12-31 23:59:59'", "'9999-12-31 23:59:59.1'", - "'9999-12-31 23:59:59.1234'", "'9999-12-31 23:59:59.4326'" ], - ["'2014-12-27 01:07:08'", "'2014-12-27 01:07:08.0'", - "'2014-12-27 01:07:08.0001'", "'2014-12-27 01:07:08.05'" ] -], '5.6.4'); - -defineTypeTest('timestamp_fractional', [ - 'TIMESTAMP(3) NULL', -], [ - ["'1970-01-01 00:00:01.123'"], - ["'2038-01-18 03:14:07.900'"], - ["'2014-12-27 01:07:08.001'"], -], '5.6.4'); - defineTypeTest('temporal_other', [ 'DATE NULL', 'TIMESTAMP NULL', @@ -339,163 +276,209 @@ defineTypeTest('text', [ [null, null, null, null] ]); -defineTypeTest('utf8mb4', [ - 'VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' -], [ - ["'á'"], // 3 byte character - ["'𠜎'"], // 4 byte character -], '5.5.3'); +// ======= below require different version of MySQL ======= -defineTypeTest('datetime_then_decimal', [ - 'DATETIME(3) NULL', - 'DECIMAL(30, 10) NULL' -], [ - ["'1000-01-01 00:00:00.123'", 10.10], - ["'9999-12-31 23:59:59.001'", -123.45], - ["'2014-12-27 01:07:08.053'", 12345.123] -], '5.6.4'); +testDb.requireVersion('5.5.3', () => { + defineTypeTest('utf8mb4', [ + 'VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' + ], [ + ["'á'"], // 3 byte character + ["'𠜎'"], // 4 byte character + ]); +}); -defineTypeTest('json', [ - 'JSON NULL' -], [ - // Small Object - ['\'{"key1": "value1", "key2": "value2", "key3": 34}\''], - // Small Object with nested object - ['\'{"key1": { "key2": "value2", "key3": 34 } }\''], - // Small Object with double nested object - ['\'{"key1": { "key2": { "key2": "value2", "key3": 34 }, "key3": 34 } }\''], - // Small Object with unicode character in key and value - ['\'{ "key2": "válue2", "keybá3": 34 }\''], - // Large Object - ['\'{' + strRepeat('"key##": "value##", ', 2839) + '"keyLast": 34}\''], - // Large Object with nested small objects - ['\'{' + strRepeat('"key##": {"subkey": "value##"}, ', 2000) + '"keyLast": 34}\''], - // Large Object with nested small arrays - ['\'{' + strRepeat('"key##": ["a", ##], ', 3000) + '"keyLast": 34}\''], - // Small array - ['\'["a", "b", 1]\''], - // Small array with nested array - ['\'["a", [2, "b"], 1]\''], - // Small array with double nested array - ['\'["a", [2, ["b", 4, 54]], 1]\''], - // Large Array - ['\'[' + strRepeat('"value##", ', 6000) + '34]\''], - // Large Array with nested small objects - ['\'[' + strRepeat('{"key##": "value##"}, ', 6000) + '34]\''], - // Large Array with nested small arrays - ['\'[' + strRepeat('[##, "value##"], ', 6000) + '34]\''], - // Strings of various lengths - ['\'"hello"\''], - ['\'{"twobytelen": "' + strRepeat('a', 256) + '"}\''], - ['\'{"twobytelen": "' + strRepeat('a', 257) + '"}\''], - ['\'{"twobytelen": "' + strRepeat('a', 258) + '"}\''], - ['\'{"twobytelen": "' + strRepeat('a', 7383) + '"}\''], - ['\'{"twobytelen": "' + strRepeat('a', 16383) + '"}\''], - ['\'{"threebytelen": "' + strRepeat('a', 16388) + '"}\''], - // Integers - ['\'{"key1": -10, "keyb": 34}\''], - ['\'10\''], - ['\'2147483647\''], // Int32 - ['\'-2147483647\''], // Int32 - ['\'2147483648\''], // Int64 - ['\'4294967295\''], // Int64 - ['\'-4294967295\''], // Int64 - ['\'9007199254740992\''], // UInt64 - ['\'-9007199254740992\''], // Int64 - ['\'3e2\''], - ['\'-3e-2\''], - // Doubles - ['\'10.123\''], - ['\'{"doubleval": "-123.38439", "another": 1283192.0004}\''], - // Literals - ['\'{"literaltest1": null, "literal2": true, "literal3": false}\''], - ['\'{"literaltest1": null, "stringafter": "heyos", "number": 35}\''], - ['\'null\''], - ['\'true\''], - ['\'false\''], - // Opaque custom data - ['JSON_OBJECT(\'key\', BINARY \'hi\')'], - ['JSON_OBJECT(\'key\', MAKEDATE(2014,361))'], - ['JSON_OBJECT(\'key\', DATE(\'100-01-01\'))'], - ['JSON_OBJECT(\'key\', DATE(\'1000-01-01\'))'], - ['JSON_OBJECT(\'key\', DATE(\'1000-01-02\'))'], - ['JSON_OBJECT(\'key\', DATE(\'1000-01-03\'))'], - ['JSON_OBJECT(\'key\', DATE(\'1000-02-01\'))'], - ['JSON_OBJECT(\'key\', DATE(\'1000-12-31\'))'], - ['JSON_OBJECT(\'key\', DATE(\'2001-01-01\'))'], - ['JSON_OBJECT(\'key\', DATE(\'2002-01-01\'))'], - ['JSON_OBJECT(\'key\', DATE(\'2003-01-01\'))'], - ['JSON_OBJECT(\'key\', DATE(\'2004-01-01\'))'], - ['JSON_OBJECT(\'key\', DATE(\'9999-01-01\'))'], - ['JSON_OBJECT(\'key\', DATE(\'9999-12-31\'))'], - ['JSON_OBJECT(\'key\', DATE(\'2002-02-02\'))'], - ['JSON_OBJECT(\'key\', DATE(\'2002-03-03\'))'], - ['JSON_OBJECT(\'key\', DATE(\'2002-12-12\'))'], - ['JSON_OBJECT(\'key\', MAKETIME(-838,59,59))'], - ['JSON_OBJECT(\'key\', MAKETIME(838,59,59))'], - ['JSON_OBJECT(\'zero\', MAKETIME(0,0,0))'], - ['JSON_OBJECT(\'onehour\', MAKETIME(1,0,0))'], - ['JSON_OBJECT(\'oneminu\', MAKETIME(0,1,0))'], - ['JSON_OBJECT(\'oneseco\', MAKETIME(0,0,1))'], - ['JSON_OBJECT(\'hurnsec\', MAKETIME(1,0,1))'], - ['JSON_OBJECT(\'minnsec\', MAKETIME(0,1,1))'], - ['JSON_OBJECT(\'2minsec\', MAKETIME(0,2,2))'], - ['JSON_OBJECT(\'2min15sec\', MAKETIME(0,2,15))'], - ['JSON_OBJECT(\'2min16sec\', MAKETIME(0,2,16))'], - ['JSON_OBJECT(\'2min32sec\', MAKETIME(0,2,32))'], - ['JSON_OBJECT(\'2min59sec\', MAKETIME(0,2,59))'], - ['JSON_OBJECT(\'key\', MAKETIME(0,59,0))'], - ['JSON_OBJECT(\'key\', MAKETIME(0,0,59))'], - ['JSON_OBJECT(\'key\', MAKETIME(20,15,10))'], - ['JSON_OBJECT(\'key\', MAKETIME(21,15,10))'], - ['JSON_OBJECT(\'key\', MAKETIME(22,15,10))'], - ['JSON_OBJECT(\'oneseco\', MAKETIME(0,0,1.123))'], - ['JSON_OBJECT(\'oneseco\', MAKETIME(0,0,1.000123))'], - ['JSON_OBJECT(\'key\', MAKETIME(-20,00,00))'], - ['JSON_OBJECT(\'-59min\', TIME(\'-00:00:00.003\'))'], - ['JSON_OBJECT(\'-59min\', TIME(\'00:00:00.003\'))'], - ['JSON_OBJECT(\'-59min\', TIME(\'-00:59:59\'))'], - ['JSON_OBJECT(\'-59min\', TIME(\'-00:59:59.0003\'))'], - ['JSON_OBJECT(\'-1hr\', MAKETIME(-1,00,00))'], - ['JSON_OBJECT(\'-2hr\', MAKETIME(-2,00,00))'], - ['JSON_OBJECT(\'-1hr1sec\', MAKETIME(-1,00,1))'], - ['JSON_OBJECT(\'-1hr1sec\', MAKETIME(-1,00,0.1))'], - ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-27\'))'], - ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-27 01:07:08\'))'], - ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-27 01:07:08.123\'))'], - ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-27 01:07:08.000456\'))'], - ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-28\'))'], - ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-29\'))'], - ['JSON_OBJECT(\'key\', TIMESTAMP(\'2003-12-31 12:00:00\'))'], - ['JSON_OBJECT(\'key\', TIMESTAMP(\'2003-12-31 12:00:00.123\'))'], - ['JSON_OBJECT(\'key\', UNIX_TIMESTAMP(\'2015-11-13 10:20:19.012\'))'], -], function(test, event) { - // JSON from MySQL client has different whitespace than JSON.stringify - // Therefore, parse and perform deep equality - event.rows.forEach(function(row, index) { - // test.deepEqual does not work when comparison objects exceed 65536 bytes - // Perform alternative assertions for these large cases - var expected = JSON.parse(this[index].col0); - var actual = JSON.parse(row.col0); - if (this[index].col0.length > 65536) { - // Large cases are either array or object - if (expected instanceof Array) { - test.strictEqual(expected.length, actual.length); - for (var i = 0; i < expected.length; i++) { - test.deepEqual(expected[i], actual[i]); +testDb.requireVersion('5.6.4', () => { + defineTypeTest('time_fraction', [ + 'TIME(0) NULL', + 'TIME(1) NULL', + 'TIME(3) NULL', + 'TIME(6) NULL' + ], [ + ["'-00:00:01'", "'-00:00:01.1'", "'-00:00:01.002'", "'-00:00:01.123456'"], + ["'00:00:00'", "'00:00:00.2'", "'00:00:00.123'", "'-00:00:00.000001'"], + ["'00:07:00'", "'00:07:00.3'", "'00:07:00.654'", "'00:07:00.010203'"], + ["'20:00:00'", "'20:00:00.4'", "'20:00:00.090'", "'20:00:00.987654'"], + ["'19:00:00'", "'19:00:00.5'", "'19:00:00.999'", "'19:00:00.000001'"], + ["'04:00:00'", "'04:00:00.0'", "'04:00:00.01'", "'04:00:00.1'"], + ]); + + defineTypeTest('datetime_fraction', [ + 'DATETIME(0) NULL', + 'DATETIME(1) NULL', + 'DATETIME(4) NULL', + 'DATETIME(6) NULL' + ], [ + ["'1000-01-01 00:00:00'", "'1000-01-01 00:00:00.5'", + "'1000-01-01 00:00:00.9999'", "'1000-01-01 00:00:00.123456'"], + ["'9999-12-31 23:59:59'", "'9999-12-31 23:59:59.9'", + "'9999-12-31 23:59:59.6543'", "'9999-12-31 23:59:59.000001'"], + ["'9999-12-31 23:59:59'", "'9999-12-31 23:59:59.1'", + "'9999-12-31 23:59:59.1234'", "'9999-12-31 23:59:59.4326'" ], + ["'2014-12-27 01:07:08'", "'2014-12-27 01:07:08.0'", + "'2014-12-27 01:07:08.0001'", "'2014-12-27 01:07:08.05'" ] + ]); + + defineTypeTest('timestamp_fractional', [ + 'TIMESTAMP(3) NULL', + ], [ + ["'1970-01-01 00:00:01.123'"], + ["'2038-01-18 03:14:07.900'"], + ["'2014-12-27 01:07:08.001'"], + ]); + + defineTypeTest('datetime_then_decimal', [ + 'DATETIME(3) NULL', + 'DECIMAL(30, 10) NULL' + ], [ + ["'1000-01-01 00:00:00.123'", 10.10], + ["'9999-12-31 23:59:59.001'", -123.45], + ["'2014-12-27 01:07:08.053'", 12345.123] + ]); +}); + +testDb.requireVersion('5.7.8', () => { + defineTypeTest('json', [ + 'JSON NULL' + ], [ + // Small Object + ['\'{"key1": "value1", "key2": "value2", "key3": 34}\''], + // Small Object with nested object + ['\'{"key1": { "key2": "value2", "key3": 34 } }\''], + // Small Object with double nested object + ['\'{"key1": { "key2": { "key2": "value2", "key3": 34 }, "key3": 34 } }\''], + // Small Object with unicode character in key and value + ['\'{ "key2": "válue2", "keybá3": 34 }\''], + // Large Object + ['\'{' + strRepeat('"key##": "value##", ', 2839) + '"keyLast": 34}\''], + // Large Object with nested small objects + ['\'{' + strRepeat('"key##": {"subkey": "value##"}, ', 2000) + '"keyLast": 34}\''], + // Large Object with nested small arrays + ['\'{' + strRepeat('"key##": ["a", ##], ', 3000) + '"keyLast": 34}\''], + // Small array + ['\'["a", "b", 1]\''], + // Small array with nested array + ['\'["a", [2, "b"], 1]\''], + // Small array with double nested array + ['\'["a", [2, ["b", 4, 54]], 1]\''], + // Large Array + ['\'[' + strRepeat('"value##", ', 6000) + '34]\''], + // Large Array with nested small objects + ['\'[' + strRepeat('{"key##": "value##"}, ', 6000) + '34]\''], + // Large Array with nested small arrays + ['\'[' + strRepeat('[##, "value##"], ', 6000) + '34]\''], + // Strings of various lengths + ['\'"hello"\''], + ['\'{"twobytelen": "' + strRepeat('a', 256) + '"}\''], + ['\'{"twobytelen": "' + strRepeat('a', 257) + '"}\''], + ['\'{"twobytelen": "' + strRepeat('a', 258) + '"}\''], + ['\'{"twobytelen": "' + strRepeat('a', 7383) + '"}\''], + ['\'{"twobytelen": "' + strRepeat('a', 16383) + '"}\''], + ['\'{"threebytelen": "' + strRepeat('a', 16388) + '"}\''], + // Integers + ['\'{"key1": -10, "keyb": 34}\''], + ['\'10\''], + ['\'2147483647\''], // Int32 + ['\'-2147483647\''], // Int32 + ['\'2147483648\''], // Int64 + ['\'4294967295\''], // Int64 + ['\'-4294967295\''], // Int64 + ['\'9007199254740992\''], // UInt64 + ['\'-9007199254740992\''], // Int64 + ['\'3e2\''], + ['\'-3e-2\''], + // Doubles + ['\'10.123\''], + ['\'{"doubleval": "-123.38439", "another": 1283192.0004}\''], + // Literals + ['\'{"literaltest1": null, "literal2": true, "literal3": false}\''], + ['\'{"literaltest1": null, "stringafter": "heyos", "number": 35}\''], + ['\'null\''], + ['\'true\''], + ['\'false\''], + // Opaque custom data + ['JSON_OBJECT(\'key\', BINARY \'hi\')'], + ['JSON_OBJECT(\'key\', MAKEDATE(2014,361))'], + ['JSON_OBJECT(\'key\', DATE(\'100-01-01\'))'], + ['JSON_OBJECT(\'key\', DATE(\'1000-01-01\'))'], + ['JSON_OBJECT(\'key\', DATE(\'1000-01-02\'))'], + ['JSON_OBJECT(\'key\', DATE(\'1000-01-03\'))'], + ['JSON_OBJECT(\'key\', DATE(\'1000-02-01\'))'], + ['JSON_OBJECT(\'key\', DATE(\'1000-12-31\'))'], + ['JSON_OBJECT(\'key\', DATE(\'2001-01-01\'))'], + ['JSON_OBJECT(\'key\', DATE(\'2002-01-01\'))'], + ['JSON_OBJECT(\'key\', DATE(\'2003-01-01\'))'], + ['JSON_OBJECT(\'key\', DATE(\'2004-01-01\'))'], + ['JSON_OBJECT(\'key\', DATE(\'9999-01-01\'))'], + ['JSON_OBJECT(\'key\', DATE(\'9999-12-31\'))'], + ['JSON_OBJECT(\'key\', DATE(\'2002-02-02\'))'], + ['JSON_OBJECT(\'key\', DATE(\'2002-03-03\'))'], + ['JSON_OBJECT(\'key\', DATE(\'2002-12-12\'))'], + ['JSON_OBJECT(\'key\', MAKETIME(-838,59,59))'], + ['JSON_OBJECT(\'key\', MAKETIME(838,59,59))'], + ['JSON_OBJECT(\'zero\', MAKETIME(0,0,0))'], + ['JSON_OBJECT(\'onehour\', MAKETIME(1,0,0))'], + ['JSON_OBJECT(\'oneminu\', MAKETIME(0,1,0))'], + ['JSON_OBJECT(\'oneseco\', MAKETIME(0,0,1))'], + ['JSON_OBJECT(\'hurnsec\', MAKETIME(1,0,1))'], + ['JSON_OBJECT(\'minnsec\', MAKETIME(0,1,1))'], + ['JSON_OBJECT(\'2minsec\', MAKETIME(0,2,2))'], + ['JSON_OBJECT(\'2min15sec\', MAKETIME(0,2,15))'], + ['JSON_OBJECT(\'2min16sec\', MAKETIME(0,2,16))'], + ['JSON_OBJECT(\'2min32sec\', MAKETIME(0,2,32))'], + ['JSON_OBJECT(\'2min59sec\', MAKETIME(0,2,59))'], + ['JSON_OBJECT(\'key\', MAKETIME(0,59,0))'], + ['JSON_OBJECT(\'key\', MAKETIME(0,0,59))'], + ['JSON_OBJECT(\'key\', MAKETIME(20,15,10))'], + ['JSON_OBJECT(\'key\', MAKETIME(21,15,10))'], + ['JSON_OBJECT(\'key\', MAKETIME(22,15,10))'], + ['JSON_OBJECT(\'oneseco\', MAKETIME(0,0,1.123))'], + ['JSON_OBJECT(\'oneseco\', MAKETIME(0,0,1.000123))'], + ['JSON_OBJECT(\'key\', MAKETIME(-20,00,00))'], + ['JSON_OBJECT(\'-59min\', TIME(\'-00:00:00.003\'))'], + ['JSON_OBJECT(\'-59min\', TIME(\'00:00:00.003\'))'], + ['JSON_OBJECT(\'-59min\', TIME(\'-00:59:59\'))'], + ['JSON_OBJECT(\'-59min\', TIME(\'-00:59:59.0003\'))'], + ['JSON_OBJECT(\'-1hr\', MAKETIME(-1,00,00))'], + ['JSON_OBJECT(\'-2hr\', MAKETIME(-2,00,00))'], + ['JSON_OBJECT(\'-1hr1sec\', MAKETIME(-1,00,1))'], + ['JSON_OBJECT(\'-1hr1sec\', MAKETIME(-1,00,0.1))'], + ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-27\'))'], + ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-27 01:07:08\'))'], + ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-27 01:07:08.123\'))'], + ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-27 01:07:08.000456\'))'], + ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-28\'))'], + ['JSON_OBJECT(\'key\', TIMESTAMP(\'2014-12-29\'))'], + ['JSON_OBJECT(\'key\', TIMESTAMP(\'2003-12-31 12:00:00\'))'], + ['JSON_OBJECT(\'key\', TIMESTAMP(\'2003-12-31 12:00:00.123\'))'], + ['JSON_OBJECT(\'key\', UNIX_TIMESTAMP(\'2015-11-13 10:20:19.012\'))'], + ], function(test, event) { // caution here , don't use arrow function + // JSON from MySQL client has different whitespace than JSON.stringify + // Therefore, parse and perform deep equality + event.rows.forEach((row, index) => { + // test.deepEqual does not work when comparison objects exceed 65536 bytes + // Perform alternative assertions for these large cases + const expected = JSON.parse(this[index].col0); + const actual = JSON.parse(row.col0); + if (this[index].col0.length > 65536) { + // Large cases are either array or object + if (expected instanceof Array) { + test.strictEqual(expected.length, actual.length); + for (let i = 0; i < expected.length; i++) { + test.deepEqual(expected[i], actual[i]); + } + } else { + const expectedKeys = Object.keys(expected); + const actualKeys = Object.keys(actual); + test.strictEqual(expectedKeys.length, actualKeys.length); + test.deepEqual(expectedKeys, actualKeys); + for (let j = 0; j < expectedKeys.length; j++) { + test.deepEqual(expected[expectedKeys[j]], actual[expectedKeys[j]]); + } } } else { - var expectedKeys = Object.keys(expected); - var actualKeys = Object.keys(actual); - test.strictEqual(expectedKeys.length, actualKeys.length); - test.deepEqual(expectedKeys, actualKeys); - for (var j = 0; j < expectedKeys.length; j++) { - test.deepEqual(expected[expectedKeys[j]], actual[expectedKeys[j]]); - } + // Comparison objects are smaller than 65536 bytes + test.deepEqual(expected, actual); } - } else { - // Comparison objects are smaller than 65536 bytes - test.deepEqual(expected, actual); - } - }.bind(this)); -}, '5.7.8'); + }); + }); +});