diff --git a/README.md b/README.md index c6044ea..0bbdfd1 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,141 @@ declare module 'fastify' { } ``` +#### MySQLRowDataPacket +Ability to add type for return data using mysql2 [RowDataPacket](https://sidorares.github.io/node-mysql2/docs/documentation/typescript-examples#rowdatapacket). + +``` +const fastifyMysql, { MySQLRowDataPacket } from '@fastify/mysql' + +const app = fastify(); + +app.register(fastifyMysql, { + connectionString: "mysql://root@localhost/mysql", +}); + +app.get("/", async () => { + const connection = app.mysql; + + // SELECT + const [rows, fields] = await connection.query( + "SELECT 1 + 1 AS `test`;", + ); + + /** + * @rows: [ { test: 2 } ] + */ + return rows[0]; +}); +``` + +#### MySQLResultSetHeader +Ability to add type for return data using mysql2 [ResultSetHeader](https://sidorares.github.io/node-mysql2/docs/documentation/typescript-examples#resultsetheader). + +``` +const fastifyMysql, { MySQLResultSetHeader } from '@fastify/mysql' + +const app = fastify(); + +app.register(fastifyMysql, { + connectionString: "mysql://root@localhost/mysql", +}); + +app.get("/", async () => { + const connection = app.mysql; + const result = await connection.query("SET @1 = 1"); + + /** + * @result: ResultSetHeader { + fieldCount: 0, + affectedRows: 0, + insertId: 0, + info: '', + serverStatus: 2, + warningStatus: 0, + changedRows: 0 + } + */ + return result +}); +``` + +##### isMySQLPool +Method to check if fastify decorator, mysql is type of [MySQLPool](https://github.com/fastify/fastify-mysql/blob/master/types/index.d.ts#L32) + +```typescript +const app = fastify(); +app + .register(fastifyMysql, { + connectionString: "mysql://root@localhost/mysql", + }) + .after(function (err) { + if (isMySQLPool(app.mysql)) { + const mysql = app.mysql + mysql.getConnection(function (err, con) { + con.release(); + }); + mysql.pool.end(); + } + }) +``` + + +##### isMySQLPromisePool +Method to check if fastify decorator, mysql is type of [MySQLPromisePool](https://github.com/fastify/fastify-mysql/blob/master/types/index.d.ts#L43) + +```typescript +app + .register(fastifyMysql, { + promise: true, + connectionString: "mysql://root@localhost/mysql", + }) + .after(async function (err) { + if (isMySQLPromisePool(app.mysql)) { + const mysql = app.mysql + const con = await mysql.getConnection(); + con.release(); + mysql.pool.end(); + } + }); +``` + + +##### isMySQLConnection +Method to check if fastify decorator, mysql is type of [MySQLConnection](https://github.com/fastify/fastify-mysql/blob/master/types/index.d.ts#L28) + +```typescript +app + .register(fastifyMysql, { + type: "connection", + connectionString: "mysql://root@localhost/mysql", + }) + .after(async function (err) { + if (isMySQLConnection(app.mysql)) { + const mysql = app.mysql + mysql.connection.end(); + } + }); +``` + + +##### isMySQLPromiseConnection +Method to check if fastify decorator, mysql is type of [MySQLPromiseConnection](https://github.com/fastify/fastify-mysql/blob/master/types/index.d.ts#L36) + +```typescript +app + .register(fastifyMysql, { + type: "connection", + promise: true, + connectionString: "mysql://root@localhost/mysql", + }) + .after(async function (err) { + if (isMySQLPromiseConnection(app.mysql)) { + const mysql = app.mysql + mysql.connection.end(); + } + }); +``` + ## Acknowledgements This project is kindly sponsored by: diff --git a/index.js b/index.js index fd6d65f..e972401 100644 --- a/index.js +++ b/index.js @@ -100,9 +100,37 @@ function _createConnection ({ connectionType, options, usePromise }, cb) { } } +function isMySQLPoolOrPromisePool (obj) { + return obj && typeof obj.query === 'function' && typeof obj.execute === 'function' && typeof obj.getConnection === 'function' && typeof obj.pool === 'object' +} + +function isMySQLConnectionOrPromiseConnection (obj) { + return obj && typeof obj.query === 'function' && typeof obj.execute === 'function' && typeof obj.connection === 'object' +} + +function isMySQLPool (obj) { + return isMySQLPoolOrPromisePool(obj) && typeof obj.pool.promise === 'function' +} + +function isMySQLPromisePool (obj) { + return isMySQLPoolOrPromisePool(obj) && obj.pool.promise === undefined +} + +function isMySQLConnection (obj) { + return isMySQLConnectionOrPromiseConnection(obj) && typeof obj.connection.promise === 'function' && typeof obj.connection.authorized === 'boolean' +} + +function isMySQLPromiseConnection (obj) { + return isMySQLConnectionOrPromiseConnection(obj) && obj.connection.promise === undefined +} + module.exports = fp(fastifyMysql, { fastify: '4.x', name: '@fastify/mysql' }) module.exports.default = fastifyMysql module.exports.fastifyMysql = fastifyMysql +module.exports.isMySQLPool = isMySQLPool +module.exports.isMySQLPromisePool = isMySQLPromisePool +module.exports.isMySQLConnection = isMySQLConnection +module.exports.isMySQLPromiseConnection = isMySQLPromiseConnection diff --git a/test/connection.test.js b/test/connection.test.js index b7f4dd2..32bf361 100644 --- a/test/connection.test.js +++ b/test/connection.test.js @@ -3,6 +3,7 @@ const test = require('tap').test const Fastify = require('fastify') const fastifyMysql = require('../index') +const { isMySQLPool, isMySQLPromisePool, isMySQLConnection, isMySQLPromiseConnection } = fastifyMysql test('fastify.mysql namespace should exist', (t) => { const fastify = Fastify() @@ -198,6 +199,43 @@ test('promise connection', (t) => { t.end() }) +test('isMySQLConnection is true', (t) => { + t.plan(5) + const fastify = Fastify() + fastify.register(fastifyMysql, { + type: 'connection', + connectionString: 'mysql://root@localhost/mysql' + }) + fastify.ready((err) => { + t.error(err) + t.equal(isMySQLConnection(fastify.mysql), true) + t.equal(isMySQLPool(fastify.mysql), false) + t.equal(isMySQLPromiseConnection(fastify.mysql), false) + t.equal(isMySQLPromisePool(fastify.mysql), false) + t.end() + }) + fastify.close() +}) + +test('isMySQLPromiseConnection is true', (t) => { + t.plan(5) + const fastify = Fastify() + fastify.register(fastifyMysql, { + promise: true, + type: 'connection', + connectionString: 'mysql://root@localhost/mysql' + }) + fastify.ready((err) => { + t.error(err) + t.equal(isMySQLPromiseConnection(fastify.mysql), true) + t.equal(isMySQLPromisePool(fastify.mysql), false) + t.equal(isMySQLConnection(fastify.mysql), false) + t.equal(isMySQLPool(fastify.mysql), false) + t.end() + }) + fastify.close() +}) + test('Promise: should throw when mysql2 fail to perform operation', (t) => { t.plan(3) diff --git a/test/pool.promise.test.js b/test/pool.promise.test.js index 1364ae1..982ef7a 100644 --- a/test/pool.promise.test.js +++ b/test/pool.promise.test.js @@ -3,6 +3,7 @@ const test = require('tap').test const Fastify = require('fastify') const fastifyMysql = require('../index') +const { isMySQLPromisePool, isMySQLPromiseConnection, isMySQLPool, isMySQLConnection } = fastifyMysql test('promise pool', (t) => { let fastify @@ -82,3 +83,21 @@ test('promise pool', (t) => { t.end() }) + +test('isMySQLPromisePool is true', (t) => { + t.plan(5) + const fastify = Fastify() + fastify.register(fastifyMysql, { + promise: true, + connectionString: 'mysql://root@localhost/mysql' + }) + fastify.ready((err) => { + t.error(err) + t.equal(isMySQLPromisePool(fastify.mysql), true) + t.equal(isMySQLPromiseConnection(fastify.mysql), false) + t.equal(isMySQLPool(fastify.mysql), false) + t.equal(isMySQLConnection(fastify.mysql), false) + t.end() + }) + fastify.close() +}) diff --git a/test/pool.test.js b/test/pool.test.js index 51af15b..f41fc8a 100644 --- a/test/pool.test.js +++ b/test/pool.test.js @@ -4,6 +4,7 @@ const t = require('tap') const test = t.test const Fastify = require('fastify') const fastifyMysql = require('../index') +const { isMySQLPool, isMySQLPromisePool, isMySQLConnection, isMySQLPromiseConnection } = fastifyMysql test('fastify.mysql namespace should exist', t => { t.plan(9) @@ -168,3 +169,20 @@ test('synchronous functions', (t) => { t.end() }) }) + +test('isMySQLPool is true', (t) => { + t.plan(5) + const fastify = Fastify() + fastify.register(fastifyMysql, { + connectionString: 'mysql://root@localhost/mysql' + }) + fastify.ready((err) => { + t.error(err) + t.equal(isMySQLPool(fastify.mysql), true) + t.equal(isMySQLConnection(fastify.mysql), false) + t.equal(isMySQLPromisePool(fastify.mysql), false) + t.equal(isMySQLPromiseConnection(fastify.mysql), false) + t.end() + }) + fastify.close() +}) diff --git a/types/index.d.ts b/types/index.d.ts index 55d40fd..0940126 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -6,6 +6,9 @@ import { format, Pool, PoolOptions, + ProcedureCallPacket, + ResultSetHeader, + RowDataPacket, } from "mysql2"; import { Connection as PromiseConnection, @@ -16,6 +19,12 @@ type FastifyMysql = FastifyPluginCallback; declare namespace fastifyMysql { + type MySQLPoolConnection = MySQLPool | MySQLConnection | MySQLPromisePool | MySQLPromiseConnection; + export function isMySQLPool(obj: MySQLPoolConnection): obj is MySQLPool; + export function isMySQLPromisePool(obj: MySQLPoolConnection): obj is MySQLPromisePool; + export function isMySQLConnection(obj: MySQLPoolConnection): obj is MySQLConnection; + export function isMySQLPromiseConnection(obj: MySQLPoolConnection): obj is MySQLPromiseConnection; + // upstream package missed type type escapeId = (val: any, forbidQualified?: boolean) => string; @@ -56,9 +65,15 @@ declare namespace fastifyMysql { connectionString?: string; } + export type MySQLProcedureCallPacket< + T = [MySQLRowDataPacket[], MySQLResultSetHeader] | MySQLResultSetHeader, + > = ProcedureCallPacket + export type MySQLResultSetHeader = ResultSetHeader + export type MySQLRowDataPacket = RowDataPacket + export const fastifyMysql: FastifyMysql export { fastifyMysql as default } } declare function fastifyMysql(...params: Parameters): ReturnType -export = fastifyMysql \ No newline at end of file +export = fastifyMysql diff --git a/types/index.test-d.ts b/types/index.test-d.ts index 3ed5b3f..7e39234 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -4,7 +4,18 @@ import fastifyMysql, { MySQLPool, MySQLPromiseConnection, MySQLPromisePool, + MySQLProcedureCallPacket, + MySQLResultSetHeader, + MySQLRowDataPacket, + isMySQLPromiseConnection, + isMySQLConnection, + isMySQLPromisePool, + isMySQLPool, } from ".."; +import { expectType } from 'tsd'; +import { Pool } from "mysql2/typings/mysql/lib/Pool"; +import { Connection, PoolConnection } from "mysql2"; +import { Pool as PromisePool, Connection as PromiseConnection, PoolConnection as PromisePoolConnection } from "mysql2/promise"; declare module "fastify" { interface FastifyInstance { @@ -16,54 +27,160 @@ declare module "fastify" { } } +type PoolGetConnectionType = (callback: (err: NodeJS.ErrnoException | null, connection: PoolConnection) => any) => void; +type PoolPromiseType = (promiseImpl?: PromiseConstructor) => PromisePool; +type ConnectionPromiseType = (promiseImpl?: PromiseConstructor) => PromiseConnection; + const app = fastify(); app .register(fastifyMysql, { connectionString: "mysql://root@localhost/mysql", }) .after(function (err) { - const mysql = app.mysql as MySQLPool; - mysql.escapeId("foo"); - mysql.escape("bar"); - mysql.format("baz"); - mysql.query("SELECT NOW()", function () {}); - mysql.execute("SELECT NOW()", function () {}); - mysql.getConnection(function (err, con) { + if (isMySQLPool(app.mysql)) { + const mysql = app.mysql + mysql.escapeId("foo"); + mysql.escape("bar"); + mysql.format("baz"); + mysql.query("SELECT NOW()", function () {}); + mysql.execute("SELECT NOW()", function () {}); + mysql.getConnection(function (err, con) { + con.release(); + }); + mysql.pool.end(); + expectType(app.mysql.getConnection); + expectType(app.mysql.pool); + expectType(app.mysql.pool.promise); + } + }); + +app + .register(fastifyMysql, { + promise: true, + connectionString: "mysql://root@localhost/mysql", + }) + .after(async function (err) { + if (isMySQLPromisePool(app.mysql)) { + const mysql = app.mysql + mysql.escapeId("foo"); + mysql.escape("bar"); + mysql.format("baz"); + await mysql.query("SELECT NOW()"); + await mysql.execute("SELECT NOW()"); + const con = await mysql.getConnection(); con.release(); - }); - mysql.pool.end(); + mysql.pool.end(); + expectType<() => Promise>(app.mysql.getConnection); + expectType(app.mysql.pool); + } + }); + +app + .register(fastifyMysql, { + type: "connection", + connectionString: "mysql://root@localhost/mysql", + }) + .after(async function (err) { + if (isMySQLConnection(app.mysql)) { + const mysql = app.mysql + mysql.escapeId("foo"); + mysql.escape("bar"); + mysql.format("baz"); + mysql.query("SELECT NOW()", function () {}); + mysql.execute("SELECT NOW()", function () {}); + mysql.connection.end(); + expectType(app.mysql.connection); + expectType(app.mysql.connection.promise); + expectType(app.mysql.connection.authorized); + } + }); + +app + .register(fastifyMysql, { + type: "connection", + promise: true, + connectionString: "mysql://root@localhost/mysql", + }) + .after(async function (err) { + if (isMySQLPromiseConnection(app.mysql)) { + const mysql = app.mysql + mysql.escapeId("foo"); + mysql.escape("bar"); + mysql.format("baz"); + await mysql.query("SELECT NOW()"); + await mysql.execute("SELECT NOW()"); + mysql.connection.end(); + expectType(app.mysql.connection); + expectType(app.mysql.connection.threadId); + } }); app .register(fastifyMysql, { + type: "connection", promise: true, connectionString: "mysql://root@localhost/mysql", }) .after(async function (err) { - const mysql = app.mysql as MySQLPromisePool; - mysql.escapeId("foo"); - mysql.escape("bar"); - mysql.format("baz"); - await mysql.query("SELECT NOW()"); - await mysql.execute("SELECT NOW()"); - const con = await mysql.getConnection(); - con.release(); - mysql.pool.end(); + if (isMySQLPromiseConnection(app.mysql)) { + const mysql = app.mysql + const result = await mysql.connection.query('SELECT NOW()'); + expectType(result[0]); + mysql.connection.end(); + } }); app .register(fastifyMysql, { type: "connection", + promise: true, + connectionString: "mysql://root@localhost/mysql", + multipleStatements: true, + }) + .after(async function (err) { + if (isMySQLPromiseConnection(app.mysql)) { + const mysql = app.mysql + const result = await mysql.connection.query(` + SELECT 1 + 1 AS test; + SELECT 2 + 2 AS test; + `); + expectType(result[0]); + mysql.connection.end(); + } + }); + +app + .register(fastifyMysql, { + type: "connection", + promise: true, + connectionString: "mysql://root@localhost/mysql", + }) + .after(async function (err) { + if (isMySQLPromiseConnection(app.mysql)) { + const mysql = app.mysql + const result = await mysql.connection.query('SET @1 = 1'); + expectType(result[0]); + mysql.connection.end(); + } + }); + +app + .register(fastifyMysql, { + type: "connection", + promise: true, connectionString: "mysql://root@localhost/mysql", + multipleStatements: true, }) .after(async function (err) { - const mysql = app.mysql as MySQLConnection; - mysql.escapeId("foo"); - mysql.escape("bar"); - mysql.format("baz"); - mysql.query("SELECT NOW()", function () {}); - mysql.execute("SELECT NOW()", function () {}); - mysql.connection.end(); + if (isMySQLPromiseConnection(app.mysql)) { + const mysql = app.mysql + const result = await mysql.connection.query(` + SET @1 = 1; + SET @2 = 2; + `); + expectType(result[0]); + mysql.connection.end(); + } }); app @@ -73,11 +190,18 @@ app connectionString: "mysql://root@localhost/mysql", }) .after(async function (err) { - const mysql = app.mysql as MySQLPromiseConnection; - mysql.escapeId("foo"); - mysql.escape("bar"); - mysql.format("baz"); - await mysql.query("SELECT NOW()"); - await mysql.execute("SELECT NOW()"); - mysql.connection.end(); + if (isMySQLPromiseConnection(app.mysql)) { + const mysql = app.mysql + mysql.connection.query('DROP PROCEDURE IF EXISTS myProcedure'); + mysql.connection.query(` + CREATE PROCEDURE myProcedure() + BEGIN + SET @1 = 1; + SET @2 = 2; + END + `); + const result = await mysql.connection.query>('CALL myProcedure()'); + expectType>(result[0]); + mysql.connection.end(); + } });