graphql-http
Version:
Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.
336 lines (335 loc) • 13.7 kB
TypeScript
/**
*
* handler
*
*/
import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, ValidationRule, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError } from 'graphql';
import { RequestParams } from './common';
/**
* The incoming request headers the implementing server should provide.
*
* @category Server
*/
export type RequestHeaders = {
/**
* Always an array in Node. Duplicates are added to it.
* Not necessarily true for other environments.
*/
'set-cookie'?: string | string[] | undefined;
[key: string]: string | string[] | undefined;
} | {
get: (key: string) => string | null;
};
/**
* Server agnostic request interface containing the raw request
* which is server dependant.
*
* @category Server
*/
export interface Request<Raw, Context> {
readonly method: string;
readonly url: string;
readonly headers: RequestHeaders;
/**
* Parsed request body or a parser function.
*
* If the provided function throws, the error message "Unparsable JSON body" will
* be in the erroneous response.
*/
readonly body: string | Record<string, unknown> | null | (() => string | Record<string, unknown> | null | Promise<string | Record<string, unknown> | null>);
/**
* The raw request itself from the implementing server.
*
* For example: `express.Request` when using Express, or maybe
* `http.IncomingMessage` when just using Node with `http.createServer`.
*/
readonly raw: Raw;
/**
* Context value about the incoming request, you're free to pass any information here.
*/
readonly context: Context;
}
/**
* The response headers that get returned from graphql-http.
*
* @category Server
*/
export type ResponseHeaders = {
accept?: string;
allow?: string;
'content-type'?: string;
} & Record<string, string>;
/**
* Server agnostic response body returned from `graphql-http` needing
* to be coerced to the server implementation in use.
*
* @category Server
*/
export type ResponseBody = string;
/**
* Server agnostic response options (ex. status and headers) returned from
* `graphql-http` needing to be coerced to the server implementation in use.
*
* @category Server
*/
export interface ResponseInit {
readonly status: number;
readonly statusText: string;
readonly headers?: ResponseHeaders;
}
/**
* Server agnostic response returned from `graphql-http` containing the
* body and init options needing to be coerced to the server implementation in use.
*
* @category Server
*/
export type Response = readonly [body: ResponseBody | null, init: ResponseInit];
/**
* A concrete GraphQL execution context value type.
*
* Mainly used because TypeScript collapses unions
* with `any` or `unknown` to `any` or `unknown`. So,
* we use a custom type to allow definitions such as
* the `context` server option.
*
* @category Server
*/
export type OperationContext = Record<PropertyKey, unknown> | symbol | number | string | boolean | undefined | null;
/**
* The (GraphQL) error formatter function.
*
* @category Server
*/
export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError | Error;
/**
* The request parser for an incoming GraphQL request in the handler.
*
* It should parse and validate the request itself, including the request method
* and the content-type of the body.
*
* In case you are extending the server to handle more request types, this is the
* perfect place to do so.
*
* If an error is thrown, it will be formatted using the provided {@link FormatError}
* and handled following the spec to be gracefully reported to the client.
*
* Throwing an instance of `Error` will _always_ have the client respond with a `400: Bad Request`
* and the error's message in the response body; however, if an instance of `GraphQLError` is thrown,
* it will be reported depending on the accepted content-type.
*
* If you return nothing, the default parser will be used instead.
*
* @category Server
*/
export type ParseRequestParams<RequestRaw = unknown, RequestContext = unknown> = (req: Request<RequestRaw, RequestContext>) => Promise<RequestParams | Response | void> | RequestParams | Response | void;
/**
* The GraphQL over HTTP spec compliant request parser for an incoming GraphQL request.
* It parses and validates the request itself, including the request method and the
* content-type of the body.
*
* If the HTTP request itself is invalid or malformed, the function will return an
* appropriate {@link Response}.
*
* If the HTTP request is valid, but is not a well-formatted GraphQL request, the
* function will throw an error and it is up to the user to handle and respond as
* they see fit.
*
* @category Server
*/
export declare function parseRequestParams<RequestRaw = unknown, RequestContext = unknown>(req: Request<RequestRaw, RequestContext>): Promise<Response | RequestParams>;
/** @category Server */
export type OperationArgs<Context extends OperationContext = undefined> = ExecutionArgs & {
contextValue?: Context;
};
/** @category Server */
export interface HandlerOptions<RequestRaw = unknown, RequestContext = unknown, Context extends OperationContext = undefined> {
/**
* The GraphQL schema on which the operations will
* be executed and validated against.
*
* If a function is provided, it will be called on every
* operation request allowing you to manipulate schema
* dynamically.
*
* If the schema is left undefined, you're trusted to
* provide one in the returned `ExecutionArgs` from the
* `onSubscribe` callback.
*
* If you want to respond to the client with a custom status and/or body,
* you should do by returning a `Request` argument which will stop
* further execution.
*/
schema?: GraphQLSchema | ((req: Request<RequestRaw, RequestContext>, args: Omit<OperationArgs<Context>, 'schema'>) => Promise<GraphQLSchema | Response> | GraphQLSchema | Response);
/**
* A value which is provided to every resolver and holds
* important contextual information like the currently
* logged in user, or access to a database.
*/
context?: Context | ((req: Request<RequestRaw, RequestContext>, params: RequestParams) => Promise<Context | Response> | Context | Response);
/**
* A custom GraphQL validate function allowing you to apply your
* own validation rules.
*
* Will not be used when implementing a custom `onSubscribe`.
*/
validate?: typeof graphqlValidate;
/**
* The validation rules for running GraphQL validate.
*
* When providing an array, the rules will be APPENDED to the default
* `specifiedRules` array provided by the graphql-js module.
*
* Alternatively, providing a function instead will OVERWRITE the defaults
* and use exclusively the rules returned by the function. The third (last)
* argument of the function are the default `specifiedRules` array provided
* by the graphql-js module, you're free to prepend/append the defaults to
* your rule set, or omit them altogether.
*/
validationRules?: readonly ValidationRule[] | ((req: Request<RequestRaw, RequestContext>, args: OperationArgs<Context>, specifiedRules: readonly ValidationRule[]) => Promise<readonly ValidationRule[]> | readonly ValidationRule[]);
/**
* Is the `execute` function from GraphQL which is
* used to execute the query and mutation operations.
*/
execute?: typeof graphqlExecute;
/**
* GraphQL parse function allowing you to apply a custom parser.
*/
parse?: typeof graphqlParse;
/**
* GraphQL operation AST getter used for detecting the operation type.
*/
getOperationAST?: typeof graphqlGetOperationAST;
/**
* The GraphQL root value or resolvers to go alongside the execution.
* Learn more about them here: https://graphql.org/learn/execution/#root-fields-resolvers.
*
* If you return from `onSubscribe`, and the returned value is
* missing the `rootValue` field, the relevant operation root
* will be used instead.
*/
rootValue?: unknown;
/**
* The subscribe callback executed right after processing the request
* before proceeding with the GraphQL operation execution.
*
* If you return `ExecutionResult` from the callback, it will be used
* directly for responding to the request. Useful for implementing a response
* cache.
*
* If you return `ExecutionArgs` from the callback, it will be used instead of
* trying to build one internally. In this case, you are responsible for providing
* a ready set of arguments which will be directly plugged in the operation execution.
*
* You *must* validate the `ExecutionArgs` yourself if returning them.
*
* If you return an array of `GraphQLError` from the callback, they will be reported
* to the client while complying with the spec.
*
* Omitting the fields `contextValue` from the returned `ExecutionArgs` will use the
* provided `context` option, if available.
*
* Useful for preparing the execution arguments following a custom logic. A typical
* use-case is persisted queries. You can identify the query from the request parameters
* and supply the appropriate GraphQL operation execution arguments.
*
* If you want to respond to the client with a custom status and/or body,
* you should do by returning a `Request` argument which will stop
* further execution.
*/
onSubscribe?: (req: Request<RequestRaw, RequestContext>, params: RequestParams) => Promise<ExecutionResult | OperationArgs<Context> | readonly GraphQLError[] | Response | void> | ExecutionResult | OperationArgs<Context> | readonly GraphQLError[] | Response | void;
/**
* Executed after the operation call resolves.
*
* The `OperationResult` argument is the result of operation
* execution. It can be an iterator or already a value.
*
* Use this callback to listen for GraphQL operations and
* execution result manipulation.
*
* If you want to respond to the client with a custom status and/or body,
* you should do by returning a `Request` argument which will stop
* further execution.
*/
onOperation?: (req: Request<RequestRaw, RequestContext>, args: OperationArgs<Context>, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
/**
* Format handled errors to your satisfaction. Either GraphQL errors
* or safe request processing errors are meant by "handled errors".
*
* If multiple errors have occurred, all of them will be mapped using
* this formatter.
*/
formatError?: FormatError;
/**
* The request parser for an incoming GraphQL request.
*
* Read more about it in {@link ParseRequestParams}.
*/
parseRequestParams?: ParseRequestParams<RequestRaw, RequestContext>;
}
/**
* The ready-to-use handler. Simply plug it in your favorite HTTP framework
* and enjoy.
*
* Errors thrown from **any** of the provided options or callbacks (or even due to
* library misuse or potential bugs) will reject the handler's promise. They are
* considered internal errors and you should take care of them accordingly.
*
* @category Server
*/
export type Handler<RequestRaw = unknown, RequestContext = unknown> = (req: Request<RequestRaw, RequestContext>) => Promise<Response>;
/**
* Makes a GraphQL over HTTP spec compliant server handler. The handler can
* be used with your favorite server library.
*
* Beware that the handler resolves only after the whole operation completes.
*
* Errors thrown from **any** of the provided options or callbacks (or even due to
* library misuse or potential bugs) will reject the handler's promise. They are
* considered internal errors and you should take care of them accordingly.
*
* For production environments, its recommended not to transmit the exact internal
* error details to the client, but instead report to an error logging tool or simply
* the console.
*
* Simple example usage with Node:
*
* ```js
* import http from 'http';
* import { createHandler } from 'graphql-http';
* import { schema } from './my-graphql-schema';
*
* // Create the GraphQL over HTTP handler
* const handler = createHandler({ schema });
*
* // Create a HTTP server using the handler on `/graphql`
* const server = http.createServer(async (req, res) => {
* if (!req.url.startsWith('/graphql')) {
* return res.writeHead(404).end();
* }
*
* try {
* const [body, init] = await handler({
* url: req.url,
* method: req.method,
* headers: req.headers,
* body: () => new Promise((resolve) => {
* let body = '';
* req.on('data', (chunk) => (body += chunk));
* req.on('end', () => resolve(body));
* }),
* raw: req,
* });
* res.writeHead(init.status, init.statusText, init.headers).end(body);
* } catch (err) {
* // BEWARE not to transmit the exact internal error message in production environments
* res.writeHead(500).end(err.message);
* }
* });
*
* server.listen(4000);
* console.log('Listening to port 4000');
* ```
*
* @category Server
*/
export declare function createHandler<RequestRaw = unknown, RequestContext = unknown, Context extends OperationContext = undefined>(options: HandlerOptions<RequestRaw, RequestContext, Context>): Handler<RequestRaw, RequestContext>;