Skip to content

Commit

Permalink
feat(postgraphql): finish up cli
Browse files Browse the repository at this point in the history
  • Loading branch information
calebmer committed Oct 9, 2016
1 parent 9e180ed commit 79b6846
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 67 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
"postgres-interval": "^1.0.2"
},
"devDependencies": {
"@types/chalk": "^0.4.30",
"@types/change-case": "0.0.29",
"@types/commander": "^2.3.30",
"@types/debug": "0.0.29",
"@types/jest": "^0.9.30",
"@types/node": "^6.0.38",
Expand Down
44 changes: 0 additions & 44 deletions src/postgraphql/cli/index.js

This file was deleted.

101 changes: 101 additions & 0 deletions src/postgraphql/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env node

import { resolve as resolvePath } from 'path'
import { readFileSync } from 'fs'
import { createServer } from 'http'
import chalk = require('chalk')
import { Command } from 'commander'
import { parse as parsePGConnectionString } from 'pg-connection-string'
import postgraphql from '../postgraphql'
import start from './start'
import demo from './demo'

const manifest = JSON.parse(readFileSync(resolvePath(__dirname, '../../../package.json')).toString())
const program = new Command('postgraphql')

program
.version(manifest.version)
.usage('[options]')
.description(manifest.description)
.option('-d, --demo', 'run PostGraphQL using the demo database connection')
.option('-c, --connection <string>', 'the Postgres connection. if not provided it will be inferred from your environment')
.option('-s, --schema <string>', 'a Postgres schema to be introspected. Use commas to define multiple schemas', option => option.split(','))
.option('-t, --host <string>', 'the hostname to be used. Defaults to `localhost`')
.option('-p, --port <number>', 'the port to be used. Defaults to 5000', parseFloat)
.option('-l, --disable-query-log', 'disables the GraphQL query log')
.option('-m, --max-pool-size <number>', 'the maximum number of clients to keep in the Postgres pool. defaults to 10', parseFloat)
.option('-q, --graphql <path>', 'the route to mount the GraphQL server on. defaults to `/graphql`')
.option('-i, --graphiql <path>', 'the route to mount the GraphiQL interface on. defaults to `/graphiql`')
.option('-b, --disable-graphiql', 'disables the GraphiQL interface. overrides the GraphiQL route option')
.option('-r, --cors', 'enable generous CORS settings. this is disabled by default, if possible use a proxy instead')
.option('-a, --classic-ids', 'use classic global id field name. required to support Relay 1')
.option('-j, --dynamic-json', 'enable dynamic JSON in GraphQL inputs and outputs. uses stringified JSON by default')

program.on('--help', () => console.log(`
Get Started:
$ postgraphql --demo
$ postgraphql --schema my_schema
`.slice(1)))

program.parse(process.argv)

// If this module was not required, run our main function.
if (!module.parent)
main()

export default program

function main () {
const {
demo: isDemo = false,
connection: pgConnectionString,
schema: schemas = ['public'],
host: hostname = 'localhost',
port = 5000,
disableQueryLog,
maxPoolSize,
graphql: graphqlRoute = '/graphql',
graphiql: graphiqlRoute = '/graphiql',
disableGraphiql = false,
cors: enableCors = false,
classicIds = false,
dynamicJson = false,
} = program as any

const pgConfig = Object.assign(
{},
pgConnectionString ? parsePGConnectionString(pgConnectionString) : {
host: process.env.PGHOST || 'localhost',
port: process.env.PGPORT || 5432,
database: process.env.PGDATABASE,
},
{ max: maxPoolSize },
)

const server = createServer(postgraphql(pgConfig, schemas, {
classicIds,
dynamicJson,
graphqlRoute,
graphiqlRoute,
graphiql: !disableGraphiql,
enableQueryLog: !disableQueryLog,
enableCors,
}))

server.listen(port, hostname, () => {
console.log('')
console.log(`PostGraphQL server listening on port ${chalk.underline(port.toString())} 🚀`)
console.log('')
console.log(` ‣ Connected to Postgres instance ${chalk.underline.blue(`postgres://${pgConfig.host}:${pgConfig.port}${pgConfig.database ? pgConfig.database : ''}`)}`)
console.log(` ‣ Introspected Postgres schema(s) ${schemas.map(schema => chalk.magenta(schema)).join(', ')}`)
console.log(` ‣ GraphQL endpoint served at ${chalk.underline(`http://${hostname}:${port}${graphqlRoute}`)}`)

if (!disableGraphiql)
console.log(` ‣ GraphiQL endpoint served at ${chalk.underline(`http://${hostname}:${port}${graphiqlRoute}`)}`)

console.log('')
console.log(chalk.gray('* * *'))
console.log('')
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ for (const [name, createServerFromHandler] of serverCreators) {
test('will render GraphiQL on another route if desired', async () => {
const server1 = createServer({ graphiqlRoute: '/x' })
const server2 = createServer({ graphiql: true, graphiqlRoute: '/x' })
const server3 = createServer({ graphiql: false, graphiqlRoute: '/x' })
await (
request(server1)
.get('/x')
Expand All @@ -388,6 +389,16 @@ for (const [name, createServerFromHandler] of serverCreators) {
.expect(200)
.expect('Content-Type', 'text/html; charset=utf-8')
)
await (
request(server3)
.get('/x')
.expect(404)
)
await (
request(server3)
.get('/graphiql')
.expect(404)
)
})

test('can use a promised GraphQL schema', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export default function createPostGraphQLHTTPRequestHandler (config: {
// in JSON. Helpful for debugging.
showErrorStack?: boolean | 'json',

// Enables a query log. Whenever a GraphQL query is about to be executed, it
// will first be logged to the console.
enableQueryLogin?: boolean,

// Enables some CORS rules. When enabled there may be some pre-flight
// requests with negative performance impacts.
enableCORS?: boolean,
enableCors?: boolean,
}): HTTPRequestHandler
29 changes: 26 additions & 3 deletions src/postgraphql/http/createPostGraphQLHTTPRequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { $$pgClient } from '../../postgres/inventory/pgClientFromContext'
import renderGraphiQL from './renderGraphiQL'

const chalk = require('chalk')
const Debugger = require('debug')
const httpError = require('http-errors')
const parseUrl = require('parseurl')
Expand All @@ -23,6 +24,7 @@ export const $$pgClientOrigQuery = Symbol()

const debugGraphql = new Debugger('postgraphql:graphql')
const debugPG = new Debugger('postgraphql:postgres')
const debugRequest = new Debugger('postgraphql:request')

const favicon = new Promise((resolve, reject) => {
readFile(resolvePath(__dirname, '../../../resources/favicon.ico'), (error, data) => {
Expand All @@ -46,7 +48,7 @@ export default function createPostGraphQLHTTPRequestHandler (options) {

// Throw an error of the GraphQL and GraphiQL routes are the same.
if (graphqlRoute === graphiqlRoute)
throw new Error(`Cannot use the same route '${graphqlRoute}' for both GraphQL and GraphiQL.`)
throw new Error(`Cannot use the same route, '${graphqlRoute}', for both GraphQL and GraphiQL. Please use different routes.`)

// Formats an error using the default GraphQL `formatError` function, and
// custom formatting using some other options.
Expand Down Expand Up @@ -143,7 +145,7 @@ export default function createPostGraphQLHTTPRequestHandler (options) {

// Add our CORS headers to be good web citizens (there are perf
// implications though so be careful!)
if (options.enableCORS)
if (options.enableCors)
addCORSHeaders(res)

// Don’t execute our GraphQL stuffs for `OPTIONS` requests.
Expand All @@ -158,6 +160,10 @@ export default function createPostGraphQLHTTPRequestHandler (options) {
// a result. We also keep track of `params`.
let params
let result
let queryDocumentAST
const queryTimeStart = process.hrtime()

debugRequest('GraphQL query request has begun.')

// This big `try`/`catch`/`finally` block represents the execution of our
// GraphQL query. All errors thrown in this block will be returned to the
Expand Down Expand Up @@ -231,7 +237,6 @@ export default function createPostGraphQLHTTPRequestHandler (options) {
throw httpError(400, `Operation name must be a string, not '${typeof params.operationName}'.`)

const source = new Source(params.query, 'GraphQL HTTP Request')
let queryDocumentAST

// Catch an errors while parsing so that we can set the `statusCode` to
// 400. Otherwise we don’t need to parse this way.
Expand All @@ -243,6 +248,8 @@ export default function createPostGraphQLHTTPRequestHandler (options) {
throw error
}

debugRequest('GraphQL query is parsed.')

// Validate our GraphQL query using given rules.
// TODO: Add a complexity GraphQL rule.
const validationErrors = validateGraphql(await graphqlSchema, queryDocumentAST)
Expand All @@ -255,6 +262,8 @@ export default function createPostGraphQLHTTPRequestHandler (options) {
return
}

debugRequest('GraphQL query is validated.')

// Lazily log the query. If this debugger isn’t enabled, don’t run it.
if (debugGraphql.enabled)
debugGraphql(printGraphql(queryDocumentAST).replace(/\s+/g, ' ').trim())
Expand Down Expand Up @@ -303,6 +312,8 @@ export default function createPostGraphQLHTTPRequestHandler (options) {
await pgClient.query('commit')

pgClient.release()

debugRequest('GraphQL query has been executed.')
}
}
catch (error) {
Expand All @@ -322,6 +333,18 @@ export default function createPostGraphQLHTTPRequestHandler (options) {

res.setHeader('Content-Type', 'application/json; charset=utf-8')
res.end(JSON.stringify(result))

debugRequest('GraphQL query request finished.')

// Log the query. If this debugger isn’t enabled, don’t run it.
if (queryDocumentAST && options.enableQueryLog) {
const prettyQuery = printGraphql(queryDocumentAST).replace(/\s+/g, ' ').trim()
const errorCount = (result.errors || []).length
const ms = Math.round(process.hrtime(queryTimeStart)[1] * 10e-7 * 100) / 100

// If we have enabled the query log for the HTTP handler, use that.
console.log(`${chalk[errorCount === 0 ? 'green' : 'red'](`${errorCount} error(s)`)} in ${chalk.grey(`${ms}ms`)} :: ${prettyQuery}`)
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/postgraphql/postgraphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export default function postgraphql (
graphiqlRoute?: string,
graphiql?: boolean,
showErrorStack?: boolean,
enableCORS?: boolean,
enableQueryLog?: boolean,
enableCors?: boolean,
} = {},
): HTTPRequestHandler {
// Do some things with `poolOrConfig` so that in the end, we actually get a
Expand Down
18 changes: 0 additions & 18 deletions src/postgres/addPGToInventory.ts

This file was deleted.

0 comments on commit 79b6846

Please sign in to comment.