import { Pool , PoolConfig } from 'pg' ; import { IncomingMessage , ServerResponse } from 'http' ; import { GraphQLSchema , GraphQLError } from 'graphql' ; import { EventEmitter } from 'events' ; import { createPostGraphileSchema , watchPostGraphileSchema } from 'postgraphile-core' ; import createPostGraphileHttpRequestHandler from './http/createPostGraphileHttpRequestHandler' ; import exportPostGraphileSchema from './schema/exportPostGraphileSchema' ; import { pluginHookFromOptions } from './pluginHook' ; import { PostGraphileOptions , mixed , HttpRequestHandler } from '../interfaces' ; import chalk from 'chalk' ; import { debugPgClient } from './withPostGraphileContext' ; import { ShutdownActions } from './shutdownActions' ; const sleep = ( ms ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ; function isPlainObject ( obj ) { if ( ! obj || typeof obj !== 'object' || String ( obj ) !== '[object Object]' ) return false ; const proto = Object . getPrototypeOf ( obj ) ; if ( proto === null || proto === Object . prototype ) { return true ; } return false ; } export interface PostgraphileSchemaBuilder < Request extends IncomingMessage = IncomingMessage , Response extends ServerResponse = ServerResponse > { _emitter : EventEmitter ; getGraphQLSchema : ( ) => Promise < GraphQLSchema > ; options : PostGraphileOptions < Request , Response > ; } /**
 * Creates a PostGraphile Http request handler by first introspecting the
 * database to get a GraphQL schema, and then using that to create the Http
 * request handler.
 */ export function getPostgraphileSchemaBuilder < Request extends IncomingMessage = IncomingMessage , Response extends ServerResponse = ServerResponse > ( pgPool , schema , incomingOptions , shutdownActions = new ShutdownActions ( ) ) { if ( incomingOptions . live && incomingOptions . subscriptions == null ) { incomingOptions . subscriptions = true ; } const pluginHook = pluginHookFromOptions ( incomingOptions ) ; const options = pluginHook ( 'postgraphile:options' , incomingOptions , { pgPool , schema , } ) ; if ( options . jwtSecret && ! options . jwtPgTypeIdentifier ) { console . warn ( 'WARNING: jwtSecret provided, however jwtPgTypeIdentifier (token identifier) not provided.' ) ; } if ( options . handleErrors && ( options . extendedErrors || options . showErrorStack ) ) { throw new Error ( `You cannot combine 'handleErrors' with the other error options` ) ; } const pgSchemas = Array . isArray ( schema ) ? schema : [ schema ] ; const _emitter = options [ '_emitter' ] || new EventEmitter ( ) ; let gqlSchema ; const gqlSchemaPromise = createGqlSchema ( ) ; return { _emitter , getGraphQLSchema : ( ) => ( gqlSchema ? Promise . resolve ( gqlSchema ) : gqlSchemaPromise ) , options , } ; async function createGqlSchema ( ) { let attempts = 0 ; let isShuttingDown = false ; shutdownActions . add ( async ( ) => { isShuttingDown = true ; } ) ; const assertAlive = ( ) => { if ( isShuttingDown ) { throw Object . assign ( new Error ( 'PostGraphile is shutting down' ) , { isShutdownAction : true } ) ; } } ; let releaseWatchFnPromise = null ; shutdownActions . add ( async ( ) => { if ( releaseWatchFnPromise ) { try { const releaseWatchFn = await releaseWatchFnPromise ; await releaseWatchFn ( ) ; } catch ( e ) { } } } ) ; let gqlSchemaPromise = null ; shutdownActions . add ( async ( ) => { if ( gqlSchemaPromise ) { await gqlSchemaPromise . catch ( ( ) => null ) ; } } ) ; while ( true ) { assertAlive ( ) ; try { if ( options . watchPg ) { releaseWatchFnPromise = watchPostGraphileSchema ( pgPool , pgSchemas , options , newSchema => { gqlSchema = newSchema ; _emitter . emit ( 'schemas:changed' ) ; exportGqlSchema ( gqlSchema ) ; } ) ; await releaseWatchFnPromise ; assertAlive ( ) ; if ( ! gqlSchema ) { throw new Error ( "Consistency error: watchPostGraphileSchema promises to call the callback before the promise resolves; but this hasn't happened" ) ; } } else { gqlSchemaPromise = createPostGraphileSchema ( pgPool , pgSchemas , options ) ; gqlSchema = await gqlSchemaPromise ; assertAlive ( ) ; exportGqlSchema ( gqlSchema ) ; } if ( attempts > 0 ) { console . error ( `Schema ${ attempts > 15 ? 'eventually' : attempts > 5 ? 'finally' : 'now' } generated successfully` ) ; } return gqlSchema ; } catch ( error ) { releaseWatchFnPromise = null ; gqlSchemaPromise = null ; attempts ++ ; const delay = Math . min ( 100 * Math . pow ( attempts , 2 ) , 30000 ) ; if ( error . isShutdownAction ) { throw error ; } else if ( isShuttingDown ) { console . error ( 'An error occurred whilst building the schema. However, the server was shutting down, which might have caused it.' ) ; console . error ( error ) ; throw error ; } else if ( typeof options . retryOnInitFail === 'function' ) { try { const start = process . hrtime ( ) ; const retry = await options . retryOnInitFail ( error , attempts ) ; const diff = process . hrtime ( start ) ; const dur = diff [ 0 ] * 1e3 + diff [ 1 ] * 1e-6 ; if ( isShuttingDown ) { throw error ; } else if ( ! retry ) { await shutdownActions . invokeAll ( ) . catch ( e => { console . error ( 'An additional error occured whilst calling shutdownActions.invokeAll():' ) ; console . error ( e ) ; } ) ; throw error ; } else { if ( dur < 50 ) { console . error ( `Your retryOnInitFail function should include a delay before resolving; falling back to a ${ delay }ms wait (attempts = ${ attempts }) to avoid overwhelming the database.` ) ; await sleep ( delay ) ; } } } catch ( e ) { throw Object . defineProperties ( new GraphQLError ( 'Failed to initialize GraphQL schema.' , undefined , undefined , undefined , undefined , e ) , { status : { value : 503 , enumerable : false , } , } ) ; } } else { const exitOnFail = ! options . retryOnInitFail ; logSeriousError ( error , 'building the initial schema' + ( attempts > 1 ? ` (attempt ${ attempts })` : '' ) , exitOnFail ? 'Exiting because `retryOnInitFail` is not set.' : `We'll try again in ${ delay }ms.` ) ; if ( exitOnFail ) { process . exit ( 34 ) ; } await sleep ( delay ) ; } } } } async function exportGqlSchema ( newGqlSchema ) { try { await exportPostGraphileSchema ( newGqlSchema , options ) ; } catch ( error ) { process . exitCode = 35 ; logSeriousError ( error , 'exporting the schema' ) ; } } } export default function postgraphile < Request extends IncomingMessage = IncomingMessage , Response extends ServerResponse = ServerResponse > ( poolOrConfig ? , schema ? , options ? ) ; export default function postgraphile < Request extends IncomingMessage = IncomingMessage , Response extends ServerResponse = ServerResponse > ( poolOrConfig ? , options ? ) ; export default function postgraphile < Request extends IncomingMessage = IncomingMessage , Response extends ServerResponse = ServerResponse > ( poolOrConfig ? , schemaOrOptions ? , maybeOptions ? ) { let schema ; let incomingOptions ; if ( typeof schemaOrOptions === 'string' || Array . isArray ( schemaOrOptions ) ) { schema = schemaOrOptions ; incomingOptions = maybeOptions || { } ; } else if ( typeof schemaOrOptions === 'object' ) { schema = 'public' ; incomingOptions = schemaOrOptions || maybeOptions || { } ; } else if ( arguments . length > 1 ) { throw new Error ( 'The second argument to postgraphile was invalid... did you mean to set a schema?' ) ; } else { schema = 'public' ; incomingOptions = { } ; } if ( typeof poolOrConfig === 'undefined' && arguments . length >= 1 ) { throw new Error ( 'The first argument to postgraphile was `undefined`... did you mean to set pool options?' ) ; } const shutdownActions = new ShutdownActions ( ) ; const { pgPool , releasePgPool } = toPgPool ( poolOrConfig ) ; if ( releasePgPool ) { shutdownActions . add ( releasePgPool ) ; } pgPool . on ( 'error' , err => { console . error ( 'PostgreSQL client generated error: ' , err . message ) ; } ) ; pgPool . on ( 'connect' , pgClient => { debugPgClient ( pgClient , ! ! options . allowExplain ) ; } ) ; const { getGraphQLSchema , options , _emitter } = getPostgraphileSchemaBuilder < Request , Response > ( pgPool , schema , incomingOptions , shutdownActions ) ; return createPostGraphileHttpRequestHandler ( { ... ( typeof poolOrConfig === 'string' ? { ownerConnectionString : poolOrConfig } : { } ) , ... options , getGqlSchema : getGraphQLSchema , pgPool , _emitter , shutdownActions , } ) ; } function logSeriousError ( error , when , nextSteps ? ) { console . error ( `A ${ chalk . bold ( 'serious error' ) } occurred when ${ chalk . bold ( when ) }. ${ nextSteps ? nextSteps + ' ' : '' }Error details:\n\n${ error . stack }\n` ) ; } function hasPoolConstructor ( obj ) { return ( ( obj && typeof obj . constructor === 'function' && obj . constructor === ( Pool as any ) . super_ ) || false ) ; } function constructorName ( obj ) { return ( ( obj && typeof obj . constructor === 'function' && obj . constructor . name && String ( obj . constructor . name ) ) || null ) ; } function toPgPool ( poolOrConfig ) { if ( quacksLikePgPool ( poolOrConfig ) ) { return { pgPool : poolOrConfig , releasePgPool : null } ; } if ( typeof poolOrConfig === 'string' ) { const pgPool = new Pool ( { connectionString : poolOrConfig } ) ; return { pgPool , releasePgPool : ( ) => pgPool . end ( ) } ; } else if ( ! poolOrConfig ) { const pgPool = new Pool ( { } ) ; return { pgPool , releasePgPool : ( ) => pgPool . end ( ) } ; } else if ( isPlainObject ( poolOrConfig ) ) { const pgPool = new Pool ( poolOrConfig ) ; return { pgPool , releasePgPool : ( ) => pgPool . end ( ) } ; } else { throw new Error ( 'Invalid connection string / Pool ' ) ; } } function quacksLikePgPool ( pgConfig ) { if ( pgConfig instanceof Pool ) return true ; if ( hasPoolConstructor ( pgConfig ) ) return true ; if ( ! pgConfig || typeof pgConfig !== 'object' ) return false ; if ( constructorName ( pgConfig ) !== 'Pool' && constructorName ( pgConfig ) !== 'BoundPool' ) 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 ; }