diff --git a/README.md b/README.md index 231d1bc..d95d644 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,16 @@ fastify.listen(3000, err => { }) ``` +## TypeScript Usage + +Install the compiler and typings for pg module: + +```shell script +npm install --save-dev typescript @types/pg +``` + +You can find examples in the [examples/typescript](./examples/typescript) directory. + ## Development and Testing First, start postgres with: diff --git a/examples/typescript/multiple-db/app.ts b/examples/typescript/multiple-db/app.ts new file mode 100644 index 0000000..4556843 --- /dev/null +++ b/examples/typescript/multiple-db/app.ts @@ -0,0 +1,37 @@ +import fastify from 'fastify'; + +import { fastifyPostgres } from '../../../index'; + +const app = fastify(); + +app.register(fastifyPostgres, { + name: 'sum', + connectionString: 'postgres://user:password@host:port/sub-db', +}); + +app.register(fastifyPostgres, { + name: 'sub', + connectionString: 'postgres://user:password@host:port/sub-db', +}); + +app.get('/calc', async () => { + const sumClient = await app.pg.sum.connect(); + const subClient = await app.pg.sub.connect(); + + const sumResult = await sumClient.query<{ sum: number }>( + 'SELECT 2 + 2 as sum' + ); + const subResult = await subClient.query<{ sub: number }>( + 'SELECT 6 - 3 as sub' + ); + + sumClient.release(); + subClient.release(); + + return { + sum: sumResult.rows, + sub: subResult.rows, + }; +}); + +export { app }; diff --git a/examples/typescript/multiple-db/fastify.d.ts b/examples/typescript/multiple-db/fastify.d.ts new file mode 100644 index 0000000..444524d --- /dev/null +++ b/examples/typescript/multiple-db/fastify.d.ts @@ -0,0 +1,10 @@ +import type { PostgresDb } from '../../../index'; + +declare module 'fastify' { + export interface FastifyInstance { + pg: { + sum: PostgresDb; + sub: PostgresDb; + }; + } +} diff --git a/examples/typescript/multiple-db/tsconfig.json b/examples/typescript/multiple-db/tsconfig.json new file mode 100644 index 0000000..97c2b2f --- /dev/null +++ b/examples/typescript/multiple-db/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@tsconfig/node10/tsconfig.json", + "compilerOptions": { + "noEmit": true, + } +} diff --git a/examples/typescript/single-db/app.ts b/examples/typescript/single-db/app.ts new file mode 100644 index 0000000..a93182a --- /dev/null +++ b/examples/typescript/single-db/app.ts @@ -0,0 +1,23 @@ +import fastify from 'fastify'; + +import { fastifyPostgres } from '../../../index'; + +const app = fastify(); + +app.register(fastifyPostgres, { + connectionString: 'postgres://user:password@host:port/db', +}); + +app.get('/calc', async () => { + const client = await app.pg.connect(); + + const sumResult = await client.query<{ sum: number }>('SELECT 2 + 2 as sum'); + + client.release(); + + return { + sum: sumResult.rows, + }; +}); + +export { app }; diff --git a/examples/typescript/single-db/fastify.d.ts b/examples/typescript/single-db/fastify.d.ts new file mode 100644 index 0000000..8c72cc7 --- /dev/null +++ b/examples/typescript/single-db/fastify.d.ts @@ -0,0 +1,7 @@ +import type { PostgresDb } from '../../../index'; + +declare module 'fastify' { + export interface FastifyInstance { + pg: PostgresDb; + } +} diff --git a/examples/typescript/single-db/tsconfig.json b/examples/typescript/single-db/tsconfig.json new file mode 100644 index 0000000..97c2b2f --- /dev/null +++ b/examples/typescript/single-db/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@tsconfig/node10/tsconfig.json", + "compilerOptions": { + "noEmit": true, + } +} diff --git a/examples/typescript/transactions/app.ts b/examples/typescript/transactions/app.ts new file mode 100644 index 0000000..636ec59 --- /dev/null +++ b/examples/typescript/transactions/app.ts @@ -0,0 +1,51 @@ +import fastify from 'fastify'; + +import { fastifyPostgres } from '../../../index'; + +const app = fastify(); + +app.register(fastifyPostgres, { + connectionString: 'postgres://user:password@host:port/db', +}); + +app.post('/init-async', async () => { + const createTableQuery = ` + CREATE TABLE routes ( + id bigserial primary key, + name varchar(80) NOT NULL, + created_at timestamp default NULL + ); + `; + + return app.pg.transact(async (client) => { + const result = await client.query(createTableQuery); + + return result; + }); +}); + +app.post('/init-cb', (_req, reply) => { + const createTableQuery = ` + CREATE TABLE routes ( + id bigserial primary key, + name varchar(80) NOT NULL, + created_at timestamp default NULL + ); + `; + + app.pg.transact( + (client) => { + return client.query(createTableQuery); + }, + (error, result) => { + if (error) { + reply.status(500).send(error); + return; + } + + reply.status(200).send(result); + } + ); +}); + +export { app }; diff --git a/examples/typescript/transactions/fastify.d.ts b/examples/typescript/transactions/fastify.d.ts new file mode 100644 index 0000000..8c72cc7 --- /dev/null +++ b/examples/typescript/transactions/fastify.d.ts @@ -0,0 +1,7 @@ +import type { PostgresDb } from '../../../index'; + +declare module 'fastify' { + export interface FastifyInstance { + pg: PostgresDb; + } +} diff --git a/examples/typescript/transactions/tsconfig.json b/examples/typescript/transactions/tsconfig.json new file mode 100644 index 0000000..97c2b2f --- /dev/null +++ b/examples/typescript/transactions/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@tsconfig/node10/tsconfig.json", + "compilerOptions": { + "noEmit": true, + } +} diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..174af26 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,41 @@ +import { FastifyPluginCallback } from 'fastify'; +import * as Pg from 'pg'; + +declare function transact( + fn: (client: Pg.PoolClient) => Promise +): Promise; + +declare function transact( + fn: (client: Pg.PoolClient) => Promise, + cb: (error: Error | null, result?: TResult) => void +): void; + +type PostgresDb = { + pool: Pg.Pool; + Client: Pg.Client; + query: Pg.Pool['query']; + connect: Pg.Pool['connect']; + transact: typeof transact; +}; + +type PostgresPluginOptions = { + /** + * Custom pg + */ + pg?: typeof Pg; + + /** + * Use pg-native + */ + native?: boolean; + + /** + * Instance name of fastify-postgres + */ + name?: string; +} & Pg.PoolConfig; + +declare const fastifyPostgres: FastifyPluginCallback; + +export { fastifyPostgres, PostgresDb, PostgresPluginOptions }; +export default fastifyPostgres; diff --git a/index.js b/index.js index bc79d75..a6dda6e 100644 --- a/index.js +++ b/index.js @@ -54,9 +54,11 @@ function transact (fn, cb) { function fastifyPostgres (fastify, options, next) { let pg = defaultPg + if (options.pg) { pg = options.pg } + if (options.native) { delete options.native if (!pg.native) { diff --git a/package.json b/package.json index 8c80f12..81bd945 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,13 @@ "version": "3.0.1", "description": "Fastify PostgreSQL connection plugin", "main": "index.js", + "types": "index.d.ts", "scripts": { - "test": "standard && tap -J test/*.test.js", + "test": "standard && tap -J test/*.test.js && npm run test:typescript", + "test:typescript": "tsd", "test:report": "standard && tap -J --coverage-report=html test/*.test.js", "test:verbose": "standard && tap -J test/*.test.js -Rspec", + "check-examples": "tsc --build examples/typescript/*", "postgres": "docker run -p 5432:5432 --name fastify-postgres -e POSTGRES_PASSWORD=postgres -d postgres:11-alpine", "load-data": "docker exec -it fastify-postgres psql -c 'CREATE TABLE users(id serial PRIMARY KEY, username VARCHAR (50) NOT NULL);' -U postgres -d postgres" }, @@ -32,13 +35,20 @@ "fastify-plugin": "^2.0.0" }, "devDependencies": { + "@tsconfig/node10": "^1.0.7", + "@types/pg": "^7.14.4", "fastify": "^3.0.0", "pg": "^8.2.1", "pg-native": "^3.0.0", "standard": "^14.0.0", - "tap": "^14.10.7" + "tap": "^14.10.7", + "tsd": "^0.13.1", + "typescript": "^4.0.2" }, "peerDependencies": { "pg": ">=6.0.0" + }, + "tsd": { + "directory": "test/types" } } diff --git a/test/types/imports.test-d.ts b/test/types/imports.test-d.ts new file mode 100644 index 0000000..ca66dbe --- /dev/null +++ b/test/types/imports.test-d.ts @@ -0,0 +1,6 @@ +import defaultPluginImport from '../../index'; +import { + fastifyPostgres as namedPluginImport, + PostgresDb, + PostgresPluginOptions, +} from '../../index'; diff --git a/test/types/initialization.test-d.ts b/test/types/initialization.test-d.ts new file mode 100644 index 0000000..0c3dc36 --- /dev/null +++ b/test/types/initialization.test-d.ts @@ -0,0 +1,32 @@ +import fastify from 'fastify'; +import * as pg from 'pg'; + +import fastifyPostgres from '../../index'; + +const app = fastify(); + +// Without parameters +app.register(fastifyPostgres); +app.register(fastifyPostgres, {}); + +// Own pg adapter +app.register(fastifyPostgres, { pg }); + +// Native libpq wrapper +app.register(fastifyPostgres, { native: true }); + +// Multiple databases +app.register(fastifyPostgres, { name: 'users' }); +app.register(fastifyPostgres, { name: 'posts' }); + +// Pool options +app.register(fastifyPostgres, { + user: 'dbuser', + host: 'database.server.com', + database: 'mydb', + password: 'secretpassword', + port: 3211, +}); +app.register(fastifyPostgres, { + connectionString: 'postgres://user:password@host:port/db', +}); diff --git a/test/types/query.test-d.ts b/test/types/query.test-d.ts new file mode 100644 index 0000000..abcad5a --- /dev/null +++ b/test/types/query.test-d.ts @@ -0,0 +1,36 @@ +import fastify from 'fastify'; +import { Client, Pool, PoolClient, QueryResult } from 'pg'; +import { expectType } from 'tsd'; + +import fastifyPostgres, { PostgresDb } from '../../index'; + +const app = fastify(); + +app.register(fastifyPostgres, { + connectionString: 'postgres://user:password@host:port/db', +}); + +declare module 'fastify' { + export interface FastifyInstance { + pg: PostgresDb; + } +} + +app.get('/calc', async () => { + expectType(app.pg); + + expectType(app.pg.pool); + expectType(app.pg.Client); + + const client = await app.pg.connect(); + expectType(client); + + const sumResult = await client.query<{ sum: number }>('SELECT 2 + 2 as sum'); + expectType>(sumResult); + + client.release(); + + return { + sum: sumResult.rows, + }; +}); diff --git a/test/types/transaction.test-d.ts b/test/types/transaction.test-d.ts new file mode 100644 index 0000000..3bc6ccf --- /dev/null +++ b/test/types/transaction.test-d.ts @@ -0,0 +1,62 @@ +import fastify from 'fastify'; +import { PoolClient, QueryResult } from 'pg'; +import { expectType } from 'tsd'; + +import fastifyPostgres, { PostgresDb } from '../../index'; + +const app = fastify(); + +app.register(fastifyPostgres, { + connectionString: 'postgres://user:password@host:port/db', +}); + +declare module 'fastify' { + export interface FastifyInstance { + pg: PostgresDb; + } +} + +app.post('/insert-async', async () => { + const insertQuery = ` + INSERT INTO routes(name) + VALUES ('ochakovo') + RETURNING 1 + 1 as sum; + `; + + const transactionResult = await app.pg.transact((client) => { + expectType(client); + + return client.query<{ sum: number }>(insertQuery); + }); + + expectType>(transactionResult); + + return transactionResult; +}); + +app.post('/insert-cb', (_req, reply) => { + const insertQuery = ` + INSERT INTO routes(name) + VALUES ('ochakovo') + RETURNING 1 + 1 as sum; + `; + + app.pg.transact( + (client) => { + expectType(client); + + return client.query<{ sum: number }>(insertQuery); + }, + (error, result) => { + expectType(error); + expectType | undefined>(result); + + if (error) { + reply.status(500).send(error); + return; + } + + reply.status(200).send(result); + } + ); +}); diff --git a/types/tsconfig.json b/types/tsconfig.json new file mode 100644 index 0000000..534ed56 --- /dev/null +++ b/types/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "fastify/types/tsconfig.json" +}