├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── examples ├── README.md └── connection │ ├── mysqlWrapper.js │ ├── query.js │ ├── querystream.js │ ├── returnArgumentsArray.js │ └── typescript │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── employee.ts │ ├── mysqlWrapper.ts │ ├── query.ts │ ├── querystream.ts │ └── returnArgumentsArray.ts │ └── tsconfig.json ├── index.d.ts ├── index.js ├── lib ├── connection.js ├── helper.js ├── pool.js ├── poolCluster.js └── poolConnection.js ├── package-lock.json ├── package.json ├── tap-snapshots └── test │ └── helper.js.test.cjs └── test ├── connection.js ├── helper.js ├── pool.js ├── poolCluster.js └── poolConnection.js /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x, 14.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm ci 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | .tern-project 31 | .jscsrc 32 | .DS_Store 33 | .nyc_output 34 | .vscode 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tap-snapshots 3 | test 4 | .travis.yml 5 | .DS_Store 6 | .vscode 7 | .nyc_output 8 | coverage 9 | examples 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Luke Bonaccorsi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Promise-mysql 2 | ================== 3 | [![Build Status](https://travis-ci.org/lukeb-uk/node-promise-mysql.svg?style=flat&branch=master)](https://travis-ci.org/lukeb-uk/node-promise-mysql?branch=master) 4 | [![Greenkeeper badge](https://badges.greenkeeper.io/lukeb-uk/node-promise-mysql.svg)](https://greenkeeper.io/) 5 | 6 | Promise-mysql is a wrapper for [mysqljs/mysql](https://github.com/mysqljs/mysql) that wraps function calls with [Bluebird](https://github.com/petkaantonov/bluebird/) promises. 7 | 8 | ## API 9 | 10 | ### mysql.createConnection(connectionOptions) 11 | This will return a the promise of a [connection](#connection-object-methods) object. 12 | 13 | #### Parameters 14 | `connectionOptions` _object_: A [connectionOptions](#connectionoptions-object) object 15 | 16 | #### Return value 17 | A Bluebird `Promise` that resolves to a [connection](#connection-object-methods) object 18 | 19 | ### mysql.createPool(poolOptions) 20 | This will return a the promise of a [pool](#pool-object-methods) object. 21 | 22 | #### Parameters 23 | `poolOptions` _object_: A [poolOptions](#pooloptions-object) object 24 | 25 | #### Return value 26 | A Bluebird `Promise` that resolves to a [pool](#pool-object-methods) object 27 | 28 | ### mysql.createPoolCluster(poolClusterOptions) 29 | This will return a the promise of a [poolCluster](#poolcluster-object-methods) object. 30 | 31 | #### Parameters 32 | `poolClusterOptions` _object_: A [poolClusterOptions](#poolclusteroptions-object) object 33 | 34 | #### Return value 35 | A Bluebird `Promise` that resolves to a [poolCluster](#poolcluster-object-methods) object 36 | 37 | ### connectionOptions object 38 | 39 | In addition to the [connection options in mysqljs/mysql](https://github.com/mysqljs/mysql#connection-options), promise-mysql accepts the following: 40 | 41 | `returnArgumentsArray` _boolean_: If set to true then methods will return an array with the callback arguments from the underlying method (excluding the any errors) and the return value from the call. 42 | 43 | `mysqlWrapper` _function_: A function that is passed the `mysql` object so that it can be wrapped with something like the [aws-xray-sdk module](https://www.npmjs.com/package/aws-xray-sdk). This function must either return the wrapped `mysql` object, return a promise of the wrapped `mysql` object or call the callback that is passed into the function. 44 | 45 | `reconnect` _boolean_ (default: true): If set to true then the connection will reconnect on the `PROTOCOL_CONNECTION_LOST`, `ECONNRESET` and `PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR` errors. 46 | 47 | #### Function arguments 48 | 49 | `mysql` _mysql object_: The mysql object 50 | 51 | `callback(error, success)` _function_: A node-style callback that can be used to pass the wrapped version of the mysql object out of the wrapper function. 52 | 53 | ### Connection object methods 54 | 55 | `connection.query`: Perform a query. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#performing-queries) 56 | 57 | `connection.queryStream`: Perform a query, but return the query object for streaming. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#streaming-query-rows) 58 | 59 | `connection.beginTransaction`: Begin a transaction. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#transactions) 60 | 61 | `connection.commit`: Commit a transaction. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#transactions) 62 | 63 | `connection.rollback`: Roll back a transaction. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#transactions) 64 | 65 | `connection.changeUser`: Change the current connected user. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#switching-users-and-altering-connection-state) 66 | 67 | `connection.ping`: Send a ping to the server. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#ping) 68 | 69 | `connection.end`: End the connection. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#terminating-connections) 70 | 71 | `connection.destroy`: Destroy the connection. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#terminating-connections) 72 | 73 | `connection.pause`: Pause a connection. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#streaming-query-rows) 74 | 75 | `connection.resume`: Resume a connection. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#streaming-query-rows) 76 | 77 | `connection.escape`: Escape query values. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#escaping-query-values) 78 | 79 | `connection.escapeId`: Escape query identifiers. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#escaping-query-identifiers) 80 | 81 | `connection.format`: Prepare a query. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#preparing-queries) 82 | 83 | `connection.on`: Add a listener to the connection object. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql) for events that may be listened for. 84 | 85 | ### poolOptions object 86 | 87 | In addition to the [pool options in mysqljs/mysql](https://www.npmjs.com/package/mysql#pool-options), promise-mysql accepts the following: 88 | 89 | `returnArgumentsArray` _boolean_: If set to true then methods will return an array with the callback arguments from the underlying method (excluding the any errors) and the return value from the call. 90 | 91 | `mysqlWrapper` _function_: A function that is passed the `mysql` object so that it can be wrapped with something like the [aws-xray-sdk module](https://www.npmjs.com/package/aws-xray-sdk). This function must either return the wrapped `mysql` object, return a promise of the wrapped `mysql` object or call the callback that is passed into the function. 92 | 93 | #### Function arguments 94 | 95 | `mysql` _mysql object_: The mysql object 96 | 97 | `callback(error, success)` _function_: A node-style callback that can be used to pass the wrapped version of the mysql object out of the wrapper function. 98 | 99 | ### Pool object methods 100 | 101 | `pool.getConnection`: Get a [poolConnection](#poolconnection-object-methods) from the pool. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#pooling-connections) 102 | 103 | `pool.query`: Get a connection from the pool, run a query and then release it back into the pool. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#pooling-connections) 104 | 105 | `pool.end`: End all the connections in a pool. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#closing-all-the-connections-in-a-pool) 106 | 107 | `pool.escape`: Escape query values. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#escaping-query-values) 108 | 109 | `pool.escapeId`: Escape query identifiers. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#escaping-query-identifiers) 110 | 111 | `pool.on`: Add a listener to the pool object. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#pool-events) for events that may be listened for. 112 | 113 | ### poolConnection object methods 114 | 115 | In addition to the [methods in the connection object](#connection-object-methods), poolConnections also has the following method: 116 | 117 | `poolConnection.release`: Release a connection back to the pool. See [mysqljs/mysql documentation](https://www.npmjs.com/package/mysql#pooling-connections) 118 | 119 | ### poolClusterOptions object 120 | 121 | The options used to create a pool cluster. See [mysqljs/mysql documentation](https://www.npmjs.com/package/mysql#poolcluster-options) 122 | 123 | ### poolCluster object methods 124 | 125 | `poolCluster.add`: Adds a pool configuration. See [mysqljs/mysql documentation](https://www.npmjs.com/package/mysql#poolcluster) 126 | 127 | `poolCluster.remove`: Removes pool configurations. See [mysqljs/mysql documentation](https://www.npmjs.com/package/mysql#poolcluster) 128 | 129 | `poolCluster.getConnection`: Get a [poolConnection](#poolconnection-object-methods) from the pool cluster. See [mysqljs/mysql documentation](https://www.npmjs.com/package/mysql#poolcluster) 130 | 131 | `poolCluster.of`: Get a pool from the pool cluster. See [mysqljs/mysql documentation](https://www.npmjs.com/package/mysql#poolcluster) 132 | 133 | `poolCluster.end`: End all the connections in a pool cluster. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#pooling-connections) 134 | 135 | `poolCluster.on`: Add a listener to the pool cluster object. See [mysqljs/mysql documentation](https://github.com/mysqljs/mysql#poolcluster) for events that may be listened for. 136 | 137 | ## Upgrading from v3 to v4 138 | The main difference is that `mysql.createPool` now returns a promise. Besides this, the API is the same and you should be able to upgrade straight to v4. The only other difference is the extra options in the [connectionOptions object](#connectionoptions-object). 139 | 140 | ## Upgrading from v4 to v5 141 | The `pool.releaseConnection` has been removed, please use `poolConnection.release` instead. 142 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | These examples run against the [test_db](https://github.com/datacharmer/test_db) dataset. It expects the database to be named `employees` and expects the root password to be `password`. 4 | 5 | To run a docker container with this data and bound to the correct port, you can run the following: 6 | 7 | ``` 8 | docker run -p 3306:3306 --name mysql_container -e MYSQL_ROOT_PASSWORD=password -d genschsa/mysql-employees 9 | ``` 10 | 11 | ### Typescript 12 | 13 | In the `typescript` folder there is a sample project with the examples that demostrate how this library would be used in a typescript project. In order to run them you need to navigate in the project's folder and then install the npm packages with `npm install` and then you can use `npm run ts-node` followed by the name of the file. So for example you can run `npm run ts-node .src/query.ts` 14 | 15 | ### Note 16 | 17 | These examples are using `ts-node` in order to avoid including a bundler. If you try to compile the typescript files to javascript using the `outDir` option in `tsconfig` then you will get import errors because we are pulling the `index` file form the root. 18 | -------------------------------------------------------------------------------- /examples/connection/mysqlWrapper.js: -------------------------------------------------------------------------------- 1 | const mysql = require('../../index.js'); 2 | const bluebird = require('bluebird'); 3 | 4 | async function runReturn() { 5 | const connection = await mysql.createConnection({ 6 | user: 'root', 7 | password: 'password', 8 | database: 'employees', 9 | mysqlWrapper: (mysqlInstance) => { 10 | return wrapMysql(mysqlInstance, 'runReturn'); 11 | } 12 | }); 13 | 14 | connection.end(); 15 | } 16 | 17 | async function runPromise() { 18 | const connection = await mysql.createConnection({ 19 | user: 'root', 20 | password: 'password', 21 | database: 'employees', 22 | mysqlWrapper: (mysqlInstance) => { 23 | return bluebird.resolve(wrapMysql(mysqlInstance, 'runPromise')); 24 | } 25 | }); 26 | 27 | connection.end(); 28 | } 29 | 30 | async function runCallback() { 31 | const connection = await mysql.createConnection({ 32 | user: 'root', 33 | password: 'password', 34 | database: 'employees', 35 | mysqlWrapper: (mysqlInstance, callback) => { 36 | callback(null, wrapMysql(mysqlInstance, 'runCallback')); 37 | } 38 | }); 39 | 40 | connection.end(); 41 | } 42 | 43 | runReturn(); 44 | runPromise(); 45 | runCallback(); 46 | 47 | 48 | function wrapMysql(mysql, fnName) { 49 | const createConnection = mysql.createConnection; 50 | 51 | mysql.createConnection = function () { 52 | console.log(`createConnection called in ${fnName}!`); 53 | 54 | mysql.createConnection = createConnection; 55 | 56 | return createConnection(...arguments); 57 | } 58 | 59 | return mysql; 60 | } 61 | -------------------------------------------------------------------------------- /examples/connection/query.js: -------------------------------------------------------------------------------- 1 | const mysql = require('../../index.js'); 2 | 3 | function run() { 4 | let connection; 5 | 6 | mysql.createConnection({ 7 | user: 'root', 8 | password: 'password', 9 | database: 'employees' 10 | }).then((conn) => { 11 | connection = conn; 12 | 13 | return connection.query('select * from employees limit 0, 10'); 14 | }).then((employees) => { 15 | employees.forEach(employee => { 16 | console.log(`The employee with the employee number ${employee.emp_no} is ${employee.first_name} ${employee.last_name}`); 17 | }); 18 | 19 | connection.end(); 20 | }); 21 | } 22 | 23 | async function runAwait() { 24 | const connection = await mysql.createConnection({ 25 | user: 'root', 26 | password: 'password', 27 | database: 'employees' 28 | }); 29 | 30 | const employees = await connection.query('select * from employees limit 0, 10'); 31 | 32 | employees.forEach(employee => { 33 | console.log(`The employee with the employee number ${employee.emp_no} is ${employee.first_name} ${employee.last_name}`); 34 | }); 35 | 36 | connection.end(); 37 | } 38 | 39 | run(); 40 | runAwait(); 41 | -------------------------------------------------------------------------------- /examples/connection/querystream.js: -------------------------------------------------------------------------------- 1 | const mysql = require('../../index.js'); 2 | 3 | function run() { 4 | mysql.createConnection({ 5 | user: 'root', 6 | password: 'password', 7 | database: 'employees' 8 | }).then((connection) => { 9 | const employees = connection.queryStream('select * from employees limit 0, 10'); 10 | 11 | employees.on('result', (employee) => { 12 | console.log(`The employee with the employee number ${employee.emp_no} is ${employee.first_name} ${employee.last_name}`); 13 | }); 14 | 15 | employees.on('end', () => { 16 | connection.end(); 17 | }) 18 | }); 19 | } 20 | 21 | async function runAwait() { 22 | const connection = await mysql.createConnection({ 23 | user: 'root', 24 | password: 'password', 25 | database: 'employees' 26 | }); 27 | 28 | const employees = connection.queryStream('select * from employees limit 0, 10'); 29 | 30 | employees.on('result', (employee) => { 31 | console.log(`The employee with the employee number ${employee.emp_no} is ${employee.first_name} ${employee.last_name}`); 32 | }); 33 | 34 | employees.on('end', () => { 35 | connection.end(); 36 | }) 37 | } 38 | 39 | run(); 40 | runAwait(); 41 | -------------------------------------------------------------------------------- /examples/connection/returnArgumentsArray.js: -------------------------------------------------------------------------------- 1 | const mysql = require('../../index.js'); 2 | 3 | function run() { 4 | let connection; 5 | 6 | mysql.createConnection({ 7 | user: 'root', 8 | password: 'password', 9 | database: 'employees', 10 | returnArgumentsArray: true 11 | }).then((conn) => { 12 | connection = conn; 13 | 14 | return connection.query('select * from employees limit 0, 10'); 15 | }).then(([data, fields, query]) => { 16 | console.log(`The SQL for the query was: ${query.sql}\n`); 17 | 18 | console.log(`The table 'employees' contains the following fields:`) 19 | fields.forEach(field => { 20 | console.log(` ${field.name}`); 21 | }) 22 | 23 | console.log('\nThe data retrieved was:') 24 | 25 | data.forEach(employee => { 26 | console.log(` Employee number ${employee.emp_no}: ${employee.first_name} ${employee.last_name}`); 27 | }); 28 | 29 | connection.end(); 30 | }); 31 | } 32 | 33 | async function runAwait() { 34 | const connection = await mysql.createConnection({ 35 | user: 'root', 36 | password: 'password', 37 | database: 'employees', 38 | returnArgumentsArray: true 39 | }); 40 | 41 | const [data, fields, query] = await connection.query('select * from employees limit 0, 10'); 42 | 43 | console.log(`The SQL for the query was: ${query.sql}\n`); 44 | 45 | console.log(`The table 'employees' contains the following fields:`) 46 | fields.forEach(field => { 47 | console.log(` ${field.name}`); 48 | }) 49 | 50 | console.log('\nThe data retrieved was:') 51 | 52 | data.forEach(employee => { 53 | console.log(` Employee number ${employee.emp_no}: ${employee.first_name} ${employee.last_name}`); 54 | }); 55 | 56 | connection.end(); 57 | } 58 | 59 | run(); 60 | runAwait(); 61 | -------------------------------------------------------------------------------- /examples/connection/typescript/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-promisify-mysql", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@cspotcode/source-map-consumer": { 8 | "version": "0.8.0", 9 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", 10 | "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", 11 | "dev": true 12 | }, 13 | "@cspotcode/source-map-support": { 14 | "version": "0.7.0", 15 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", 16 | "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", 17 | "dev": true, 18 | "requires": { 19 | "@cspotcode/source-map-consumer": "0.8.0" 20 | } 21 | }, 22 | "@tsconfig/node10": { 23 | "version": "1.0.8", 24 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", 25 | "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", 26 | "dev": true 27 | }, 28 | "@tsconfig/node12": { 29 | "version": "1.0.9", 30 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", 31 | "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", 32 | "dev": true 33 | }, 34 | "@tsconfig/node14": { 35 | "version": "1.0.1", 36 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", 37 | "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", 38 | "dev": true 39 | }, 40 | "@tsconfig/node16": { 41 | "version": "1.0.2", 42 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", 43 | "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", 44 | "dev": true 45 | }, 46 | "@types/bluebird": { 47 | "version": "3.5.36", 48 | "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", 49 | "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", 50 | "dev": true 51 | }, 52 | "@types/mysql": { 53 | "version": "2.15.21", 54 | "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.21.tgz", 55 | "integrity": "sha512-NPotx5CVful7yB+qZbWtXL2fA4e7aEHkihHLjklc6ID8aq7bhguHgeIoC1EmSNTAuCgI6ZXrjt2ZSaXnYX0EUg==", 56 | "dev": true, 57 | "requires": { 58 | "@types/node": "*" 59 | } 60 | }, 61 | "@types/node": { 62 | "version": "17.0.29", 63 | "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.29.tgz", 64 | "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==", 65 | "dev": true 66 | }, 67 | "acorn": { 68 | "version": "8.7.1", 69 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", 70 | "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", 71 | "dev": true 72 | }, 73 | "acorn-walk": { 74 | "version": "8.2.0", 75 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 76 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 77 | "dev": true 78 | }, 79 | "arg": { 80 | "version": "4.1.3", 81 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 82 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 83 | "dev": true 84 | }, 85 | "bignumber.js": { 86 | "version": "9.0.0", 87 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", 88 | "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", 89 | "dev": true 90 | }, 91 | "bluebird": { 92 | "version": "3.7.2", 93 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 94 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", 95 | "dev": true 96 | }, 97 | "core-util-is": { 98 | "version": "1.0.3", 99 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 100 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 101 | "dev": true 102 | }, 103 | "create-require": { 104 | "version": "1.1.1", 105 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 106 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 107 | "dev": true 108 | }, 109 | "diff": { 110 | "version": "4.0.2", 111 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 112 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 113 | "dev": true 114 | }, 115 | "inherits": { 116 | "version": "2.0.4", 117 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 118 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 119 | "dev": true 120 | }, 121 | "isarray": { 122 | "version": "1.0.0", 123 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 124 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 125 | "dev": true 126 | }, 127 | "make-error": { 128 | "version": "1.3.6", 129 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 130 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 131 | "dev": true 132 | }, 133 | "mysql": { 134 | "version": "2.18.1", 135 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", 136 | "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", 137 | "dev": true, 138 | "requires": { 139 | "bignumber.js": "9.0.0", 140 | "readable-stream": "2.3.7", 141 | "safe-buffer": "5.1.2", 142 | "sqlstring": "2.3.1" 143 | } 144 | }, 145 | "process-nextick-args": { 146 | "version": "2.0.1", 147 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 148 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 149 | "dev": true 150 | }, 151 | "readable-stream": { 152 | "version": "2.3.7", 153 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 154 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 155 | "dev": true, 156 | "requires": { 157 | "core-util-is": "~1.0.0", 158 | "inherits": "~2.0.3", 159 | "isarray": "~1.0.0", 160 | "process-nextick-args": "~2.0.0", 161 | "safe-buffer": "~5.1.1", 162 | "string_decoder": "~1.1.1", 163 | "util-deprecate": "~1.0.1" 164 | } 165 | }, 166 | "safe-buffer": { 167 | "version": "5.1.2", 168 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 169 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 170 | "dev": true 171 | }, 172 | "sqlstring": { 173 | "version": "2.3.1", 174 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", 175 | "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=", 176 | "dev": true 177 | }, 178 | "string_decoder": { 179 | "version": "1.1.1", 180 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 181 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 182 | "dev": true, 183 | "requires": { 184 | "safe-buffer": "~5.1.0" 185 | } 186 | }, 187 | "ts-node": { 188 | "version": "10.7.0", 189 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", 190 | "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", 191 | "dev": true, 192 | "requires": { 193 | "@cspotcode/source-map-support": "0.7.0", 194 | "@tsconfig/node10": "^1.0.7", 195 | "@tsconfig/node12": "^1.0.7", 196 | "@tsconfig/node14": "^1.0.0", 197 | "@tsconfig/node16": "^1.0.2", 198 | "acorn": "^8.4.1", 199 | "acorn-walk": "^8.1.1", 200 | "arg": "^4.1.0", 201 | "create-require": "^1.1.0", 202 | "diff": "^4.0.1", 203 | "make-error": "^1.1.1", 204 | "v8-compile-cache-lib": "^3.0.0", 205 | "yn": "3.1.1" 206 | } 207 | }, 208 | "typescript": { 209 | "version": "4.6.3", 210 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", 211 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", 212 | "dev": true 213 | }, 214 | "util-deprecate": { 215 | "version": "1.0.2", 216 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 217 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 218 | "dev": true 219 | }, 220 | "v8-compile-cache-lib": { 221 | "version": "3.0.1", 222 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 223 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 224 | "dev": true 225 | }, 226 | "yn": { 227 | "version": "3.1.1", 228 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 229 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 230 | "dev": true 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /examples/connection/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-promisify-mysql", 3 | "version": "1.0.0", 4 | "description": "A demo to show how one would use promisify-mysql withing a typescript project", 5 | "scripts": { 6 | "ts-node": "ts-node" 7 | }, 8 | "author": "Michael Kornelakis", 9 | "license": "MIT", 10 | "devDependencies": { 11 | "mysql": "^2.18.1", 12 | "@types/mysql": "^2.15.2", 13 | "@types/bluebird": "^3.5.26", 14 | "bluebird": "^3.5.1", 15 | "ts-node": "^10.7.0", 16 | "typescript": "^4.6.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/connection/typescript/src/employee.ts: -------------------------------------------------------------------------------- 1 | export type Employee = { first_name: string, last_name: string, emp_no: number }; 2 | -------------------------------------------------------------------------------- /examples/connection/typescript/src/mysqlWrapper.ts: -------------------------------------------------------------------------------- 1 | import Bluebird from 'bluebird'; 2 | import * as mysql from '../../../../index'; 3 | 4 | async function runReturn() { 5 | const connection = await mysql.createConnection({ 6 | user: 'root', 7 | password: 'password', 8 | database: 'employees', 9 | mysqlWrapper: (mysqlInstance: mysql.mysqlModule) => wrapMysql(mysqlInstance, 'runReturn') 10 | }); 11 | 12 | connection.end(); 13 | } 14 | 15 | async function runPromise() { 16 | const connection = await mysql.createConnection({ 17 | user: 'root', 18 | password: 'password', 19 | database: 'employees', 20 | mysqlWrapper: (mysqlInstance: mysql.mysqlModule) => Bluebird.resolve(wrapMysql(mysqlInstance, 'runPromise')) as unknown as Promise 21 | }); 22 | 23 | connection.end(); 24 | } 25 | 26 | async function runCallback() { 27 | const connection = await mysql.createConnection({ 28 | user: 'root', 29 | password: 'password', 30 | database: 'employees', 31 | mysqlWrapper: (mysqlInstance: mysql.mysqlModule, callback) => callback?.(null, wrapMysql(mysqlInstance, 'runCallback')) 32 | }); 33 | 34 | connection.end(); 35 | } 36 | 37 | runReturn(); 38 | runPromise(); 39 | runCallback(); 40 | 41 | 42 | function wrapMysql(mysqlInstance: mysql.mysqlModule, fnName: string) { 43 | const createConnection = mysqlInstance.createConnection; 44 | 45 | mysqlInstance.createConnection = function (...args) { 46 | console.log(`createConnection called in ${fnName}!`); 47 | 48 | mysqlInstance.createConnection = createConnection; 49 | 50 | return createConnection(...args); 51 | } 52 | 53 | return mysqlInstance; 54 | } 55 | -------------------------------------------------------------------------------- /examples/connection/typescript/src/query.ts: -------------------------------------------------------------------------------- 1 | import * as mysql from '../../../../index'; 2 | import { Employee } from './employee'; 3 | 4 | async function run() { 5 | let connection: mysql.Connection; 6 | 7 | mysql.createConnection({ 8 | user: 'root', 9 | password: 'password', 10 | database: 'employees' 11 | }).then((conn) => { 12 | connection = conn; 13 | 14 | return connection.query('select * from employees limit 0, 10'); 15 | }).then((employees) => { 16 | employees.forEach(employee => { 17 | console.log(`The employee with the employee number ${employee.emp_no} is ${employee.first_name} ${employee.last_name}`); 18 | }); 19 | 20 | connection.end(); 21 | }) 22 | } 23 | 24 | async function runAwait() { 25 | const connection = await mysql.createConnection({ 26 | user: 'root', 27 | password: 'password', 28 | database: 'employees' 29 | }); 30 | 31 | const employees = await connection.query('select * from employees limit 0, 10'); 32 | 33 | employees.forEach(employee => { 34 | console.log(`The employee with the employee number ${employee.emp_no} is ${employee.first_name} ${employee.last_name}`); 35 | }); 36 | 37 | connection.end(); 38 | } 39 | 40 | run(); 41 | runAwait(); 42 | -------------------------------------------------------------------------------- /examples/connection/typescript/src/querystream.ts: -------------------------------------------------------------------------------- 1 | import * as mysql from '../../../../index'; 2 | import { Employee } from './employee'; 3 | 4 | function run() { 5 | mysql.createConnection({ 6 | user: 'root', 7 | password: 'password', 8 | database: 'employees' 9 | }).then((connection) => { 10 | const employees = connection.queryStream('select * from employees limit 0, 10'); 11 | 12 | employees.on('result', (employee) => { 13 | console.log(`The employee with the employee number ${employee.emp_no} is ${employee.first_name} ${employee.last_name}`); 14 | }); 15 | 16 | employees.on('end', () => { 17 | connection.end(); 18 | }) 19 | }); 20 | } 21 | 22 | async function runAwait() { 23 | const connection = await mysql.createConnection({ 24 | user: 'root', 25 | password: 'password', 26 | database: 'employees' 27 | }); 28 | 29 | const employees = connection.queryStream('select * from employees limit 0, 10'); 30 | 31 | employees.on('result', (employee) => { 32 | console.log(`The employee with the employee number ${employee.emp_no} is ${employee.first_name} ${employee.last_name}`); 33 | }); 34 | 35 | employees.on('end', () => { 36 | connection.end(); 37 | }) 38 | } 39 | 40 | run(); 41 | runAwait(); 42 | -------------------------------------------------------------------------------- /examples/connection/typescript/src/returnArgumentsArray.ts: -------------------------------------------------------------------------------- 1 | import mysql, { ArgumentsArray } from '../../../../index'; 2 | import { Employee } from './employee'; 3 | 4 | function run() { 5 | let connection: mysql.Connection; 6 | 7 | mysql.createConnection({ 8 | user: 'root', 9 | password: 'password', 10 | database: 'employees', 11 | returnArgumentsArray: true 12 | }).then((conn) => { 13 | connection = conn; 14 | 15 | return connection.query>('select * from employees limit 0, 10'); 16 | }).then(([data, fields, query]) => { 17 | console.log(`The SQL for the query was: ${query.sql}\n`); 18 | 19 | console.log(`The table 'employees' contains the following fields:`) 20 | fields.forEach(field => { 21 | console.log(` ${field.name}`); 22 | }) 23 | 24 | console.log('\nThe data retrieved was:') 25 | 26 | data.forEach(employee => { 27 | console.log(` Employee number ${employee.emp_no}: ${employee.first_name} ${employee.last_name}`); 28 | }); 29 | 30 | connection.end(); 31 | }); 32 | } 33 | 34 | async function runAwait() { 35 | const connection = await mysql.createConnection({ 36 | user: 'root', 37 | password: 'password', 38 | database: 'employees', 39 | returnArgumentsArray: true 40 | }); 41 | 42 | const [data, fields, query] = await connection.query>('select * from employees limit 0, 10'); 43 | 44 | console.log(`The SQL for the query was: ${query.sql}\n`); 45 | 46 | console.log(`The table 'employees' contains the following fields:`) 47 | fields.forEach(field => { 48 | console.log(` ${field.name}`); 49 | }) 50 | 51 | console.log('\nThe data retrieved was:') 52 | 53 | data.forEach(employee => { 54 | console.log(` Employee number ${employee.emp_no}: ${employee.first_name} ${employee.last_name}`); 55 | }); 56 | 57 | connection.end(); 58 | } 59 | 60 | run(); 61 | runAwait(); 62 | -------------------------------------------------------------------------------- /examples/connection/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "allowJs": true, 6 | "strict": true, 7 | "paths": {}, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true 10 | }, 11 | "include": ["**/*.ts"], 12 | "exclude": ["node_modules"] 13 | } 14 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as mysql from 'mysql'; 2 | import Bluebird from 'bluebird'; 3 | 4 | export function createConnection(connectionUri: string | ConnectionConfig): Bluebird; 5 | 6 | export function createPool(config: PoolConfig | string): Bluebird; 7 | 8 | export function createPoolCluster(config: mysql.PoolClusterConfig): Bluebird; 9 | 10 | export { Types, escape, escapeId, format, raw, ConnectionOptions, PoolClusterConfig, MysqlError } from 'mysql'; 11 | 12 | export type mysqlModule = typeof mysql; 13 | 14 | export type ArgumentsArray = [data: T[], fields: mysql.FieldInfo[], query: Query]; 15 | export interface ConnectionConfig extends mysql.ConnectionConfig { 16 | mysqlWrapper?: (mysql: mysqlModule, callback?: (err: Error | null, success?: mysqlModule) => void) => mysqlModule | Promise | void; 17 | returnArgumentsArray?: boolean; 18 | reconnect?: boolean; 19 | } 20 | 21 | export interface PoolConfig extends mysql.PoolConfig { 22 | mysqlWrapper?: (mysql: mysqlModule, callback: (err: Error | null, success?: mysqlModule) => void) => mysqlModule | Promise | void; 23 | returnArgumentsArray?: boolean; 24 | reconnect?: boolean; 25 | } 26 | 27 | export interface QueryFunction { 28 | (query: mysql.Query | string | mysql.QueryOptions): Bluebird; 29 | 30 | (options: string, values?: any): Bluebird; 31 | } 32 | 33 | export interface Query extends mysql.Query { 34 | 35 | on(ev: 'result', callback: (row: T, index: number) => void): mysql.Query; 36 | 37 | on(ev: 'error', callback: (err: mysql.MysqlError) => void): mysql.Query; 38 | 39 | on(ev: 'fields', callback: (fields: mysql.FieldInfo[], index: number) => void): mysql.Query; 40 | 41 | on(ev: 'packet', callback: (packet: T) => void): mysql.Query; 42 | 43 | on(ev: 'end', callback: () => void): mysql.Query; 44 | } 45 | 46 | export class Connection { 47 | constructor(config: string | ConnectionConfig, _connection?: Connection); 48 | 49 | query: QueryFunction; 50 | 51 | beginTransaction(options?: mysql.QueryOptions): Bluebird; 52 | 53 | commit(options?: mysql.QueryOptions): Bluebird; 54 | 55 | rollback(options?: mysql.QueryOptions): Bluebird; 56 | 57 | changeUser(options?: mysql.ConnectionOptions): Bluebird; 58 | 59 | ping(options?: mysql.QueryOptions): Bluebird; 60 | 61 | queryStream(options: string, values?: any): Query; 62 | 63 | statistics(options?: mysql.QueryOptions): Bluebird; 64 | 65 | end(options?: mysql.QueryOptions): Bluebird; 66 | 67 | destroy(): void; 68 | 69 | pause(): void; 70 | 71 | resume(): void; 72 | 73 | escape(value: any, stringifyObjects?: boolean, timeZone?: string): string; 74 | 75 | escapeId(value: string, forbidQualified?: boolean): string; 76 | 77 | format(sql: string, values: any[], stringifyObjects?: boolean, timeZone?: string): string; 78 | 79 | on(ev: 'error', callback: (err: mysql.MysqlError) => void): void; 80 | 81 | on(ev: 'end', callback: () => void): void; 82 | } 83 | 84 | export class PoolConnection extends Connection { 85 | constructor(config: ConnectionConfig, _connection?: mysql.Connection); 86 | 87 | release(): any; 88 | } 89 | 90 | export class Pool { 91 | constructor(config: ConnectionConfig, _pool?: mysql.Pool); 92 | 93 | getConnection(): Bluebird; 94 | 95 | query: QueryFunction; 96 | 97 | end(options?: mysql.QueryOptions): Bluebird; 98 | 99 | release(options?: mysql.QueryOptions): Bluebird; 100 | 101 | escape(value: any, stringifyObjects?: boolean, timeZone?: string): string; 102 | 103 | escapeId(value: string, forbidQualified?: boolean): string; 104 | 105 | on(ev: 'connection' | 'acquire' | 'release', callback: (connection: mysql.PoolConnection) => void): mysql.Pool; 106 | 107 | on(ev: 'error', callback: (err: mysql.MysqlError) => void): mysql.Pool; 108 | 109 | on(ev: 'enqueue', callback: (err?: mysql.MysqlError) => void): mysql.Pool; 110 | 111 | on(ev: string, callback: (...args: T[]) => void): mysql.Pool; 112 | } 113 | 114 | export class PoolCluster { 115 | constructor(config: mysql.PoolClusterConfig); 116 | 117 | config: mysql.PoolClusterConfig; 118 | 119 | add(config: PoolConfig): void; 120 | 121 | add(id: string, config: PoolConfig): void; 122 | 123 | end(): Bluebird; 124 | 125 | of(pattern: string, selector?: string): Pool; 126 | of(pattern: undefined | null | false, selector: string): Pool; 127 | 128 | /** 129 | * remove all pools which match pattern 130 | */ 131 | remove(pattern: string): void; 132 | 133 | getConnection(pattern?: string, selector?: string): Bluebird; 134 | 135 | /** 136 | * Set handler to be run on a certain event. 137 | */ 138 | on(ev: string, callback: (...args: T[]) => void): PoolCluster; 139 | 140 | /** 141 | * Set handler to be run when a node is removed or goes offline. 142 | */ 143 | on(ev: 'remove' | 'offline', callback: (nodeId: string) => void): PoolCluster; 144 | } 145 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Connection = require('./lib/connection.js'); 2 | var Pool = require('./lib/pool.js'); 3 | var PoolCluster = require('./lib/poolCluster.js'); 4 | var mysql = require('mysql'); 5 | 6 | exports.createConnection = function(config){ 7 | return new Connection(config); 8 | } 9 | 10 | exports.createPool = function(config){ 11 | return new Pool(config); 12 | } 13 | 14 | exports.createPoolCluster = function(config){ 15 | return new PoolCluster(config); 16 | } 17 | 18 | exports.Types = mysql.Types; 19 | exports.escape = mysql.escape; 20 | exports.escapeId = mysql.escapeId; 21 | exports.format = mysql.format; 22 | exports.raw = mysql.raw; 23 | 24 | exports.PoolCluster = PoolCluster; 25 | exports.Pool = Pool; 26 | exports.Connection = Connection; 27 | -------------------------------------------------------------------------------- /lib/connection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require(`bluebird`); 4 | const mysql = require(`mysql`); 5 | const promiseCallback = require(`./helper`).promiseCallback; 6 | 7 | class connection { 8 | constructor(config = {}, _connection) { 9 | let mysqlValue = mysql; 10 | let mysqlWrapperCallbackPromise; 11 | 12 | if (typeof config !== 'string') { 13 | 14 | if (config.mysqlWrapper) { 15 | let callback; 16 | mysqlWrapperCallbackPromise = new Promise((resolve, reject) => { 17 | callback = (err, mysql) => { 18 | if (err) { 19 | return reject(err); 20 | } 21 | 22 | return resolve(mysql); 23 | } 24 | }) 25 | mysqlValue = config.mysqlWrapper(mysql, callback); 26 | config.mysqlWrapper = undefined; 27 | } 28 | 29 | if (config.returnArgumentsArray) { 30 | this.returnArgumentsArray = config.returnArgumentsArray; 31 | config.returnArgumentsArray = undefined; 32 | } 33 | 34 | if (config.reconnect === true || config.reconnect === undefined) { 35 | this.reconnect = true; 36 | config.reconnect = undefined; 37 | } 38 | } 39 | 40 | this.config = config; 41 | 42 | return Promise.resolve(mysqlValue || mysqlWrapperCallbackPromise).then((mysql) => { 43 | if (_connection && this.reconnect) { 44 | addReconnectHandler(_connection, mysql, this.config, this.reconnect); 45 | } else if (!_connection) { 46 | _connection = connect(mysql, this.config, this.reconnect); 47 | } 48 | 49 | return _connection; 50 | }).then((connection) => { 51 | this.connection = connection; 52 | 53 | return this; 54 | }) 55 | } 56 | 57 | query() { 58 | return promiseCallback.apply(this.connection, [`query`, arguments, this.returnArgumentsArray]); 59 | } 60 | 61 | queryStream(sql, values) { 62 | return this.connection.query(sql, values); 63 | }; 64 | 65 | beginTransaction() { 66 | return promiseCallback.apply(this.connection, [`beginTransaction`, arguments, this.returnArgumentsArray]); 67 | } 68 | 69 | commit() { 70 | return promiseCallback.apply(this.connection, [`commit`, arguments, this.returnArgumentsArray]); 71 | } 72 | 73 | rollback() { 74 | return promiseCallback.apply(this.connection, [`rollback`, arguments, this.returnArgumentsArray]); 75 | } 76 | 77 | changeUser() { 78 | return promiseCallback.apply(this.connection, [`changeUser`, arguments, this.returnArgumentsArray]); 79 | } 80 | 81 | ping() { 82 | return promiseCallback.apply(this.connection, [`ping`, arguments, this.returnArgumentsArray]); 83 | } 84 | 85 | statistics() { 86 | return promiseCallback.apply(this.connection, [`statistics`, arguments, this.returnArgumentsArray]); 87 | } 88 | 89 | end() { 90 | return promiseCallback.apply(this.connection, [`end`, arguments, this.returnArgumentsArray]); 91 | } 92 | 93 | destroy() { 94 | this.connection.destroy(); 95 | } 96 | 97 | pause() { 98 | this.connection.pause(); 99 | } 100 | 101 | resume() { 102 | this.connection.resume(); 103 | } 104 | 105 | escape(value) { 106 | return this.connection.escape(value); 107 | } 108 | 109 | escapeId(value) { 110 | return this.connection.escapeId(value); 111 | } 112 | 113 | format(sql, values) { 114 | return this.connection.format(sql, values); 115 | } 116 | 117 | on(event, fn) { 118 | this.connection.on(event, fn); 119 | } 120 | } 121 | 122 | const connect = (mysql, config, reconnect) => { 123 | const connection = mysql.createConnection(config); 124 | 125 | return new Promise((resolve, reject) => { 126 | connection.connect((err) => { 127 | if (err) { 128 | return reject(err); 129 | } else { 130 | if (reconnect) { 131 | addReconnectHandler(connection, mysql, config, reconnect); 132 | } 133 | 134 | return resolve(connection); 135 | } 136 | }) 137 | }); 138 | } 139 | 140 | const addReconnectHandler = (connection, mysql, config, reconnect) => { 141 | connection.once(`error`, (err) => { 142 | if( 143 | err.code === `PROTOCOL_CONNECTION_LOST` || 144 | err.code === `ECONNRESET` || 145 | err.code === `PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR` 146 | ) { 147 | connect(mysql, config, reconnect); 148 | } 149 | }); 150 | } 151 | 152 | module.exports = connection; 153 | -------------------------------------------------------------------------------- /lib/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | 5 | module.exports = { 6 | promiseCallback: function(functionName, params, returnArgumentsArray = false) { 7 | params = Array.prototype.slice.call(params, 0); 8 | return new Promise((resolve, reject) => { 9 | params.push(function(err) { 10 | const args = Array.prototype.slice.call(arguments, 1); 11 | 12 | if (err) { 13 | return reject(err); 14 | } 15 | 16 | process.nextTick(() => { 17 | if (returnArgumentsArray) { 18 | args.push(call); 19 | return resolve(args) 20 | } 21 | 22 | return resolve(args[0]); 23 | }) 24 | }); 25 | 26 | const call = this[functionName].apply(this, params); 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /lib/pool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const mysql = require('mysql'); 5 | const PoolConnection = require('./poolConnection.js'); 6 | const promiseCallback = require('./helper').promiseCallback; 7 | 8 | class pool { 9 | constructor(config = {}, _pool) { 10 | if (_pool) { 11 | this.pool = _pool; 12 | return this; 13 | } 14 | 15 | let mysqlValue = mysql; 16 | let mysqlWrapperCallbackPromise; 17 | 18 | if (config.mysqlWrapper) { 19 | let callback; 20 | mysqlWrapperCallbackPromise = new Promise((resolve, reject) => { 21 | callback = (err, mysql) => { 22 | if (err) { 23 | return reject(err); 24 | } 25 | 26 | return resolve(mysql); 27 | } 28 | }) 29 | mysqlValue = config.mysqlWrapper(mysql, callback); 30 | delete config.mysqlWrapper; 31 | } 32 | 33 | if (config.returnArgumentsArray) { 34 | this.returnArgumentsArray = config.returnArgumentsArray; 35 | config.returnArgumentsArray = undefined; 36 | } 37 | 38 | return Promise.resolve(mysqlValue || mysqlWrapperCallbackPromise).then((mysql) => { 39 | this.pool = mysql.createPool(config); 40 | return Promise.resolve(this); 41 | }); 42 | } 43 | 44 | getConnection() { 45 | return promiseCallback.apply(this.pool, ['getConnection', arguments]) 46 | .then((_connection) => { 47 | const config = { 48 | returnArgumentsArray: this.returnArgumentsArray, 49 | reconnect: false 50 | }; 51 | 52 | return new PoolConnection(config, _connection); 53 | }); 54 | } 55 | 56 | query() { 57 | return promiseCallback.apply(this.pool, ['query', arguments, this.returnArgumentsArray]); 58 | } 59 | 60 | end() { 61 | return promiseCallback.apply(this.pool, ['end', arguments, this.returnArgumentsArray]); 62 | } 63 | 64 | escape(value) { 65 | return this.pool.escape(value); 66 | } 67 | 68 | escapeId(value) { 69 | return this.pool.escapeId(value); 70 | } 71 | 72 | on(event, fn) { 73 | this.pool.on(event, fn); 74 | } 75 | } 76 | 77 | module.exports = pool; 78 | -------------------------------------------------------------------------------- /lib/poolCluster.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const mysql = require('mysql'); 5 | const Pool = require('./pool'); 6 | const PoolConnection = require('./poolConnection.js'); 7 | const promiseCallback = require('./helper').promiseCallback; 8 | 9 | class poolCluster { 10 | constructor(config = {}) { 11 | if (config.returnArgumentsArray) { 12 | this.returnArgumentsArray = config.returnArgumentsArray; 13 | config.returnArgumentsArray = undefined; 14 | } 15 | 16 | return Promise.resolve(mysql).then((mysql) => { 17 | this.poolCluster = mysql.createPoolCluster(config); 18 | return Promise.resolve(this); 19 | }); 20 | } 21 | 22 | add(id, config) { 23 | return this.poolCluster.add(id, config); 24 | } 25 | 26 | end() { 27 | return promiseCallback.apply(this.poolCluster, ['end', arguments, this.returnArgumentsArray]); 28 | } 29 | 30 | of(pattern, selector) { 31 | const pool = this.poolCluster.of(pattern, selector); 32 | return new Pool(undefined, pool); 33 | } 34 | 35 | remove(pattern) { 36 | return this.poolCluster.remove(pattern); 37 | } 38 | 39 | getConnection() { 40 | return promiseCallback.apply(this.poolCluster, ['getConnection', arguments, this.returnArgumentsArray]) 41 | .then((_connection) => { 42 | const config = { 43 | reconnect: false 44 | }; 45 | 46 | return new PoolConnection(config, _connection); 47 | }); 48 | } 49 | 50 | on(event, fn) { 51 | return this.poolCluster.on(event, fn); 52 | } 53 | } 54 | 55 | module.exports = poolCluster; 56 | -------------------------------------------------------------------------------- /lib/poolConnection.js: -------------------------------------------------------------------------------- 1 | const Connection = require('./connection.js'); 2 | 3 | class poolConnection extends Connection { 4 | constructor(config, _connection) { 5 | super(config, _connection); 6 | } 7 | 8 | release() { 9 | this.connection.release(); 10 | } 11 | } 12 | 13 | module.exports = poolConnection; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promise-mysql", 3 | "version": "5.2.0", 4 | "description": "A bluebird wrapper for node-mysql", 5 | "main": "index.js", 6 | "scripts": { 7 | "tap": "tap", 8 | "test": "tap test/*.js --100", 9 | "coverage": "tap test/*.js --coverage-report=html", 10 | "updatesnapshots": "TAP_SNAPSHOT=1 npm run test" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/lukeb-uk/node-promise-mysql.git" 15 | }, 16 | "keywords": [ 17 | "promise", 18 | "performance", 19 | "promises", 20 | "promises-a", 21 | "promises-aplus", 22 | "async", 23 | "await", 24 | "deferred", 25 | "deferreds", 26 | "future", 27 | "flow control", 28 | "dsl", 29 | "fluent interface", 30 | "database", 31 | "mysql", 32 | "mysql-promise", 33 | "bluebird", 34 | "q" 35 | ], 36 | "author": "Luke Bonaccorsi", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/lukeb-uk/node-promise-mysql/issues" 40 | }, 41 | "dependencies": { 42 | "@types/mysql": "^2.15.2", 43 | "@types/bluebird": "^3.5.26", 44 | "bluebird": "^3.5.1", 45 | "mysql": "^2.18.1" 46 | }, 47 | "devDependencies": { 48 | "proxyquire": "^2.0.1", 49 | "sinon": "^8.1.0", 50 | "tap": "^15.0.9" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tap-snapshots/test/helper.js.test.cjs: -------------------------------------------------------------------------------- 1 | /* IMPORTANT 2 | * This snapshot file is auto-generated, but designed for humans. 3 | * It should be checked into source control and tracked carefully. 4 | * Re-generate by setting TAP_SNAPSHOT=1 and running tests. 5 | * Make sure to inspect the output below. Do not ignore changes! 6 | */ 7 | 'use strict' 8 | exports[`test/helper.js TAP promiseCallback resolves the promise to an array if returnArgumentsArray is true > promise resolves to an array 1`] = ` 9 | Array [ 10 | 1, 11 | 2, 12 | 3, 13 | "returnValue", 14 | ] 15 | ` 16 | -------------------------------------------------------------------------------- /test/connection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tap = require(`tap`); 4 | const sinon = require(`sinon`); 5 | const proxyquire = require(`proxyquire`); 6 | const eventEmitter = require(`events`); 7 | const bluebird = require(`bluebird`); 8 | 9 | sinon.addBehavior(`callsLastArgWith`, (fake, errVal, retVal) => { 10 | fake.callsArgWith(fake.stub.args.length - 1, errVal, retVal); 11 | }) 12 | 13 | let connectionProxy; 14 | 15 | const mysqlProxy = {}; 16 | 17 | const Connection = proxyquire(`../lib/connection.js`, { 18 | mysql: mysqlProxy 19 | }) 20 | 21 | function getConnection(stubs = {}, options, passConnection) { 22 | connectionProxy = new eventEmitter(); 23 | mysqlProxy.createConnection = sinon.stub().returns(connectionProxy) 24 | 25 | connectionProxy.query = stubs.query || sinon.stub().callsLastArgWith(null, `queryPromise`).returns(`queryReturn`); 26 | connectionProxy.beginTransaction = stubs.beginTransaction || sinon.stub().callsLastArgWith(null, `beginTransactionPromise`).returns(`beginTransactionReturn`); 27 | connectionProxy.commit = stubs.commit || sinon.stub().callsLastArgWith(null, `commitPromise`).returns(`commitReturn`); 28 | connectionProxy.rollback = stubs.rollback || sinon.stub().callsLastArgWith(null, `rollbackPromise`).returns(`rollbackReturn`); 29 | connectionProxy.changeUser = stubs.changeUser || sinon.stub().callsLastArgWith(null, `changeUserPromise`).returns(`changeUserReturn`); 30 | connectionProxy.ping = stubs.ping || sinon.stub().callsLastArgWith(null, `pingPromise`).returns(`pingReturn`); 31 | connectionProxy.statistics = stubs.statistics || sinon.stub().callsLastArgWith(null, `statisticsPromise`).returns(`statisticsReturn`); 32 | connectionProxy.end = stubs.end || sinon.stub().callsLastArgWith(null, `endPromise`).returns(`endReturn`); 33 | connectionProxy.destroy = stubs.destroy || sinon.stub(); 34 | connectionProxy.pause = stubs.pause || sinon.stub(); 35 | connectionProxy.resume = stubs.resume || sinon.stub(); 36 | connectionProxy.escape = stubs.escape || sinon.stub().returnsArg(0); 37 | connectionProxy.escapeId = stubs.escapeId || sinon.stub().returnsArg(0); 38 | connectionProxy.format = stubs.format || sinon.stub().returnsArg(0); 39 | connectionProxy.connect = stubs.connect || sinon.stub().callsArg(0); 40 | 41 | return new Connection(options, passConnection ? connectionProxy : undefined); 42 | } 43 | 44 | tap.test(`It should create a new connection`, (t) => { 45 | const connection = getConnection(); 46 | 47 | connection.then((connection) => { 48 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should only be called once`); 49 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should only be called once`); 50 | 51 | const promiseSpec = [ 52 | {method: `query`, args: [`test`, [`test`]]}, 53 | {method: `beginTransaction`, args: []}, 54 | {method: `commit`, args: []}, 55 | {method: `rollback`, args: []}, 56 | {method: `changeUser`, args: [{user: `test`}]}, 57 | {method: `ping`, args: []}, 58 | {method: `statistics`, args: [{option: `test`}]}, 59 | {method: `end`, args: []} 60 | ]; 61 | 62 | promiseSpec.forEach((spec) => { 63 | t.test(`connection.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 64 | connection[spec.method](...spec.args).then(() => { 65 | t.ok(connectionProxy[spec.method].calledOnce, `underlying ${spec.method} method called`) 66 | t.equal( 67 | connectionProxy[spec.method].lastCall.args.length, 68 | spec.args.length + 1, 69 | `underlying ${spec.method} called with correct number of arguments` 70 | ); 71 | t.ok(connectionProxy[spec.method].calledWith(...spec.args)); 72 | t.end(); 73 | }); 74 | }); 75 | }); 76 | 77 | const callSpec = [ 78 | {method: `destroy`, underlyingMethod: `destroy`, args: []}, 79 | {method: `pause`, underlyingMethod: `pause`, args: []}, 80 | {method: `resume`, underlyingMethod: `resume`, args: []}, 81 | {method: `escape`, underlyingMethod: `escape`, args: [`test`]}, 82 | {method: `escapeId`, underlyingMethod: `escapeId`, args: [`testId`]}, 83 | ]; 84 | 85 | callSpec.forEach((spec) => { 86 | t.test(`connection.${spec.method} should call the underlying ${spec.underlyingMethod} method with the correct arguments`, (t) => { 87 | connection[spec.method](...spec.args); 88 | t.ok(connectionProxy[spec.underlyingMethod].calledOnce, `underlying ${spec.underlyingMethod} method called`) 89 | t.equal( 90 | connectionProxy[spec.underlyingMethod].lastCall.args.length, 91 | spec.args.length, 92 | `underlying ${spec.underlyingMethod} called with correct number of arguments` 93 | ); 94 | t.ok(connectionProxy[spec.underlyingMethod].calledWith(...spec.args)); 95 | t.end(); 96 | }); 97 | }); 98 | 99 | t.test(`connection.format should call the underlying format method with the correct arguments`, (t) => { 100 | const value = connection.format(`test`, `test2`); 101 | t.ok(connectionProxy.format.calledOnce, `underlying format method called`) 102 | t.ok(connectionProxy.format.calledWithExactly(`test`, `test2`), `underlying format called with correct arguments`); 103 | t.equal(value, `test`, `returns value from underlying function`); 104 | t.end(); 105 | }); 106 | 107 | t.test(`connection.on should call the underlying on method with the correct arguments`, (t) => { 108 | const handler = sinon.spy(); 109 | connection.on(`test`, handler); 110 | 111 | connectionProxy.emit(`test`); 112 | t.ok(handler.calledOnce, `handler is called`); 113 | t.end(); 114 | }); 115 | 116 | t.test(`connection.queryStream should call the underlying query method with the correct arguments`, (t) => { 117 | return getConnection({ 118 | query: sinon.stub().returns(`queryReturn`) 119 | }).then((connection) => { 120 | const value = connection.queryStream(`test`, [`test`]); 121 | t.ok(connectionProxy.query.calledOnce, `underlying format method called`) 122 | t.ok(connectionProxy.query.calledWithExactly(`test`, [`test`]), `underlying query called with correct arguments`); 123 | t.equal(value, `queryReturn`, `returns value from underlying function`); 124 | t.end(); 125 | }); 126 | }); 127 | 128 | t.end(); 129 | }) 130 | }); 131 | 132 | tap.test(`It should work with a connection string`, (t) => { 133 | const connection = getConnection({}, 'myConnectionString'); 134 | 135 | connection.then((connection) => { 136 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should only be called once`); 137 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should only be called once`); 138 | 139 | const promiseSpec = [ 140 | {method: `query`, args: [`test`, [`test`]]}, 141 | {method: `beginTransaction`, args: []}, 142 | {method: `commit`, args: []}, 143 | {method: `rollback`, args: []}, 144 | {method: `changeUser`, args: [{user: `test`}]}, 145 | {method: `ping`, args: []}, 146 | {method: `statistics`, args: [{option: `test`}]}, 147 | {method: `end`, args: []} 148 | ]; 149 | 150 | promiseSpec.forEach((spec) => { 151 | t.test(`connection.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 152 | connection[spec.method](...spec.args).then(() => { 153 | t.ok(connectionProxy[spec.method].calledOnce, `underlying ${spec.method} method called`) 154 | t.equal( 155 | connectionProxy[spec.method].lastCall.args.length, 156 | spec.args.length + 1, 157 | `underlying ${spec.method} called with correct number of arguments` 158 | ); 159 | t.ok(connectionProxy[spec.method].calledWith(...spec.args)); 160 | t.end(); 161 | }); 162 | }); 163 | }); 164 | 165 | const callSpec = [ 166 | {method: `destroy`, underlyingMethod: `destroy`, args: []}, 167 | {method: `pause`, underlyingMethod: `pause`, args: []}, 168 | {method: `resume`, underlyingMethod: `resume`, args: []}, 169 | {method: `escape`, underlyingMethod: `escape`, args: [`test`]}, 170 | {method: `escapeId`, underlyingMethod: `escapeId`, args: [`testId`]}, 171 | ]; 172 | 173 | callSpec.forEach((spec) => { 174 | t.test(`connection.${spec.method} should call the underlying ${spec.underlyingMethod} method with the correct arguments`, (t) => { 175 | connection[spec.method](...spec.args); 176 | t.ok(connectionProxy[spec.underlyingMethod].calledOnce, `underlying ${spec.underlyingMethod} method called`) 177 | t.equal( 178 | connectionProxy[spec.underlyingMethod].lastCall.args.length, 179 | spec.args.length, 180 | `underlying ${spec.underlyingMethod} called with correct number of arguments` 181 | ); 182 | t.ok(connectionProxy[spec.underlyingMethod].calledWith(...spec.args)); 183 | t.end(); 184 | }); 185 | }); 186 | 187 | t.test(`connection.format should call the underlying format method with the correct arguments`, (t) => { 188 | const value = connection.format(`test`, `test2`); 189 | t.ok(connectionProxy.format.calledOnce, `underlying format method called`) 190 | t.ok(connectionProxy.format.calledWithExactly(`test`, `test2`), `underlying format called with correct arguments`); 191 | t.equal(value, `test`, `returns value from underlying function`); 192 | t.end(); 193 | }); 194 | 195 | t.test(`connection.on should call the underlying on method with the correct arguments`, (t) => { 196 | const handler = sinon.spy(); 197 | connection.on(`test`, handler); 198 | 199 | connectionProxy.emit(`test`); 200 | t.ok(handler.calledOnce, `handler is called`); 201 | t.end(); 202 | }); 203 | 204 | t.test(`connection.queryStream should call the underlying query method with the correct arguments`, (t) => { 205 | return getConnection({ 206 | query: sinon.stub().returns(`queryReturn`) 207 | }).then((connection) => { 208 | const value = connection.queryStream(`test`, [`test`]); 209 | t.ok(connectionProxy.query.calledOnce, `underlying format method called`) 210 | t.ok(connectionProxy.query.calledWithExactly(`test`, [`test`]), `underlying query called with correct arguments`); 211 | t.equal(value, `queryReturn`, `returns value from underlying function`); 212 | t.end(); 213 | }); 214 | }); 215 | 216 | t.end(); 217 | }); 218 | }); 219 | 220 | tap.test(`It should work with a passed connection`, (t) => { 221 | const connection = getConnection({}, {}, true); 222 | 223 | connection.then((connection) => { 224 | t.ok(mysqlProxy.createConnection.notCalled, `createConnection should not be called`); 225 | t.ok(connectionProxy.connect.notCalled, `connection.connect should not be called`); 226 | 227 | const promiseSpec = [ 228 | {method: `query`, args: [`test`, [`test`]]}, 229 | {method: `beginTransaction`, args: []}, 230 | {method: `commit`, args: []}, 231 | {method: `rollback`, args: []}, 232 | {method: `changeUser`, args: [{user: `test`}]}, 233 | {method: `ping`, args: []}, 234 | {method: `statistics`, args: [{option: `test`}]}, 235 | {method: `end`, args: []} 236 | ]; 237 | 238 | promiseSpec.forEach((spec) => { 239 | t.test(`connection.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 240 | connection[spec.method](...spec.args).then(() => { 241 | t.ok(connectionProxy[spec.method].calledOnce, `underlying ${spec.method} method called`) 242 | t.equal( 243 | connectionProxy[spec.method].lastCall.args.length, 244 | spec.args.length + 1, 245 | `underlying ${spec.method} called with correct number of arguments` 246 | ); 247 | t.ok(connectionProxy[spec.method].calledWith(...spec.args)); 248 | t.end(); 249 | }) 250 | }); 251 | }); 252 | 253 | const callSpec = [ 254 | {method: `destroy`, underlyingMethod: `destroy`, args: []}, 255 | {method: `pause`, underlyingMethod: `pause`, args: []}, 256 | {method: `resume`, underlyingMethod: `resume`, args: []}, 257 | {method: `escape`, underlyingMethod: `escape`, args: [`test`]}, 258 | {method: `escapeId`, underlyingMethod: `escapeId`, args: [`testId`]} 259 | ]; 260 | 261 | callSpec.forEach((spec) => { 262 | t.test(`connection.${spec.method} should call the underlying ${spec.underlyingMethod} method with the correct arguments`, (t) => { 263 | connection[spec.method](...spec.args); 264 | t.ok(connectionProxy[spec.underlyingMethod].calledOnce, `underlying ${spec.underlyingMethod} method called`) 265 | t.equal( 266 | connectionProxy[spec.underlyingMethod].lastCall.args.length, 267 | spec.args.length, 268 | `underlying ${spec.underlyingMethod} called with correct number of arguments` 269 | ); 270 | t.ok(connectionProxy[spec.underlyingMethod].calledWith(...spec.args)); 271 | t.end(); 272 | }); 273 | }); 274 | 275 | t.test(`connection.format should call the underlying format method with the correct arguments`, (t) => { 276 | const value = connection.format(`test`, `test2`); 277 | t.ok(connectionProxy.format.calledOnce, `underlying format method called`) 278 | t.ok(connectionProxy.format.calledWithExactly(`test`, `test2`), `underlying format called with correct arguments`); 279 | t.equal(value, `test`, `returns value from underlying function`); 280 | t.end(); 281 | }); 282 | 283 | t.test(`connection.queryStream should call the underlying query method with the correct arguments`, (t) => { 284 | return getConnection({ 285 | query: sinon.stub().returns(`queryReturn`) 286 | }, {}, true).then((connection) => { 287 | const value = connection.queryStream(`test`, [`test`]); 288 | t.ok(connectionProxy.query.calledOnce, `underlying format method called`) 289 | t.ok(connectionProxy.query.calledWithExactly(`test`, [`test`]), `underlying query called with correct arguments`); 290 | t.equal(value, `queryReturn`, `returns value from underlying function`); 291 | t.end(); 292 | }); 293 | }); 294 | 295 | t.end(); 296 | }) 297 | }); 298 | 299 | tap.test(`It should return the arguments array`, (t) => { 300 | const connection = getConnection({}, {returnArgumentsArray: true}); 301 | 302 | connection.then((connection) => { 303 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should only be called once`); 304 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should only be called once`); 305 | 306 | const promiseSpec = [ 307 | {method: `query`, args: [`test`, [`test`]]}, 308 | {method: `beginTransaction`, args: []}, 309 | {method: `commit`, args: []}, 310 | {method: `rollback`, args: []}, 311 | {method: `changeUser`, args: [{user: `test`}]}, 312 | {method: `ping`, args: []}, 313 | {method: `statistics`, args: [{option: `test`}]}, 314 | {method: `end`, args: []} 315 | ]; 316 | 317 | promiseSpec.forEach((spec) => { 318 | t.test(`connection.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 319 | connection[spec.method](...spec.args).then((retVal) => { 320 | t.ok(connectionProxy[spec.method].calledOnce, `underlying ${spec.method} method called`) 321 | t.equal( 322 | connectionProxy[spec.method].lastCall.args.length, 323 | spec.args.length + 1, 324 | `underlying ${spec.method} called with correct number of arguments` 325 | ); 326 | t.same( 327 | retVal, 328 | [`${spec.method}Promise`, `${spec.method}Return`], 329 | `returns arguments array` 330 | ); 331 | t.ok(connectionProxy[spec.method].calledWith(...spec.args)); 332 | t.end(); 333 | }); 334 | }); 335 | }); 336 | 337 | t.end(); 338 | }) 339 | }); 340 | 341 | tap.test(`it should reconnect if specific error events are fired and reconnect is undefined`, (t) => { 342 | t.test(`PROTOCOL_CONNECTION_LOST`, (t) => { 343 | const connection = getConnection(); 344 | 345 | connection.then((connection) => { 346 | connectionProxy.emit(`error`, {code: `PROTOCOL_CONNECTION_LOST`}); 347 | t.ok(mysqlProxy.createConnection.calledTwice, `createConnection should be called`); 348 | t.ok(connectionProxy.connect.calledTwice, `connection.connect should be called`); 349 | t.end(); 350 | }); 351 | }); 352 | 353 | t.test(`ECONNRESET`, (t) => { 354 | const connection = getConnection(); 355 | 356 | connection.then((connection) => { 357 | connectionProxy.emit(`error`, {code: `ECONNRESET`}); 358 | t.ok(mysqlProxy.createConnection.calledTwice, `createConnection should be called`); 359 | t.ok(connectionProxy.connect.calledTwice, `connection.connect should be called`); 360 | t.end(); 361 | }); 362 | }); 363 | 364 | t.test(`PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`, (t) => { 365 | const connection = getConnection(); 366 | 367 | connection.then((connection) => { 368 | connectionProxy.emit(`error`, {code: `PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`}); 369 | t.ok(mysqlProxy.createConnection.calledTwice, `createConnection should be called`); 370 | t.ok(connectionProxy.connect.calledTwice, `connection.connect should be called`); 371 | t.end(); 372 | }); 373 | }); 374 | 375 | t.end(); 376 | }); 377 | 378 | tap.test(`it should reconnect if specific error events are fired and reconnect is true`, (t) => { 379 | t.test(`PROTOCOL_CONNECTION_LOST`, (t) => { 380 | const connection = getConnection({}, { 381 | reconnect: true 382 | }); 383 | 384 | connection.then((connection) => { 385 | connectionProxy.emit(`error`, {code: `PROTOCOL_CONNECTION_LOST`}); 386 | t.ok(mysqlProxy.createConnection.calledTwice, `createConnection should be called`); 387 | t.ok(connectionProxy.connect.calledTwice, `connection.connect should be called`); 388 | t.end(); 389 | }); 390 | }); 391 | 392 | t.test(`ECONNRESET`, (t) => { 393 | const connection = getConnection({}, { 394 | reconnect: true 395 | }); 396 | 397 | connection.then((connection) => { 398 | connectionProxy.emit(`error`, {code: `ECONNRESET`}); 399 | t.ok(mysqlProxy.createConnection.calledTwice, `createConnection should be called`); 400 | t.ok(connectionProxy.connect.calledTwice, `connection.connect should be called`); 401 | t.end(); 402 | }); 403 | }); 404 | 405 | t.test(`PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`, (t) => { 406 | const connection = getConnection({}, { 407 | reconnect: true 408 | }); 409 | 410 | connection.then((connection) => { 411 | connectionProxy.emit(`error`, {code: `PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`}); 412 | t.ok(mysqlProxy.createConnection.calledTwice, `createConnection should be called`); 413 | t.ok(connectionProxy.connect.calledTwice, `connection.connect should be called`); 414 | t.end(); 415 | }); 416 | }); 417 | 418 | t.end(); 419 | }); 420 | 421 | tap.test(`it should not reconnect if specific error events are fired and reconnect is false`, (t) => { 422 | t.test(`PROTOCOL_CONNECTION_LOST`, (t) => { 423 | const connection = getConnection({}, { 424 | reconnect: false 425 | }); 426 | 427 | connection.then((conn) => { 428 | const err = { code: `PROTOCOL_CONNECTION_LOST` }; 429 | const handler = sinon.spy(); 430 | 431 | conn.on(`error`, handler); 432 | connectionProxy.emit(`error`, err); 433 | t.ok( 434 | handler.calledWith(err), 435 | `error handler was called with error` 436 | ); 437 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called once`); 438 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called once`); 439 | t.end(); 440 | }); 441 | }); 442 | 443 | t.test(`ECONNRESET`, (t) => { 444 | const connection = getConnection({}, { 445 | reconnect: false 446 | }); 447 | 448 | connection.then((conn) => { 449 | const err = { code: `ECONNRESET` }; 450 | const handler = sinon.spy(); 451 | 452 | conn.on(`error`, handler); 453 | connectionProxy.emit(`error`, err); 454 | t.ok( 455 | handler.calledWith(err), 456 | `error handler was called with error` 457 | ); 458 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called once`); 459 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called once`); 460 | t.end(); 461 | }); 462 | }); 463 | 464 | t.test(`PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`, (t) => { 465 | const connection = getConnection({}, { 466 | reconnect: false 467 | }); 468 | 469 | connection.then((conn) => { 470 | const err = { code: `PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR` }; 471 | const handler = sinon.spy(); 472 | 473 | conn.on(`error`, handler); 474 | connectionProxy.emit(`error`, err); 475 | t.ok( 476 | handler.calledWith(err), 477 | `error handler was called with error` 478 | ); 479 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called once`); 480 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called once`); 481 | t.end(); 482 | }); 483 | }); 484 | 485 | t.end(); 486 | }); 487 | 488 | tap.test(`with a passed connection it should reconnect if specific error events are fired and reconnect is undefined`, (t) => { 489 | t.test(`PROTOCOL_CONNECTION_LOST`, (t) => { 490 | const connection = getConnection({}, {}, true); 491 | 492 | connection.then((connection) => { 493 | connectionProxy.emit(`error`, {code: `PROTOCOL_CONNECTION_LOST`}); 494 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called`); 495 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called`); 496 | t.end(); 497 | }); 498 | }); 499 | 500 | t.test(`ECONNRESET`, (t) => { 501 | const connection = getConnection({}, {}, true); 502 | 503 | connection.then((connection) => { 504 | connectionProxy.emit(`error`, {code: `ECONNRESET`}); 505 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called`); 506 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called`); 507 | t.end(); 508 | }); 509 | }); 510 | 511 | t.test(`PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`, (t) => { 512 | const connection = getConnection({}, {}, true); 513 | 514 | connection.then((connection) => { 515 | connectionProxy.emit(`error`, {code: `PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`}); 516 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called`); 517 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called`); 518 | t.end(); 519 | }); 520 | }); 521 | 522 | t.end(); 523 | }); 524 | 525 | tap.test(`with a passed connection it should reconnect if specific error events are fired and reconnect is true`, (t) => { 526 | t.test(`PROTOCOL_CONNECTION_LOST`, (t) => { 527 | const connection = getConnection({}, { 528 | reconnect: true 529 | }, true); 530 | 531 | connection.then((connection) => { 532 | connectionProxy.emit(`error`, {code: `PROTOCOL_CONNECTION_LOST`}); 533 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called`); 534 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called`); 535 | t.end(); 536 | }); 537 | }); 538 | 539 | t.test(`ECONNRESET`, (t) => { 540 | const connection = getConnection({}, { 541 | reconnect: true 542 | }, true); 543 | 544 | connection.then((connection) => { 545 | connectionProxy.emit(`error`, {code: `ECONNRESET`}); 546 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called`); 547 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called`); 548 | t.end(); 549 | }); 550 | }); 551 | 552 | t.test(`PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`, (t) => { 553 | const connection = getConnection({}, { 554 | reconnect: true 555 | }, true); 556 | 557 | connection.then((connection) => { 558 | connectionProxy.emit(`error`, {code: `PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`}); 559 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should be called`); 560 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should be called`); 561 | t.end(); 562 | }); 563 | }); 564 | 565 | t.end(); 566 | }); 567 | 568 | tap.test(`with a passed connection it should not reconnect if specific error events are fired and reconnect is false`, (t) => { 569 | t.test(`PROTOCOL_CONNECTION_LOST`, (t) => { 570 | const connection = getConnection({}, { 571 | reconnect: false 572 | }, true); 573 | 574 | connection.then((conn) => { 575 | const err = { code: `PROTOCOL_CONNECTION_LOST` }; 576 | const handler = sinon.spy(); 577 | 578 | conn.on(`error`, handler); 579 | connectionProxy.emit(`error`, err); 580 | t.ok( 581 | handler.calledWith(err), 582 | `error handler was called with error` 583 | ); 584 | t.ok(mysqlProxy.createConnection.notCalled, `createConnection should be called once`); 585 | t.ok(connectionProxy.connect.notCalled, `connection.connect should be called once`); 586 | t.end(); 587 | }); 588 | }); 589 | 590 | t.test(`ECONNRESET`, (t) => { 591 | const connection = getConnection({}, { 592 | reconnect: false 593 | }, true); 594 | 595 | connection.then((conn) => { 596 | const err = { code: `ECONNRESET` }; 597 | const handler = sinon.spy(); 598 | 599 | conn.on(`error`, handler); 600 | connectionProxy.emit(`error`, err); 601 | t.ok( 602 | handler.calledWith(err), 603 | `error handler was called with error` 604 | ); 605 | t.ok(mysqlProxy.createConnection.notCalled, `createConnection should be called once`); 606 | t.ok(connectionProxy.connect.notCalled, `connection.connect should be called once`); 607 | t.end(); 608 | }); 609 | }); 610 | 611 | t.test(`PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR`, (t) => { 612 | const connection = getConnection({}, { 613 | reconnect: false 614 | }, true); 615 | 616 | connection.then((conn) => { 617 | const err = { code: `PROTOCOL_ENQUEUE_AFTER_FATAL_ERROR` }; 618 | const handler = sinon.spy(); 619 | 620 | conn.on(`error`, handler); 621 | connectionProxy.emit(`error`, err); 622 | t.ok( 623 | handler.calledWith(err), 624 | `error handler was called with error` 625 | ); 626 | t.ok(mysqlProxy.createConnection.notCalled, `createConnection should be called once`); 627 | t.ok(connectionProxy.connect.notCalled, `connection.connect should be called once`); 628 | t.end(); 629 | }); 630 | }); 631 | 632 | t.end(); 633 | }); 634 | 635 | tap.test(`it should ignore any other errors`, (t) => { 636 | const connection = getConnection(); 637 | 638 | connection.then((conn) => { 639 | const err = { code: `FAAAAAKE` }; 640 | const handler = sinon.spy(); 641 | 642 | conn.on(`error`, handler); 643 | connectionProxy.emit(`error`, err); 644 | t.ok(handler.calledWith(err), `error handler was called with error`); 645 | t.ok(mysqlProxy.createConnection.calledOnce, `createConnection should only be called for the first connection`); 646 | t.ok(connectionProxy.connect.calledOnce, `connection.connect should only be called for the first connection`); 647 | t.end(); 648 | }); 649 | }); 650 | 651 | tap.test(`it should reject if connection.connect errors`, (t) => { 652 | const expectedError = new Error(`error`); 653 | const connection = getConnection({ 654 | connect: sinon.stub().callsArgWith(0, expectedError) 655 | }); 656 | 657 | t.rejects(connection, expectedError, 'rejects with expected error'); 658 | t.end(); 659 | }); 660 | 661 | tap.test(`it should allow you to wrap mysql`, (t) => { 662 | t.test(`returning a value`, (t) => { 663 | const wrappedConnectionProxy = new eventEmitter(); 664 | wrappedConnectionProxy.connect = sinon.stub().callsArg(0); 665 | const wrappedMysqlProxy = { 666 | createConnection: sinon.stub().returns(wrappedConnectionProxy) 667 | }; 668 | 669 | const connection = getConnection({}, { 670 | mysqlWrapper: (mysql) => { 671 | t.equal(mysql, mysqlProxy, `proxy should be passed to the wrapper`); 672 | return wrappedMysqlProxy; 673 | } 674 | }); 675 | 676 | connection.then((connection) => { 677 | t.ok(wrappedMysqlProxy.createConnection.calledOnce, `createConnection should only be called for the first connection`); 678 | t.ok(wrappedConnectionProxy.connect.calledOnce, `connection.connect should only be called for the first connection`); 679 | t.end(); 680 | }); 681 | }); 682 | 683 | t.test(`returning a promise`, (t) => { 684 | const wrappedConnectionProxy = new eventEmitter(); 685 | wrappedConnectionProxy.connect = sinon.stub().callsArg(0); 686 | const wrappedMysqlProxy = { 687 | createConnection: sinon.stub().returns(wrappedConnectionProxy) 688 | }; 689 | 690 | const connection = getConnection({}, { 691 | mysqlWrapper: (mysql) => { 692 | t.equal(mysql, mysqlProxy, `proxy should be passed to the wrapper`); 693 | return bluebird.resolve(wrappedMysqlProxy); 694 | } 695 | }); 696 | 697 | connection.then((connection) => { 698 | t.ok(wrappedMysqlProxy.createConnection.calledOnce, `createConnection should only be called for the first connection`); 699 | t.ok(wrappedConnectionProxy.connect.calledOnce, `connection.connect should only be called for the first connection`); 700 | t.end(); 701 | }); 702 | }); 703 | 704 | t.test(`returning a rejected promise`, (t) => { 705 | const wrappedConnectionProxy = new eventEmitter(); 706 | wrappedConnectionProxy.connect = sinon.stub().callsArg(0); 707 | const wrappedMysqlProxy = { 708 | createConnection: sinon.stub().returns(wrappedConnectionProxy) 709 | }; 710 | 711 | const connection = getConnection({}, { 712 | mysqlWrapper: (mysql) => { 713 | t.equal(mysql, mysqlProxy, `proxy should be passed to the wrapper`); 714 | return bluebird.reject(`faaaaaaail`); 715 | } 716 | }); 717 | 718 | connection.catch((err) => { 719 | t.ok(err, `faaaaaaail`, `The connection should be rejected with the error`); 720 | t.end(); 721 | }); 722 | }); 723 | 724 | t.test(`calling the callback`, (t) => { 725 | const wrappedConnectionProxy = new eventEmitter(); 726 | wrappedConnectionProxy.connect = sinon.stub().callsArg(0); 727 | const wrappedMysqlProxy = { 728 | createConnection: sinon.stub().returns(wrappedConnectionProxy) 729 | }; 730 | 731 | const connection = getConnection({}, { 732 | mysqlWrapper: (mysql, callback) => { 733 | t.equal(mysql, mysqlProxy, `proxy should be passed to the wrapper`); 734 | callback(null, wrappedMysqlProxy); 735 | } 736 | }); 737 | 738 | connection.then((connection) => { 739 | t.ok(wrappedMysqlProxy.createConnection.calledOnce, `createConnection should only be called for the first connection`); 740 | t.ok(wrappedConnectionProxy.connect.calledOnce, `connection.connect should only be called for the first connection`); 741 | t.end(); 742 | }); 743 | }); 744 | 745 | t.test(`calling the callback with an error`, (t) => { 746 | const wrappedConnectionProxy = new eventEmitter(); 747 | wrappedConnectionProxy.connect = sinon.stub().callsArg(0); 748 | const wrappedMysqlProxy = { 749 | createConnection: sinon.stub().returns(wrappedConnectionProxy) 750 | }; 751 | 752 | const connection = getConnection({}, { 753 | mysqlWrapper: (mysql, callback) => { 754 | t.equal(mysql, mysqlProxy, `proxy should be passed to the wrapper`); 755 | callback(`faaaaaaail`); 756 | } 757 | }); 758 | 759 | connection.catch((err) => { 760 | t.ok(err, `faaaaaaail`, `The connection should be rejected with the error`); 761 | t.end(); 762 | }); 763 | }); 764 | 765 | t.end(); 766 | }); 767 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tap = require(`tap`); 4 | const sinon = require(`sinon`); 5 | const helper = require(`../lib/helper.js`); 6 | 7 | tap.test(`promiseCallback returns a promise and calls the defined function`, (t) => { 8 | const callback = sinon.stub() 9 | const promiseCallback = helper.promiseCallback.apply({ 10 | test: sinon.stub().callsArgWith(0, undefined, `passed`) 11 | }, 12 | [ `test`, [] ]); 13 | 14 | t.ok(promiseCallback.then, `promise has been returned`); 15 | 16 | t.resolveMatch(promiseCallback, `passed`, `promise resolves to expected value`) 17 | t.end(); 18 | }); 19 | 20 | tap.test(`promiseCallback resolves the promise to an array if returnArgumentsArray is true`, (t) => { 21 | const callback = sinon.stub() 22 | const promiseCallback = helper.promiseCallback.apply({ 23 | test: sinon.stub().callsArgWith(0, undefined, 1, 2, 3).returns('returnValue') 24 | }, [ `test`, [], true ]); 25 | 26 | t.ok(promiseCallback.then, `promise has been returned`); 27 | 28 | promiseCallback.then((value) => { 29 | t.matchSnapshot(value, `promise resolves to an array`); 30 | t.end(); 31 | }) 32 | 33 | 34 | }); 35 | 36 | tap.test(`promiseCallback rejects the promise when there's an error`, (t) => { 37 | const callback = sinon.stub() 38 | const promiseCallback = helper.promiseCallback.apply({ 39 | test: sinon.stub().callsArgWith(0, `fail`) 40 | }, [ 41 | `test`, [] 42 | ]); 43 | 44 | t.ok(promiseCallback.then, `promise has been returned`); 45 | 46 | t.rejects(promiseCallback, `fail`, `promise rejects with expected value`) 47 | t.end(); 48 | }) 49 | -------------------------------------------------------------------------------- /test/pool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tap = require(`tap`); 4 | const sinon = require(`sinon`); 5 | const proxyquire = require(`proxyquire`); 6 | const bluebird = require(`bluebird`); 7 | 8 | sinon.addBehavior(`callsLastArgWith`, (fake, errVal, retVal) => { 9 | fake.callsArgWith(fake.stub.args.length - 1, errVal, retVal); 10 | }); 11 | 12 | const poolMock = { 13 | getConnection: sinon.stub().callsLastArgWith(null, `getConnectionPromise`).returns(`getConnectionReturn`), 14 | query: sinon.stub().callsLastArgWith(null, `queryPromise`).returns(`queryReturn`), 15 | end: sinon.stub().callsLastArgWith(null, `endPromise`).returns(`endReturn`), 16 | escape: sinon.stub().returnsArg(0), 17 | escapeId: sinon.stub().returnsArg(0), 18 | on: sinon.stub().returnsArg(0) 19 | }; 20 | 21 | const createPool = sinon.stub().returns(poolMock); 22 | 23 | const mysqlMock = { 24 | createPool 25 | }; 26 | 27 | class mockPoolConnection { 28 | } 29 | 30 | const pool = proxyquire(`../lib/pool.js`, { 31 | mysql: mysqlMock, 32 | './poolConnection.js': mockPoolConnection 33 | }); 34 | 35 | tap.beforeEach(() => { 36 | createPool.resetHistory(); 37 | poolMock.getConnection.resetHistory(); 38 | poolMock.query.resetHistory(); 39 | poolMock.end.resetHistory(); 40 | poolMock.escape.resetHistory(); 41 | poolMock.escapeId.resetHistory(); 42 | }); 43 | 44 | tap.test(`createPool is called`, (t) => { 45 | t.test(`without arguments`, (t) => { 46 | return new pool().then(() => { 47 | t.ok(createPool.calledOnce, `createPool is called once`); 48 | t.ok(createPool.calledWith({}), `createPool is called with no arguments`); 49 | t.end(); 50 | }); 51 | }); 52 | 53 | t.test(`with just configuration options`, (t) => { 54 | const config = { 55 | test: "test" 56 | }; 57 | return new pool(config).then(() => { 58 | t.ok(createPool.calledOnce, `createPool is called once`); 59 | t.ok(createPool.calledWith(config), `createPool is called with arguments`); 60 | t.end(); 61 | }); 62 | }); 63 | 64 | t.test(`with a already instantiated underlying pool`, (t) => { 65 | const config = undefined; 66 | const underlyingPool = sinon.mock(); 67 | const p = new pool(config, underlyingPool); 68 | t.ok(p.pool === underlyingPool, `uses the underlying pool`); 69 | t.ok(createPool.notCalled, `createPool is not called`); 70 | t.end(); 71 | }); 72 | 73 | t.end(); 74 | }); 75 | 76 | tap.test(`it should allow you to wrap mysql`, (t) => { 77 | t.test(`returning a value`, (t) => { 78 | const wrappedMysqlProxy = { 79 | createPool: sinon.stub().returns(poolMock) 80 | }; 81 | 82 | return new pool({ 83 | mysqlWrapper: (mysql) => { 84 | t.equal(mysql, mysqlMock, `proxy should be passed to the wrapper`); 85 | return wrappedMysqlProxy; 86 | } 87 | }).then(() => { 88 | t.ok(wrappedMysqlProxy.createPool.calledOnce, `wrapped createPool should get called`); 89 | t.end(); 90 | }); 91 | }); 92 | 93 | t.test(`returning a promise`, (t) => { 94 | const wrappedMysqlProxy = { 95 | createPool: sinon.stub().returns(poolMock) 96 | }; 97 | 98 | return new pool({ 99 | mysqlWrapper: (mysql) => { 100 | t.equal(mysql, mysqlMock, `proxy should be passed to the wrapper`); 101 | return bluebird.resolve(wrappedMysqlProxy); 102 | } 103 | }).then(() => { 104 | t.ok(wrappedMysqlProxy.createPool.calledOnce, `wrapped createPool should get called`); 105 | t.end(); 106 | }); 107 | }); 108 | 109 | t.test(`returning a rejected promise`, (t) => { 110 | return new pool({ 111 | mysqlWrapper: (mysql) => { 112 | t.equal(mysql, mysqlMock, `proxy should be passed to the wrapper`); 113 | return bluebird.reject(`faaaaaaail`); 114 | } 115 | }).catch((err) => { 116 | t.ok(err, `faaaaaaail`, `The connection should be rejected with the error`); 117 | t.end(); 118 | }); 119 | }); 120 | 121 | t.test(`calling the callback`, (t) => { 122 | const wrappedMysqlProxy = { 123 | createPool: sinon.stub().returns(poolMock) 124 | }; 125 | 126 | return new pool({ 127 | mysqlWrapper: (mysql, callback) => { 128 | t.equal(mysql, mysqlMock, `proxy should be passed to the wrapper`); 129 | callback(null, wrappedMysqlProxy); 130 | } 131 | }).then(() => { 132 | t.ok(wrappedMysqlProxy.createPool.calledOnce, `wrapped createPool should get called`); 133 | t.end(); 134 | }); 135 | }); 136 | 137 | t.test(`calling the callback with an error`, (t) => { 138 | const wrappedMysqlProxy = { 139 | createPool: sinon.stub().returns(poolMock) 140 | }; 141 | 142 | return new pool({ 143 | mysqlWrapper: (mysql, callback) => { 144 | t.equal(mysql, mysqlMock, `proxy should be passed to the wrapper`); 145 | callback(`faaaaaaail`); 146 | } 147 | }).catch((err) => { 148 | t.ok(err, `faaaaaaail`, `The connection should be rejected with the error`); 149 | t.end(); 150 | }); 151 | }); 152 | 153 | t.end(); 154 | }); 155 | 156 | tap.test(`calls the underlying methods`, (t) => { 157 | return new pool().then((pool) => { 158 | const callSpec = [{ 159 | method: `escape`, 160 | args: [`test`] 161 | }, 162 | { 163 | method: `escapeId`, 164 | args: [`test`] 165 | }, 166 | { 167 | method: `on`, 168 | args: [`test`, () => {}] 169 | } 170 | ]; 171 | 172 | callSpec.forEach((spec) => { 173 | t.test(`pool.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 174 | pool[spec.method](...spec.args) 175 | t.ok(poolMock[spec.method].calledOnce, `underlying ${spec.method} method called`) 176 | t.equal( 177 | poolMock[spec.method].lastCall.args.length, 178 | spec.args.length, 179 | `underlying ${spec.method} called with correct number of arguments` 180 | ); 181 | t.ok(poolMock[spec.method].calledWith(...spec.args)); 182 | t.end(); 183 | }); 184 | }); 185 | 186 | const promiseSpec = [ 187 | { 188 | method: `query`, 189 | args: [`test`, [`test`]] 190 | }, 191 | { 192 | method: `end`, 193 | args: [] 194 | } 195 | ]; 196 | 197 | promiseSpec.forEach((spec) => { 198 | t.test(`pool.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 199 | return pool[spec.method](...spec.args).then((retVal) => { 200 | t.ok(poolMock[spec.method].calledOnce, `underlying ${spec.method} method called`) 201 | t.equal( 202 | poolMock[spec.method].lastCall.args.length, 203 | spec.args.length + 1, 204 | `underlying ${spec.method} called with correct number of arguments` 205 | ); 206 | t.same( 207 | retVal, 208 | `${spec.method}Promise`, 209 | `returns arguments` 210 | ); 211 | t.ok(poolMock[spec.method].calledWith(...spec.args)); 212 | t.end(); 213 | }); 214 | }); 215 | }); 216 | 217 | t.test(`pool.getConnection should call the underlying getConnection method with the correct arguments`, (t) => { 218 | return pool.getConnection().then((retVal) => { 219 | t.ok(poolMock.getConnection.calledOnce, `underlying getConnection method called`) 220 | t.equal( 221 | poolMock.getConnection.lastCall.args.length, 222 | 1, 223 | `underlying getConnection called with correct number of arguments` 224 | ); 225 | t.ok(retVal instanceof mockPoolConnection); 226 | t.end(); 227 | }); 228 | }); 229 | 230 | t.end(); 231 | }); 232 | }) 233 | 234 | tap.test(`returns an argument array`, (t) => { 235 | return new pool({returnArgumentsArray: true}).then((pool) => { 236 | 237 | const promiseSpec = [{ 238 | method: `query`, 239 | args: [`test`, [`test`]] 240 | }, 241 | { 242 | method: `end`, 243 | args: [] 244 | } 245 | ]; 246 | 247 | promiseSpec.forEach((spec) => { 248 | t.test(`pool.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 249 | return pool[spec.method](...spec.args).then((retVal) => { 250 | t.ok(poolMock[spec.method].calledOnce, `underlying ${spec.method} method called`) 251 | t.equal( 252 | poolMock[spec.method].lastCall.args.length, 253 | spec.args.length + 1, 254 | `underlying ${spec.method} called with correct number of arguments` 255 | ); 256 | t.same( 257 | retVal, 258 | [`${spec.method}Promise`, `${spec.method}Return`], 259 | `returns arguments array` 260 | ); 261 | t.ok(poolMock[spec.method].calledWith(...spec.args)); 262 | t.end(); 263 | }); 264 | }); 265 | }); 266 | 267 | t.end(); 268 | }); 269 | }) 270 | -------------------------------------------------------------------------------- /test/poolCluster.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tap = require(`tap`); 4 | const sinon = require(`sinon`); 5 | const proxyquire = require(`proxyquire`); 6 | const bluebird = require(`bluebird`); 7 | 8 | sinon.addBehavior(`callsLastArgWith`, (fake, errVal, retVal) => { 9 | fake.callsArgWith(fake.stub.args.length - 1, errVal, retVal); 10 | }); 11 | 12 | const poolClusterMock = { 13 | add: sinon.stub().returnsArg(0), 14 | remove: sinon.stub().returnsArg(0), 15 | getConnection: sinon.stub().callsLastArgWith(null, `getConnectionPromise`).returns(`getConnectionReturn`), 16 | of: sinon.stub().returns(`pool`), 17 | end: sinon.stub().callsLastArgWith(null, `endPromise`).returns(`endReturn`), 18 | on: sinon.stub().returnsArg(0), 19 | }; 20 | 21 | const createPoolCluster = sinon.stub().returns(poolClusterMock); 22 | 23 | const mysqlMock = { 24 | createPoolCluster 25 | }; 26 | 27 | class mockPool { 28 | } 29 | 30 | class mockPoolConnection { 31 | } 32 | 33 | const poolCluster = proxyquire(`../lib/poolCluster.js`, { 34 | mysql: mysqlMock, 35 | './pool.js': mockPool, 36 | './poolConnection.js': mockPoolConnection 37 | }); 38 | 39 | tap.beforeEach(() => { 40 | createPoolCluster.resetHistory(); 41 | poolClusterMock.add.resetHistory(); 42 | poolClusterMock.remove.resetHistory(); 43 | poolClusterMock.getConnection.resetHistory(); 44 | poolClusterMock.of.resetHistory(); 45 | poolClusterMock.end.resetHistory(); 46 | poolClusterMock.on.resetHistory(); 47 | }); 48 | 49 | tap.test(`createPool is called`, (t) => { 50 | t.test(`without arguments`, (t) => { 51 | return new poolCluster().then(() => { 52 | t.ok(createPoolCluster.calledOnce, `createPoolCluster is called once`); 53 | t.ok(createPoolCluster.calledWith({}), `createPoolCluster is called with no arguments`); 54 | t.end(); 55 | }); 56 | }); 57 | 58 | t.test(`with configuration options`, (t) => { 59 | const config = { 60 | test: "test" 61 | }; 62 | return new poolCluster(config).then(() => { 63 | t.ok(createPoolCluster.calledOnce, `createPoolCluster is called once`); 64 | t.ok(createPoolCluster.calledWith(config), `createPoolCluster is called with arguments`); 65 | t.end(); 66 | }); 67 | }); 68 | 69 | t.end(); 70 | }); 71 | 72 | tap.test(`calls the underlying methods`, (t) => { 73 | return new poolCluster().then((poolCluster) => { 74 | const callSpec = [ 75 | { 76 | method: `add`, 77 | args: [`test`, undefined], 78 | }, 79 | { 80 | method: `add`, 81 | args: [`identifier`, `test`], 82 | }, 83 | { 84 | method: `remove`, 85 | args: [`test`], 86 | }, 87 | { 88 | method: `on`, 89 | args: [`test`, () => { }], 90 | } 91 | ]; 92 | 93 | callSpec.forEach((spec) => { 94 | t.test(`poolCluster.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 95 | poolCluster[spec.method](...spec.args) 96 | t.ok(poolClusterMock[spec.method].calledOnce, `underlying ${spec.method} method called`) 97 | t.equal( 98 | poolClusterMock[spec.method].lastCall.args.length, 99 | spec.args.length, 100 | `underlying ${spec.method} called with correct number of arguments`, 101 | ); 102 | t.ok(poolClusterMock[spec.method].calledWith(...spec.args)); 103 | t.end(); 104 | }); 105 | }); 106 | 107 | t.test(`poolCluster.of should call the underlying of method with the correct arguments and return a new pool object`, (t) => { 108 | const pattern = 'test'; 109 | const retVal = poolCluster.of(pattern); 110 | t.ok(poolClusterMock.of.calledOnce, `underlying of method called`); 111 | t.ok(poolClusterMock.of.calledWith(pattern)); 112 | t.ok(retVal instanceof mockPool); 113 | t.end(); 114 | }); 115 | 116 | const promiseSpec = [ 117 | { 118 | method: `end`, 119 | args: [] 120 | } 121 | ]; 122 | 123 | promiseSpec.forEach((spec) => { 124 | t.test(`poolCluster.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 125 | return poolCluster[spec.method](...spec.args).then((retVal) => { 126 | t.ok(poolClusterMock[spec.method].calledOnce, `underlying ${spec.method} method called`) 127 | t.equal( 128 | poolClusterMock[spec.method].lastCall.args.length, 129 | spec.args.length + 1, 130 | `underlying ${spec.method} called with correct number of arguments` 131 | ); 132 | t.same( 133 | retVal, 134 | `${spec.method}Promise`, 135 | `returns arguments` 136 | ); 137 | t.ok(poolClusterMock[spec.method].calledWith(...spec.args)); 138 | t.end(); 139 | }); 140 | }); 141 | }); 142 | 143 | t.test(`poolCluster.getConnection should call the underlying getConnection method with the correct arguments`, (t) => { 144 | return poolCluster.getConnection().then((retVal) => { 145 | t.ok(poolClusterMock.getConnection.calledOnce, `underlying getConnection method called`) 146 | t.equal( 147 | poolClusterMock.getConnection.lastCall.args.length, 148 | 1, 149 | `underlying getConnection called with correct number of arguments` 150 | ); 151 | t.ok(retVal instanceof mockPoolConnection); 152 | t.end(); 153 | }); 154 | }); 155 | }); 156 | }); 157 | 158 | tap.test(`returns an argument array`, (t) => { 159 | return new poolCluster({returnArgumentsArray: true}).then((poolCluster) => { 160 | 161 | const promiseSpec = [ 162 | { 163 | method: `end`, 164 | args: [] 165 | } 166 | ]; 167 | 168 | promiseSpec.forEach((spec) => { 169 | t.test(`poolCluster.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 170 | return poolCluster[spec.method](...spec.args).then((retVal) => { 171 | t.ok(poolClusterMock[spec.method].calledOnce, `underlying ${spec.method} method called`) 172 | t.equal( 173 | poolClusterMock[spec.method].lastCall.args.length, 174 | spec.args.length + 1, 175 | `underlying ${spec.method} called with correct number of arguments` 176 | ); 177 | t.same( 178 | retVal, 179 | [`${spec.method}Promise`, `${spec.method}Return`], 180 | `returns arguments array` 181 | ); 182 | t.ok(poolClusterMock[spec.method].calledWith(...spec.args)); 183 | t.end(); 184 | }); 185 | }); 186 | }); 187 | 188 | t.end(); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /test/poolConnection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tap = require(`tap`); 4 | const sinon = require(`sinon`); 5 | const proxyquire = require(`proxyquire`); 6 | 7 | sinon.addBehavior(`callsLastArgWith`, (fake, errVal, retVal) => { 8 | fake.callsArgWith(fake.stub.args.length - 1, errVal, retVal); 9 | }); 10 | 11 | const constructorSpy = sinon.spy(); 12 | const connectionMock = { 13 | release: sinon.stub().returns(`releaseReturn`), 14 | destroy: sinon.stub().returns(`destroyReturn`) 15 | }; 16 | 17 | const poolConnection = proxyquire(`../lib/poolConnection.js`, { 18 | './connection.js': class Connection { 19 | constructor(_connection) { 20 | constructorSpy(_connection); 21 | this.connection = connectionMock; 22 | } 23 | } 24 | }); 25 | 26 | tap.beforeEach(() => { 27 | constructorSpy.resetHistory(); 28 | connectionMock.release.resetHistory(); 29 | connectionMock.destroy.resetHistory(); 30 | }); 31 | 32 | tap.test(`constructor is called`, (t) => { 33 | t.test(`without arguments`, (t) => { 34 | new poolConnection(); 35 | 36 | t.ok(constructorSpy.calledOnce, `constructor is called once`); 37 | t.ok(constructorSpy.calledWith(undefined), `constructor is called with no arguments`); 38 | t.end(); 39 | }); 40 | 41 | t.test(`with arguments`, (t) => { 42 | new poolConnection(`badger`); 43 | 44 | t.ok(constructorSpy.calledOnce, `constructor is called once`); 45 | t.ok(constructorSpy.calledWith(`badger`), `constructor is called with no arguments`); 46 | t.end(); 47 | }); 48 | 49 | t.end(); 50 | }); 51 | 52 | tap.test(`calls the underlying methods`, (t) => { 53 | const connection = new poolConnection(); 54 | 55 | const callSpec = [ 56 | {method: `release`, args: []}, 57 | {method: `destroy`, args: []} 58 | ]; 59 | 60 | callSpec.forEach((spec) => { 61 | t.test(`connection.${spec.method} should call the underlying ${spec.method} method with the correct arguments`, (t) => { 62 | connection[spec.method](...spec.args) 63 | t.ok(connectionMock[spec.method].calledOnce, `underlying ${spec.method} method called`) 64 | t.equal( 65 | connectionMock[spec.method].lastCall.args.length, 66 | spec.args.length, 67 | `underlying ${spec.method} called with correct number of arguments` 68 | ); 69 | t.ok(connectionMock[spec.method].calledWith(...spec.args)); 70 | t.end(); 71 | }); 72 | }); 73 | 74 | t.end(); 75 | }) 76 | 77 | 78 | --------------------------------------------------------------------------------