import type { IncomingMessage , ServerResponse } from 'http' ; import type { DocumentNode , ValidationRule , ExecutionArgs , ExecutionResult , FormattedExecutionResult , GraphQLSchema , GraphQLFieldResolver , GraphQLTypeResolver , GraphQLFormattedError , } from 'graphql' ; import accepts from 'accepts' ; import httpError from 'http-errors' ; import { Source , GraphQLError , parse , validate , execute , formatError , validateSchema , getOperationAST , specifiedRules , } from 'graphql' ; import type { GraphiQLOptions , GraphiQLData } from './renderGraphiQL' ; import { parseBody } from './parseBody' ; import { renderGraphiQL } from './renderGraphiQL' ; type Request = IncomingMessage & { url : string ; } ; type Response = ServerResponse & { json ? : ( data : unknown ) => void ; } ; type MaybePromise < T > = Promise < T > | T ; /**
 * Used to configure the graphqlHTTP middleware by providing a schema
 * and other configuration options.
 *
 * Options can be provided as an Object, a Promise for an Object, or a Function
 * that returns an Object or a Promise for an Object.
 */ export type Options = ( ( request : Request , response : Response , params ? : GraphQLParams ) => MaybePromise < OptionsData > ) | MaybePromise < OptionsData > ; export interface OptionsData { /**
     * A GraphQL schema from graphql-js.
     */ schema : GraphQLSchema ; /**
     * A value to pass as the context to this middleware.
     */ context ? : unknown ; /**
     * An object to pass as the rootValue to the graphql() function.
     */ rootValue ? : unknown ; /**
     * A boolean to configure whether the output should be pretty-printed.
     */ pretty ? : boolean ; /**
     * An optional array of validation rules that will be applied on the document
     * in additional to those defined by the GraphQL spec.
     */ validationRules ? : ReadonlyArray < ValidationRule > ; /**
     * An optional function which will be used to validate instead of default `validate`
     * from `graphql-js`.
     */ customValidateFn ? : ( schema : GraphQLSchema , documentAST : DocumentNode , rules : ReadonlyArray < ValidationRule > ) => ReadonlyArray < GraphQLError > ; /**
     * An optional function which will be used to execute instead of default `execute`
     * from `graphql-js`.
     */ customExecuteFn ? : ( args : ExecutionArgs ) => MaybePromise < ExecutionResult > ; /**
     * An optional function which will be used to format any errors produced by
     * fulfilling a GraphQL operation. If no function is provided, GraphQL's
     * default spec-compliant `formatError` function will be used.
     */ customFormatErrorFn ? : ( error : GraphQLError ) => GraphQLFormattedError ; /**
     * An optional function which will be used to create a document instead of
     * the default `parse` from `graphql-js`.
     */ customParseFn ? : ( source : Source ) => DocumentNode ; /**
     * `formatError` is deprecated and replaced by `customFormatErrorFn`. It will
     *  be removed in version 1.0.0.
     */ formatError ? : ( error : GraphQLError ) => GraphQLFormattedError ; /**
     * An optional function for adding additional metadata to the GraphQL response
     * as a key-value object. The result will be added to "extensions" field in
     * the resulting JSON. This is often a useful place to add development time
     * info such as the runtime of a query or the amount of resources consumed.
     *
     * Information about the request is provided to be used.
     *
     * This function may be async.
     */ extensions ? : ( info : RequestInfo ) => MaybePromise < undefined | { [ key ] : unknown ; } > ; /**
     * A boolean to optionally enable GraphiQL mode.
     * Alternatively, instead of `true` you can pass in an options object.
     */ graphiql ? : boolean | GraphiQLOptions ; /**
     * A resolver function to use when one is not provided by the schema.
     * If not provided, the default field resolver is used (which looks for a
     * value or method on the source value with the field's name).
     */ fieldResolver ? : GraphQLFieldResolver < unknown , unknown > ; /**
     * A type resolver function to use when none is provided by the schema.
     * If not provided, the default type resolver is used (which looks for a
     * `__typename` field or alternatively calls the `isTypeOf` method).
     */ typeResolver ? : GraphQLTypeResolver < unknown , unknown > ; } /**
 * All information about a GraphQL request.
 */ export interface RequestInfo { /**
     * The parsed GraphQL document.
     */ document : DocumentNode ; /**
     * The variable values used at runtime.
     */ variables : { readonly [ name ] : unknown ; } | null ; /**
     * The (optional) operation name requested.
     */ operationName : string | null ; /**
     * The result of executing the operation.
     */ result : FormattedExecutionResult ; /**
     * A value to pass as the context to the graphql() function.
     */ context ? : unknown ; } type Middleware = ( request : Request , response : Response ) => Promise < void > ; /**
 * Middleware for express; takes an options object or function as input to
 * configure behavior, and returns an express middleware.
 */ export function graphqlHTTP ( options ) { devAssertIsNonNullable ( options , 'GraphQL middleware requires options.' ) ; return async function graphqlMiddleware ( request , response ) : Promise < void > { let params ; let showGraphiQL = false ; let graphiqlOptions ; let formatErrorFn = formatError ; let pretty = false ; let result ; try { try { params = await getGraphQLParams ( request ) ; } catch ( error ) { const optionsData = await resolveOptions ( ) ; pretty = optionsData . pretty ?? false ; formatErrorFn = optionsData . customFormatErrorFn ?? optionsData . formatError ?? formatErrorFn ; throw error ; } const optionsData = await resolveOptions ( params ) ; const schema = optionsData . schema ; const rootValue = optionsData . rootValue ; const validationRules = optionsData . validationRules ?? [ ] ; const fieldResolver = optionsData . fieldResolver ; const typeResolver = optionsData . typeResolver ; const graphiql = optionsData . graphiql ?? false ; const extensionsFn = optionsData . extensions ; const context = optionsData . context ?? request ; const parseFn = optionsData . customParseFn ?? parse ; const executeFn = optionsData . customExecuteFn ?? execute ; const validateFn = optionsData . customValidateFn ?? validate ; pretty = optionsData . pretty ?? false ; formatErrorFn = optionsData . customFormatErrorFn ?? optionsData . formatError ?? formatErrorFn ; devAssertIsObject ( schema , 'GraphQL middleware options must contain a schema.' ) ; if ( request . method !== 'GET' && request . method !== 'POST' ) { throw httpError ( 405 , 'GraphQL only supports GET and POST requests.' , { headers : { Allow : 'GET, POST' } , } ) ; } const { query , variables , operationName } = params ; showGraphiQL = canDisplayGraphiQL ( request , params ) && graphiql !== false ; if ( typeof graphiql !== 'boolean' ) { graphiqlOptions = graphiql ; } if ( query == null ) { if ( showGraphiQL ) { return respondWithGraphiQL ( response , graphiqlOptions ) ; } throw httpError ( 400 , 'Must provide query string.' ) ; } const schemaValidationErrors = validateSchema ( schema ) ; if ( schemaValidationErrors . length > 0 ) { throw httpError ( 500 , 'GraphQL schema validation error.' , { graphqlErrors : schemaValidationErrors , } ) ; } let documentAST ; try { documentAST = parseFn ( new Source ( query , 'GraphQL request' ) ) ; } catch ( syntaxError ) { throw httpError ( 400 , 'GraphQL syntax error.' , { graphqlErrors : [ syntaxError ] , } ) ; } const validationErrors = validateFn ( schema , documentAST , [ ... specifiedRules , ... validationRules , ] ) ; if ( validationErrors . length > 0 ) { throw httpError ( 400 , 'GraphQL validation error.' , { graphqlErrors : validationErrors , } ) ; } if ( request . method === 'GET' ) { const operationAST = getOperationAST ( documentAST , operationName ) ; if ( operationAST && operationAST . operation !== 'query' ) { if ( showGraphiQL ) { return respondWithGraphiQL ( response , graphiqlOptions , params ) ; } throw httpError ( 405 , `Can only perform a ${ operationAST . operation } operation from a POST request.` , { headers : { Allow : 'POST' } } ) ; } } try { result = await executeFn ( { schema , document : documentAST , rootValue , contextValue : context , variableValues : variables , operationName , fieldResolver , typeResolver , } ) ; } catch ( contextError ) { throw httpError ( 400 , 'GraphQL execution context error.' , { graphqlErrors : [ contextError ] , } ) ; } if ( extensionsFn ) { const extensions = await extensionsFn ( { document : documentAST , variables , operationName , result , context , } ) ; if ( extensions != null ) { result = { ... result , extensions } ; } } } catch ( rawError ) { const error = httpError ( 500 , rawError instanceof Error ? rawError : String ( rawError ) ) ; response . statusCode = error . status ; const { headers } = error ; if ( headers != null ) { for ( const [ key , value ] of Object . entries ( headers ) ) { response . setHeader ( key , String ( value ) ) ; } } if ( error . graphqlErrors == null ) { const graphqlError = new GraphQLError ( error . message , undefined , undefined , undefined , undefined , error ) ; result = { data : undefined , errors : [ graphqlError ] } ; } else { result = { data : undefined , errors : error . graphqlErrors } ; } } if ( response . statusCode === 200 && result . data == null ) { response . statusCode = 500 ; } const formattedResult = { ... result , errors : result . errors ?. map ( formatErrorFn ) , } ; if ( showGraphiQL ) { return respondWithGraphiQL ( response , graphiqlOptions , params , formattedResult ) ; } if ( ! pretty && typeof response . json === 'function' ) { response . json ( formattedResult ) ; } else { const payload = JSON . stringify ( formattedResult , null , pretty ? 2 : 0 ) ; sendResponse ( response , 'application/json' , payload ) ; } async function resolveOptions ( requestParams ? ) { const optionsResult = await Promise . resolve ( typeof options === 'function' ? options ( request , response , requestParams ) : options ) ; devAssertIsObject ( optionsResult , 'GraphQL middleware option function must return an options object or a promise which will be resolved to an options object.' ) ; if ( optionsResult . formatError ) { console . warn ( '`formatError` is deprecated and replaced by `customFormatErrorFn`. It will be removed in version 1.0.0.' ) ; } return optionsResult ; } } ; } function respondWithGraphiQL ( response , options ? , params ? , result ? ) { const data = { query : params ?. query , variables : params ?. variables , operationName : params ?. operationName , result , } ; const payload = renderGraphiQL ( data , options ) ; return sendResponse ( response , 'text/html' , payload ) ; } export interface GraphQLParams { query : string | null ; variables : { readonly [ name ] : unknown ; } | null ; operationName : string | null ; raw : boolean ; } /**
 * Provided a "Request" provided by express or connect (typically a node style
 * HTTPClientRequest), Promise the GraphQL request parameters.
 */ export async function getGraphQLParams ( request ) { const urlData = new URLSearchParams ( request . url . split ( '?' ) [ 1 ] ) ; const bodyData = await parseBody ( request ) ; let query = urlData . get ( 'query' ) ?? ( bodyData . query as string | null ) ; if ( typeof query !== 'string' ) { query = null ; } let variables = ( urlData . get ( 'variables' ) ?? bodyData . variables ) as { readonly [ name ] : unknown ; } | null ; if ( typeof variables === 'string' ) { try { variables = JSON . parse ( variables ) ; } catch { throw httpError ( 400 , 'Variables are invalid JSON.' ) ; } } else if ( typeof variables !== 'object' ) { variables = null ; } let operationName = urlData . get ( 'operationName' ) ?? ( bodyData . operationName as string | null ) ; if ( typeof operationName !== 'string' ) { operationName = null ; } const raw = urlData . get ( 'raw' ) != null || bodyData . raw !== undefined ; return { query , variables , operationName , raw } ; } /**
 * Helper function to determine if GraphiQL can be displayed.
 */ function canDisplayGraphiQL ( request , params ) { return ! params . raw && accepts ( request ) . types ( [ 'json' , 'html' ] ) === 'html' ; } /**
 * Helper function for sending a response using only the core Node server APIs.
 */ function sendResponse ( response , type , data ) { const chunk = Buffer . from ( data , 'utf8' ) ; response . setHeader ( 'Content-Type' , type + '; charset=utf-8' ) ; response . setHeader ( 'Content-Length' , String ( chunk . length ) ) ; response . end ( chunk ) ; } function devAssertIsObject ( value , message ) { devAssert ( value != null && typeof value === 'object' , message ) ; } function devAssertIsNonNullable ( value , message ) { devAssert ( value != null , message ) ; } function devAssert ( condition , message ) { const booleanCondition = Boolean ( condition ) ; if ( ! booleanCondition ) { throw new TypeError ( message ) ; } }