Socket
Socket
Sign inDemoInstall

graphql-http

Package Overview
Dependencies
1
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.19.0 to 1.20.0

4

lib/client.d.ts

@@ -79,3 +79,3 @@ /**

*
* Useful for retrying requests that failed because the service is temporarely unavailable.
* Useful for retrying requests that failed because the service is temporarily unavailable.
*

@@ -139,3 +139,3 @@ * `retries` argument counts actual retries, so it will begin with

/**
* The underlyig response thats considered an error.
* The underlying response thats considered an error.
*

@@ -142,0 +142,0 @@ * Will be undefined when no response is received,

@@ -14,6 +14,6 @@ /**

export interface RequestParams {
operationName?: string | undefined;
operationName?: string | null | undefined;
query: string;
variables?: Record<string, unknown> | undefined;
extensions?: Record<string, unknown> | undefined;
variables?: Record<string, unknown> | null | undefined;
extensions?: Record<string, unknown> | null | undefined;
}

@@ -28,3 +28,3 @@ /**

next(value: T): void;
/** An error that has occured. This function "closes" the sink. */
/** An error that has occurred. This function "closes" the sink. */
error(error: unknown): void;

@@ -31,0 +31,0 @@ /** The sink has completed. This function "closes" the sink. */

@@ -88,11 +88,5 @@ /**

/**
* Checks whether the passed value is the `graphql-http` server agnostic response.
*
* @category Server
*/
export declare function isResponse(val: unknown): val is Response;
/**
* A concrete GraphQL execution context value type.
*
* Mainly used because TypeScript collapes unions
* Mainly used because TypeScript collapses unions
* with `any` or `unknown` to `any` or `unknown`. So,

@@ -111,2 +105,21 @@ * we use a custom type to allow definitions such as

export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError | Error;
/**
* The 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.
*
* 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;
/** @category Server */

@@ -228,11 +241,17 @@ export type OperationArgs<Context extends OperationContext = undefined> = ExecutionArgs & {

* Format handled errors to your satisfaction. Either GraphQL errors
* or safe request processing errors are meant by "handleded errors".
* or safe request processing errors are meant by "handled errors".
*
* If multiple errors have occured, all of them will be mapped using
* 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 favourite HTTP framework
* The ready-to-use handler. Simply plug it in your favorite HTTP framework
* and enjoy.

@@ -249,3 +268,3 @@ *

* Makes a GraphQL over HTTP spec compliant server handler. The handler can
* be used with your favourite server library.
* be used with your favorite server library.
*

@@ -304,28 +323,1 @@ * Beware that the handler resolves only after the whole operation completes.

export declare function createHandler<RequestRaw = unknown, RequestContext = unknown, Context extends OperationContext = undefined>(options: HandlerOptions<RequestRaw, RequestContext, Context>): Handler<RequestRaw, RequestContext>;
/**
* Request's Media-Type that the server accepts.
*
* @category Server
*/
export type AcceptableMediaType = 'application/graphql-response+json' | 'application/json';
/**
* Inspects the request and detects the appropriate/acceptable Media-Type
* looking at the `Accept` header while complying with the GraphQL over HTTP spec.
*
* @category Server
*/
export declare function getAcceptableMediaType(acceptHeader: string | null | undefined): AcceptableMediaType | null;
/**
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
*
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
*
* If the first argument is (an array of) `GraphQLError`, or an `ExecutionResult` without the `data` field, it will be treated
* the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP spec.
*
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
* error will be present in the `ExecutionResult` style.
*
* @category Server
*/
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError> | Readonly<Error>, acceptedMediaType: AcceptableMediaType, formatError: FormatError): Response;

