From d5ed9a935f7947210f2decfed3d447044ba896a2 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Sat, 17 Feb 2018 20:00:29 +0000 Subject: [PATCH] feat(pg): duck-type PG to prevent 'provide valid PG client' error (#168) * feat(pg): duck-type PG to prevent clashes * Duck-type in watch mode also * Satisfy Flow --- .../src/plugins/PgIntrospectionPlugin.js | 3 +- .../graphile-build-pg/src/withPgClient.js | 40 ++++++++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.js b/packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.js index d269c4eb0..314a69c4d 100644 --- a/packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.js +++ b/packages/graphile-build-pg/src/plugins/PgIntrospectionPlugin.js @@ -6,6 +6,7 @@ import { readFile as rawReadFile } from "fs"; import pg from "pg"; import debugFactory from "debug"; import chalk from "chalk"; +import { quacksLikePgPool } from "../withPgClient"; import { version } from "../../package.json"; @@ -260,7 +261,7 @@ export default (async function PgIntrospectionPlugin( await stopListening(); // Check we can get a pgClient - if (pgConfig instanceof pg.Pool) { + if (pgConfig instanceof pg.Pool || quacksLikePgPool(pgConfig)) { pgClient = await pgConfig.connect(); releasePgClient = () => pgClient && pgClient.release(); } else if (typeof pgConfig === "string") { diff --git a/packages/graphile-build-pg/src/withPgClient.js b/packages/graphile-build-pg/src/withPgClient.js index 9f48ea585..db2614ead 100644 --- a/packages/graphile-build-pg/src/withPgClient.js +++ b/packages/graphile-build-pg/src/withPgClient.js @@ -3,6 +3,35 @@ import pg from "pg"; import debugFactory from "debug"; const debug = debugFactory("graphile-build-pg"); +function constructorName(obj) { + return obj && typeof obj.constructor === "function" && obj.constructor.name; +} + +// Some duck-typing + +function quacksLikePgClient(pgConfig: mixed): boolean { + // A diagnosis of exclusion + if (!pgConfig || typeof pgConfig !== "object") return false; + if (constructorName(pgConfig) !== "Client") return false; + if (typeof pgConfig.connect !== "function") return false; + if (typeof pgConfig.end !== "function") return false; + if (typeof pgConfig.escapeLiteral !== "function") return false; + if (typeof pgConfig.escapeIdentifier !== "function") return false; + return true; +} + +export function quacksLikePgPool(pgConfig: mixed): boolean { + // A diagnosis of exclusion + if (!pgConfig || typeof pgConfig !== "object") return false; + if (constructorName(pgConfig) !== "Pool") return false; + if (!pgConfig.Client) return false; + if (!pgConfig.options) return false; + if (typeof pgConfig.connect !== "function") return false; + if (typeof pgConfig.end !== "function") return false; + if (typeof pgConfig.query !== "function") return false; + return true; +} + const withPgClient = async ( pgConfig: pg.Client | pg.Pool | string = process.env.DATABASE_URL, fn: (pgClient: pg.Client) => * @@ -11,18 +40,19 @@ const withPgClient = async ( throw new Error("Nothing to do!"); } let releasePgClient = () => {}; - let pgClient; + let pgClient: pg.Client; let result; try { - if (pgConfig instanceof pg.Client) { - pgClient = pgConfig; + if (pgConfig instanceof pg.Client || quacksLikePgClient(pgConfig)) { + pgClient = (pgConfig: pg.Client); if (!pgClient.release) { throw new Error( "We only support PG clients from a PG pool (because otherwise the `await` call can hang indefinitely if an error occurs and there's no error handler)" ); } - } else if (pgConfig instanceof pg.Pool) { - pgClient = await pgConfig.connect(); + } else if (pgConfig instanceof pg.Pool || quacksLikePgPool(pgConfig)) { + const pgPool = (pgConfig: pg.Pool); + pgClient = await pgPool.connect(); releasePgClient = () => pgClient.release(); } else if (pgConfig === undefined || typeof pgConfig === "string") { pgClient = new pg.Client(pgConfig);