diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml new file mode 100644 index 0000000..2270eda --- /dev/null +++ b/.github/workflows/node.js.yml @@ -0,0 +1,30 @@ +# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x, 16.x, 18.x, 20.x, 22.x, 24.x] + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm run build --if-present + - run: npm test +# - run: npm run coverage \ No newline at end of file diff --git a/README.md b/README.md index 69f9bb9..4e5f865 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ [![NPM](https://nodei.co/npm/mysql2-cache.png?downloads=true&stars=true)](https://nodei.co/npm/mysql2-cache/) -![GitHub release (latest by date)](https://img.shields.io/github/v/release/2naive/node-mysql2-cache) +![GitHub release (latest by date)](https://img.shields.io/github/v/release/2naive/mysql2-cache) ![node-current](https://img.shields.io/node/v/mysql2-cache) -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/2naive/node-mysql2-cache/Node.js%20Package) -![Coveralls github](https://img.shields.io/coveralls/github/2naive/node-mysql2-cache) +![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/2naive/mysql2-cache/npm-publish.yml?branch=main) +![Coveralls github](https://img.shields.io/coveralls/github/2naive/mysql2-cache) ![Standard - JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg) > ✔ MySQL2 upgrade: cache queries, easy shortcuts, logging and debugging. @@ -39,17 +39,16 @@ const db = mysql.connect({ database: 'test', password: 'root' }) -db.q('SELECT * FROM test_table WHERE id=?', 1, true) // use cache with default ttl +db.q('SELECT * FROM test_table WHERE id=?', 1, true) // use cache with default ttl=300s db.q('SELECT * FROM test_table WHERE id=?', 1, true, 300) // ttl in seconds ``` - -### Debugging easy +## Debugging easy Pass `DEBUG=mysql2-cache*` environment variable to pretty debug. ```bash - mysql2-cache:1 SELECT * FROM test_table undefined +0ms + mysql2-cache:1 SELECT * FROM test_table WHERE age > ? [1] +0ms mysql2-cache:1 ┌─────────┬─────────┬─────┐ mysql2-cache:1 │ (index) │ name │ age │ mysql2-cache:1 ├─────────┼─────────┼─────┤ @@ -59,6 +58,26 @@ Pass `DEBUG=mysql2-cache*` environment variable to pretty debug. mysql2-cache:1 +32ms ``` +## API + +You may use all [MySQL2](https://github.com/sidorares/node-mysql2) methods plus: + +### async q(sql, params = [], cache = false, ttl = undefined) + +### async insert(table, row) + +### async update(table, row, where = false) + +### async delete(table, row, where = false) + +### stat() + +### cacheFlush(sql, params) + +### cacheFlushAll() + +### cacheStat() + ## Getting help If you've found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! diff --git a/index.js b/index.js index 303477e..1db023a 100644 --- a/index.js +++ b/index.js @@ -46,7 +46,7 @@ module.exports.connect = (config = {}) => { pool.q = async (sql, params = [], cache = false, ttl = undefined) => { qid++ const log = debug.extend(qid) - log(sql, params) + log(sql, params, {cache: cache, ttl: ttl ? ttl : DEFAULT_CACHE_TTL}) // https://medium.com/@chris_72272/what-is-the-fastest-node-js-hashing-algorithm-c15c1a0e164e const hash = crypto.createHash('sha1').update(sql + JSON.stringify(params)).digest('base64') if (cache && queryCache.has(hash)) { @@ -77,15 +77,7 @@ module.exports.connect = (config = {}) => { return Array.isArray(rows) && rows.length ? rows[0] : false } - pool.stat = () => { - return { - ALL: pool.pool._allConnections.toArray().length, - // USE: pool.pool._allConnections.toArray().length - pool.pool._freeConnections.toArray().length, - FRE: pool.pool._freeConnections.toArray().length, - QUE: pool.pool._connectionQueue.toArray().length - } - } - + // @todo insert array of objects pool.insert = pool.i = async (table, row) => { qid++ const log = debug.extend(qid) @@ -128,6 +120,32 @@ module.exports.connect = (config = {}) => { return rows || false } + pool.stat = () => { + return { + ALL: pool.pool._allConnections.toArray().length, + // USE: pool.pool._allConnections.toArray().length - pool.pool._freeConnections.toArray().length, + FRE: pool.pool._freeConnections.toArray().length, + QUE: pool.pool._connectionQueue.toArray().length + } + } + + pool.cacheFlush = (sql, params) => { + const hash = crypto.createHash('sha1').update(sql + JSON.stringify(params)).digest('base64') + const deleted = queryCache.del(hash) + debug('Cache flush', sql, params, { deleted }, queryCache.getStats()) + return deleted + } + + pool.cacheFlushAll = () => { + queryCache.flushAll() + debug('Cache flush all', queryCache.getStats()) + return true + } + + exports.cacheStat = () => { + return queryCache.getStats() + } + pool.on('acquire', (connection) => { debug('Connection #%s acquired', connection.threadId, pool.stat()) }) diff --git a/package-lock.json b/package-lock.json index c64d552..e4c1509 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,19 @@ { "name": "mysql2-cache", - "version": "1.0.1", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.0.1", + "version": "1.1.0", "license": "MIT", "dependencies": { - "crypto": "^1.0.1", - "debug": "^4.3.3", - "mysql2": "^2.3.3", + "debug": "^4.3.4", + "mysql2": "^3.2.0", "node-cache": "^5.1.2" + }, + "engines": { + "node": ">=14" } }, "node_modules/clone": { @@ -22,12 +24,6 @@ "node": ">=0.8" } }, - "node_modules/crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", - "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -77,19 +73,16 @@ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/ms": { @@ -98,16 +91,16 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mysql2": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", - "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.2.0.tgz", + "integrity": "sha512-0Vn6a9WSrq6fWwvPgrvIwnOCldiEcgbzapVRDAtDZ4cMTxN7pnGqCTx8EG32S/NYXl6AXkdO+9hV1tSIi/LigA==", "dependencies": { - "denque": "^2.0.1", + "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", - "long": "^4.0.0", - "lru-cache": "^6.0.0", - "named-placeholders": "^1.1.2", + "long": "^5.2.1", + "lru-cache": "^7.14.1", + "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" }, @@ -126,14 +119,6 @@ "node": ">=12.0.0" } }, - "node_modules/named-placeholders/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "engines": { - "node": ">=12" - } - }, "node_modules/node-cache": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", @@ -162,11 +147,6 @@ "engines": { "node": ">= 0.6" } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } }, "dependencies": { @@ -175,11 +155,6 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" }, - "crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -215,17 +190,14 @@ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" }, "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" }, "ms": { "version": "2.1.2", @@ -233,16 +205,16 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mysql2": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.3.3.tgz", - "integrity": "sha512-wxJUev6LgMSgACDkb/InIFxDprRa6T95+VEoR+xPvtngtccNH2dGjEB/fVZ8yg1gWv1510c9CvXuJHi5zUm0ZA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.2.0.tgz", + "integrity": "sha512-0Vn6a9WSrq6fWwvPgrvIwnOCldiEcgbzapVRDAtDZ4cMTxN7pnGqCTx8EG32S/NYXl6AXkdO+9hV1tSIi/LigA==", "requires": { - "denque": "^2.0.1", + "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.6.3", - "long": "^4.0.0", - "lru-cache": "^6.0.0", - "named-placeholders": "^1.1.2", + "long": "^5.2.1", + "lru-cache": "^7.14.1", + "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } @@ -253,13 +225,6 @@ "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", "requires": { "lru-cache": "^7.14.1" - }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" - } } }, "node-cache": { @@ -284,11 +249,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/package.json b/package.json index 240f32d..fe9f766 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "mysql2-cache", - "version": "1.0.1", + "version": "1.3.0", "description": "✔ MySQL2 upgrade: cache queries, easy shortcuts, logging and debugging.", "main": "index.js", "scripts": { - "test": "DEBUG=* node ./test/test.js" + "test": "node ./test/test.js" }, "repository": { "type": "git", @@ -27,10 +27,12 @@ "url": "https://github.com/2naive/node-mysql2-cache/issues" }, "homepage": "https://github.com/2naive/node-mysql2-cache#readme", + "engines": { + "node": ">=14" + }, "dependencies": { - "crypto": "^1.0.1", - "debug": "^4.3.3", - "mysql2": "^2.3.3", + "debug": "^4.3.4", + "mysql2": "^3.2.0", "node-cache": "^5.1.2" } } diff --git a/test/test.js b/test/test.js index 0074070..fe8b59d 100644 --- a/test/test.js +++ b/test/test.js @@ -1,72 +1,102 @@ -// @todo Node default test runner +// @todo Node default test runner or Jest const mysql = require('../index.js') +const debug = require('debug') +debug.enable('mysql2-cache*') // https://github.com/sidorares/node-mysql2/blob/master/examples/server.js const server = mysql.createServer() server.listen(3306) server.on('connection', conn => { const id = Math.floor(Math.random() * 100) - conn.serverHandshake({ - protocolVersion: 10, - serverVersion: '5.6.10', - connectionId: id, - statusFlags: 2, - characterSet: 8, - authCallback: (params) => { - conn.writeOk() - conn.sequenceId = 0 - }, - capabilityFlags: 2181036031 - }) - conn.on('query', query => { - // https://github.com/sidorares/node-mysql2/issues/528#issuecomment-944949065 - // https://github.com/sidorares/node-mysql2/blob/master/lib/constants/types.js - conn.sequenceId = 1 - conn.writeColumns([ - { - catalog: 'def', - schema: 'test', - table: 'test_table', - orgTable: 'test_table', - name: 'name', - orgName: 'name', - characterSet: 33, - columnLength: 384, - columnType: 253, - flags: 0, - decimals: 0 + try { + conn.serverHandshake({ + protocolVersion: 10, + serverVersion: '5.6.10', + connectionId: id, + statusFlags: 2, + characterSet: 8, + authCallback: (params) => { + conn.writeOk() + conn.sequenceId = 0 }, - { - catalog: 'def', - schema: 'test', - table: 'test_table', - orgTable: 'test_table', - name: 'age', - orgName: 'age', - characterSet: 33, - columnLength: 384, - columnType: 2, - flags: 0, - decimals: 0 - } - ]) - conn.writeTextRow(['Alice', id]) - conn.writeTextRow(['Bob', 42]) - conn.writeEof() - conn.sequenceId = 0 - conn.close() + capabilityFlags: 2181036031 + }) + } catch (error) { + console.error(error) + } + conn.on('query', query => { + try { + // https://github.com/sidorares/node-mysql2/issues/528#issuecomment-944949065 + // https://github.com/sidorares/node-mysql2/blob/master/lib/constants/types.js + conn.sequenceId = 1 + conn.writeColumns([ + { + catalog: 'def', + schema: 'test', + table: 'test_table', + orgTable: 'test_table', + name: 'name', + orgName: 'name', + characterSet: 33, + columnLength: 384, + columnType: 253, + flags: 0, + decimals: 0 + }, + { + catalog: 'def', + schema: 'test', + table: 'test_table', + orgTable: 'test_table', + name: 'age', + orgName: 'age', + characterSet: 33, + columnLength: 384, + columnType: 2, + flags: 0, + decimals: 0 + } + ]) + conn.writeTextRow(['Alice', id]) + conn.writeTextRow(['Bob', 42]) + conn.writeEof() + conn.sequenceId = 0 + // conn.close() + } catch (error) { + console.log('MySQL server on.query error', error) + } }) }) -const db = mysql.connect() -db.q('SELECT * FROM test_table').then(res => console.dir) -db.q('SELECT * FROM test_table', {}, true).then((res) => { - db.q('SELECT * FROM test_table', {}, true).then((res) => { - server.close() - process.exit(0) +const db = mysql.connect({ + connectionLimit: 2, + maxIdle: 1, + idleTimeout: 2000 +}); + +(async () => { + await db.q('DROP TABLE IF EXISTS test') + await db.q('CREATE TABLE test (`name` VARCHAR(50) NULL DEFAULT NULL, `age` INT(10) NULL DEFAULT NULL)') + db.insert('test', { name: 'Alice', age: 92 }) + db.insert('test', { name: 'Bob', age: 42 }) + // no cache + db.q('SELECT * FROM test LIMIT 1').then(res => console.dir) + // cache + db.q('SELECT * FROM test LIMIT 1', [], true).then((res) => { + db.q('SELECT * FROM test LIMIT 1', [], true) }) - console.log('✅ ', res) -}) + // cache, flush, flush all + db.q('SELECT * FROM test WHERE 1=0', [], true).then((res) => { + db.cacheFlush('SELECT * FROM test LIMIT 1', []) + db.cacheFlushAll() + }) + db.del('test', { age: 92 }) + await db.i('test', { name: 'Mark', age: 36 }) + await db.update('test', { age: 13 }, { age: 36 }) + await db.delete('test', { age: 13 }) + await db.insert('test', { name: 'Mark', age: 11 }) + await db.q('SELECT * FROM test') + process.exit(0) +})() -process.on('exit', code => { - console.log(`About to exit with code: ${code}`) -}) +// db.q('Unhandled rejection') +// throw(new Error('Unhandled error'))