@@ -8,10 +8,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.makeResponse = exports.getAcceptableMediaType = exports.createHandler = exports.isResponse = void 0;
exports.createHandler = void 0;
const graphql_1 = require("graphql");
const utils_1 = require("./utils");
/**
* Checks whether the passed value is the `graphql-http` server agnostic response.
*
* @category Server
*/
/** Checks whether the passed value is the `graphql-http` server agnostic response. */
function isResponse(val) {

@@ -23,6 +19,5 @@ // TODO: make sure the contents of init match ResponseInit

}
exports.isResponse = isResponse;
/**
* Makes a GraphQL over HTTP spec compliant server handler. The handler can
* be used with your favourite server library.
* be used with your favorite server library.
*

@@ -81,19 +76,28 @@ * Beware that the handler resolves only after the whole operation completes.

function createHandler(options) {
const { schema, context, validate = graphql_1.validate, validationRules = [], execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, } = options;
const { schema, context, validate = graphql_1.validate, validationRules = [], execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, parseRequestParams = defaultParseRequestParams, } = options;
return async function handler(req) {
var _a, _b;
const method = req.method;
if (method !== 'GET' && method !== 'POST') {
return [
null,
{
status: 405,
statusText: 'Method Not Allowed',
headers: {
allow: 'GET, POST',
},
},
];
let acceptedMediaType = null;
const accepts = (getHeader(req, 'accept') || '*/*')
.replace(/\s/g, '')
.toLowerCase()
.split(',');
for (const accept of accepts) {
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
// TODO: handle the weight parameter "q"
const [mediaType, ...params] = accept.split(';');
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
if (mediaType === 'application/graphql-response+json' &&
charset === 'charset=utf8') {
acceptedMediaType = 'application/graphql-response+json';
break;
}
// application/json should be the default until watershed
if ((mediaType === 'application/json' ||
mediaType === 'application/*' ||
mediaType === '*/*') &&
charset === 'charset=utf8') {
acceptedMediaType = 'application/json';
break;
}
}
const acceptedMediaType = getAcceptableMediaType(getHeader(req, 'accept'));
if (!acceptedMediaType) {

@@ -111,85 +115,10 @@ return [

}
// TODO: should graphql-http care about content-encoding? I'd say unzipping should happen before handler is reached
const [mediaType, charset = 'charset=utf-8', // utf-8 is assumed when not specified. this parameter is either "charset" or "boundary" (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
] = (getHeader(req, 'content-type') || '')
.replace(/\s/g, '')
.toLowerCase()
.split(';');
let params;
try {
const partParams = {};
switch (true) {
case method === 'GET': {
// TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
try {
const [, search] = req.url.split('?');
const searchParams = new URLSearchParams(search);
partParams.operationName =
(_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
const variables = searchParams.get('variables');
if (variables)
partParams.variables = JSON.parse(variables);
const extensions = searchParams.get('extensions');
if (extensions)
partParams.extensions = JSON.parse(extensions);
}
catch (_c) {
throw new Error('Unparsable URL');
}
break;
}
case method === 'POST' &&
mediaType === 'application/json' &&
charset === 'charset=utf-8':
{
if (!req.body) {
throw new Error('Missing body');
}
let data;
try {
const body = typeof req.body === 'function' ? await req.body() : req.body;
data = typeof body === 'string' ? JSON.parse(body) : body;
}
catch (err) {
throw new Error('Unparsable JSON body');
}
if (!(0, utils_1.isObject)(data)) {
throw new Error('JSON body must be an object');
}
partParams.operationName = data.operationName;
partParams.query = data.query;
partParams.variables = data.variables;
partParams.extensions = data.extensions;
break;
}
default: // graphql-http doesnt support any other content type
return [
null,
{
status: 415,
statusText: 'Unsupported Media Type',
},
];
}
if (partParams.query == null)
throw new Error('Missing query');
if (typeof partParams.query !== 'string')
throw new Error('Invalid query');
if (partParams.variables != null &&
(typeof partParams.variables !== 'object' ||
Array.isArray(partParams.variables))) {
throw new Error('Invalid variables');
}
if (partParams.operationName != null &&
typeof partParams.operationName !== 'string') {
throw new Error('Invalid operationName');
}
if (partParams.extensions != null &&
(typeof partParams.extensions !== 'object' ||
Array.isArray(partParams.extensions))) {
throw new Error('Invalid extensions');
}
// request parameters are checked and now complete
params = partParams;
let paramsOrRes = await parseRequestParams(req);
if (!paramsOrRes)
paramsOrRes = await defaultParseRequestParams(req);
if (isResponse(paramsOrRes))
return paramsOrRes;
params = paramsOrRes;
}

@@ -256,3 +185,3 @@ catch (err) {

}
catch (_d) {
catch (_a) {
return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType, formatError);

@@ -265,3 +194,3 @@ }

// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
if (operation === 'mutation' && method === 'GET') {
if (operation === 'mutation' && req.method === 'GET') {
return [

@@ -303,34 +232,105 @@ JSON.stringify({

/**
* Inspects the request and detects the appropriate/acceptable Media-Type
* looking at the `Accept` header while complying with the GraphQL over HTTP spec.
* The default request params parser. Used when no custom one is provided or if it
* returns nothing.
*
* @category Server
* Read more about it in {@link ParseRequestParams}.
*
* TODO: should graphql-http itself care about content-encoding? I'd say unzipping should happen before handler is reached
*/
function getAcceptableMediaType(acceptHeader) {
let acceptedMediaType = null;
const accepts = (acceptHeader || '*/*')
async function defaultParseRequestParams(req) {
var _a, _b;
const method = req.method;
if (method !== 'GET' && method !== 'POST') {
return [
null,
{
status: 405,
statusText: 'Method Not Allowed',
headers: {
allow: 'GET, POST',
},
},
];
}
const [mediaType, charset = 'charset=utf-8', // utf-8 is assumed when not specified. this parameter is either "charset" or "boundary" (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
] = (getHeader(req, 'content-type') || '')
.replace(/\s/g, '')
.toLowerCase()
.split(',');
for (const accept of accepts) {
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
// TODO: handle the weight parameter "q"
const [mediaType, ...params] = accept.split(';');
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
if (mediaType === 'application/graphql-response+json' &&
charset === 'charset=utf8') {
acceptedMediaType = 'application/graphql-response+json';
.split(';');
const partParams = {};
switch (true) {
case method === 'GET': {
// TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
try {
const [, search] = req.url.split('?');
const searchParams = new URLSearchParams(search);
partParams.operationName =
(_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
const variables = searchParams.get('variables');
if (variables)
partParams.variables = JSON.parse(variables);
const extensions = searchParams.get('extensions');
if (extensions)
partParams.extensions = JSON.parse(extensions);
}
catch (_c) {
throw new Error('Unparsable URL');
}
break;
}
if ((mediaType === 'application/json' ||
mediaType === 'application/*' ||
mediaType === '*/*') &&
charset === 'charset=utf8') {
acceptedMediaType = 'application/json';
break;
}
case method === 'POST' &&
mediaType === 'application/json' &&
charset === 'charset=utf-8':
{
if (!req.body) {
throw new Error('Missing body');
}
let data;
try {
const body = typeof req.body === 'function' ? await req.body() : req.body;
data = typeof body === 'string' ? JSON.parse(body) : body;
}
catch (err) {
throw new Error('Unparsable JSON body');
}
if (!(0, utils_1.isObject)(data)) {
throw new Error('JSON body must be an object');
}
partParams.operationName = data.operationName;
partParams.query = data.query;
partParams.variables = data.variables;
partParams.extensions = data.extensions;
break;
}
default: // graphql-http doesnt support any other content type
return [
null,
{
status: 415,
statusText: 'Unsupported Media Type',
},
];
}
return acceptedMediaType;
if (partParams.query == null)
throw new Error('Missing query');
if (typeof partParams.query !== 'string')
throw new Error('Invalid query');
if (partParams.variables != null &&
(typeof partParams.variables !== 'object' ||
Array.isArray(partParams.variables))) {
throw new Error('Invalid variables');
}
if (partParams.operationName != null &&
typeof partParams.operationName !== 'string') {
throw new Error('Invalid operationName');
}
if (partParams.extensions != null &&
(typeof partParams.extensions !== 'object' ||
Array.isArray(partParams.extensions))) {
throw new Error('Invalid extensions');
}
// request parameters are checked and now complete
return partParams;
}
exports.getAcceptableMediaType = getAcceptableMediaType;
/**

@@ -346,4 +346,2 @@ * Creates an appropriate GraphQL over HTTP response following the provided arguments.

* error will be present in the `ExecutionResult` style.
*
* @category Server
*/

@@ -402,3 +400,2 @@ function makeResponse(resultOrErrors, acceptedMediaType, formatError) {

}
exports.makeResponse = makeResponse;
function getHeader(req, key) {

@@ -405,0 +402,0 @@ if (typeof req.headers.get === 'function') {

@@ -51,3 +51,5 @@ "use strict";

/** @private */
function jsonErrorReplacer(_key, val) {
function jsonErrorReplacer(_key,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
val) {
if (val instanceof Error &&

@@ -54,0 +56,0 @@ // GraphQL errors implement their own stringer

{
"name": "graphql-http",
"version": "1.19.0",
"version": "1.20.0",
"description": "Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.",

@@ -27,3 +27,3 @@ "keywords": [

},
"packageManager": "yarn@3.6.0",
"packageManager": "yarn@3.6.1",
"main": "lib/index.js",

@@ -95,14 +95,14 @@ "module": "lib/index.mjs",

"scripts": {
"gendocs": "typedoc --options typedoc.js src/",
"lint:eslint": "eslint 'src'",
"lint:prettier": "prettier --check .",
"lint:fix": "yarn lint:eslint --fix && yarn lint:prettier --write",
"lint": "yarn lint:eslint && yarn lint:prettier",
"type-check": "tsc --noEmit",
"check:format": "prettier --check .",
"format": "yarn check:format --write",
"check:lint": "eslint 'src'",
"check:spell": "cspell --gitignore **/*.md",
"check:type": "tsc --noEmit",
"test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest",
"build:esm": "tsc -b tsconfig.esm.json && node scripts/esm-post-process.mjs",
"build:esm": "tsc -b tsconfig.esm.json && tsx scripts/esm-post-process.ts",
"build:cjs": "tsc -b tsconfig.cjs.json",
"build:umd": "rollup --configPlugin typescript --config rollup.config.ts",
"build": "yarn build:esm && yarn build:cjs && yarn build:umd",
"release": "semantic-release"
"release": "semantic-release",
"gendocs": "typedoc --options typedoc.js src/"
},

@@ -116,3 +116,3 @@ "workspaces": [

"devDependencies": {
"@babel/core": "^7.22.1",
"@babel/core": "^7.22.8",
"@babel/plugin-proposal-class-properties": "^7.18.6",

@@ -122,9 +122,10 @@ "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",

"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/preset-env": "^7.22.4",
"@babel/preset-typescript": "^7.21.5",
"@babel/preset-env": "^7.22.7",
"@babel/preset-typescript": "^7.22.5",
"@cspell/cspell-types": "^6.31.1",
"@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-typescript": "^11.1.1",
"@rollup/plugin-typescript": "^11.1.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/eslint": "^8.40.0",
"@types/eslint": "^8.44.0",
"@types/express": "^4.17.17",

@@ -134,18 +135,19 @@ "@types/glob": "^8.1.0",

"@types/jest": "^29.5.2",
"@types/k6": "^0.44.2",
"@types/k6": "^0.45.0",
"@types/koa": "^2.13.6",
"@types/koa-mount": "^4.0.2",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"@whatwg-node/fetch": "^0.9.2",
"babel-jest": "^29.5.0",
"eslint": "^8.42.0",
"@typescript-eslint/eslint-plugin": "^5.61.0",
"@typescript-eslint/parser": "^5.61.0",
"@whatwg-node/fetch": "^0.9.7",
"babel-jest": "^29.6.1",
"cspell": "^6.31.1",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"express": "^4.18.2",
"fastify": "^4.17.0",
"glob": "^10.2.6",
"graphql": "^16.6.0",
"fastify": "^4.19.2",
"glob": "^10.3.2",
"graphql": "^16.7.1",
"html-validator": "^6.0.1",
"jest": "^29.5.0",
"jest-jasmine2": "^29.5.0",
"jest": "^29.6.1",
"jest-jasmine2": "^29.6.1",
"koa": "^2.14.2",

@@ -155,11 +157,12 @@ "koa-mount": "^4.0.0",

"prettier": "^2.8.8",
"rollup": "^3.23.1",
"rollup": "^3.26.2",
"rollup-plugin-gzip": "^3.1.0",
"semantic-release": "^21.0.3",
"tslib": "^2.5.3",
"semantic-release": "^21.0.7",
"tslib": "^2.6.0",
"tsx": "^3.12.7",
"typedoc": "^0.24.8",
"typedoc-plugin-markdown": "^3.15.3",
"typescript": "^5.1.3",
"typescript": "^5.1.6",
"uWebSockets.js": "uNetworking/uWebSockets.js#v20.30.0"
}
}

@@ -64,3 +64,3 @@ <div align="center">

// Create a HTTP server using the listner on `/graphql`
// Create a HTTP server using the listener on `/graphql`
const server = http.createServer((req, res) => {

@@ -564,4 +564,4 @@ if (req.url.startsWith('/graphql')) {

<details id="migrating-express-grpahql">
<summary><a href="#migrating-express-grpahql">🔗</a> Server handler migration from <a href="https://github.com/graphql/express-graphql">express-graphql</a></summary>
<details id="migrating-express-graphql">
<summary><a href="#migrating-express-graphql">🔗</a> Server handler migration from <a href="https://github.com/graphql/express-graphql">express-graphql</a></summary>

@@ -635,3 +635,3 @@ ```diff

},
// or static context by supplying the value direcly
// or static context by supplying the value directly
});

@@ -731,2 +731,74 @@ ```

<details id="graphql-upload-http">
<summary><a href="#graphql-upload-http">🔗</a> Server handler usage with <a href="https://github.com/jaydenseric/graphql-upload">graphql-upload</a> and <a href="https://nodejs.org/api/http.html">http</a></summary>
```js
import http from 'http';
import { createHandler } from 'graphql-http/lib/use/http';
import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload
import { schema } from './my-graphql';
const handler = createHandler({
schema,
async parseRequestParams(req) {
const params = await processRequest(req.raw, req.context.res);
if (Array.isArray(params)) {
throw new Error('Batching is not supported');
}
return {
...params,
// variables must be an object as per the GraphQL over HTTP spec
variables: Object(params.variables),
};
},
});
const server = http.createServer((req, res) => {
if (req.url.startsWith('/graphql')) {
handler(req, res);
} else {
res.writeHead(404).end();
}
});
server.listen(4000);
console.log('Listening to port 4000');
```
</details>
<details id="graphql-upload-express">
<summary><a href="#graphql-upload-express">🔗</a> Server handler usage with <a href="https://github.com/jaydenseric/graphql-upload">graphql-upload</a> and <a href="https://expressjs.com/">express</a></summary>
```js
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-http/lib/use/express';
import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload
import { schema } from './my-graphql';
const app = express();
app.all(
'/graphql',
createHandler({
schema,
async parseRequestParams(req) {
const params = await processRequest(req.raw, req.context.res);
if (Array.isArray(params)) {
throw new Error('Batching is not supported');
}
return {
...params,
// variables must be an object as per the GraphQL over HTTP spec
variables: Object(params.variables),
};
},
}),
);
app.listen({ port: 4000 });
console.log('Listening to port 4000');
```
</details>
<details id="audit-jest">

@@ -733,0 +805,0 @@ <summary><a href="#audit-jest">🔗</a> Audit for servers usage in <a href="https://jestjs.io">Jest</a> environment</summary>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc