Skip to content

Commit

Permalink
feat(pg): duck-type PG to prevent 'provide valid PG client' error (#168)
Browse files Browse the repository at this point in the history
* feat(pg): duck-type PG to prevent clashes

* Duck-type in watch mode also

* Satisfy Flow
  • Loading branch information
benjie committed Feb 17, 2018
1 parent a22e8c7 commit d5ed9a9
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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") {
Expand Down
40 changes: 35 additions & 5 deletions packages/graphile-build-pg/src/withPgClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => *
Expand All @@ -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);
Expand Down

0 comments on commit d5ed9a9

Please sign in to comment.