graphql-http 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +13 -1
  2. package/lib/handler.d.mts +19 -2
  3. package/lib/handler.d.ts +19 -2
  4. package/lib/handler.js +145 -115
  5. package/lib/handler.mjs +137 -108
  6. package/lib/use/@netlify/functions.d.mts +14 -0
  7. package/lib/use/@netlify/functions.d.ts +14 -0
  8. package/lib/use/@netlify/functions.js +37 -0
  9. package/lib/use/@netlify/functions.mjs +33 -0
  10. package/lib/use/express.d.mts +41 -0
  11. package/lib/use/express.d.ts +41 -0
  12. package/lib/use/express.js +72 -19
  13. package/lib/use/express.mjs +71 -19
  14. package/lib/use/fastify.d.mts +42 -1
  15. package/lib/use/fastify.d.ts +42 -1
  16. package/lib/use/fastify.js +68 -11
  17. package/lib/use/fastify.mjs +67 -11
  18. package/lib/use/fetch.d.mts +50 -0
  19. package/lib/use/fetch.d.ts +50 -0
  20. package/lib/use/fetch.js +74 -9
  21. package/lib/use/fetch.mjs +73 -9
  22. package/lib/use/http.d.mts +44 -0
  23. package/lib/use/http.d.ts +44 -0
  24. package/lib/use/http.js +75 -13
  25. package/lib/use/http.mjs +74 -13
  26. package/lib/use/http2.d.mts +56 -0
  27. package/lib/use/http2.d.ts +56 -0
  28. package/lib/use/http2.js +93 -13
  29. package/lib/use/http2.mjs +92 -13
  30. package/lib/use/koa.d.mts +47 -1
  31. package/lib/use/koa.d.ts +47 -1
  32. package/lib/use/koa.js +84 -1
  33. package/lib/use/koa.mjs +83 -1
  34. package/lib/use/uWebSockets.d.mts +55 -0
  35. package/lib/use/uWebSockets.d.ts +55 -0
  36. package/lib/use/uWebSockets.js +109 -31
  37. package/lib/use/uWebSockets.mjs +108 -31
  38. package/lib/utils.d.mts +0 -7
  39. package/lib/utils.d.ts +0 -7
  40. package/lib/utils.js +1 -31
  41. package/lib/utils.mjs +0 -27
  42. package/package.json +28 -32
  43. package/umd/graphql-http-audits.js +2 -2
  44. package/umd/graphql-http-audits.min.js +1 -1
  45. package/umd/graphql-http-audits.min.js.gz +0 -0
  46. package/umd/graphql-http.js +3 -2
  47. package/umd/graphql-http.min.js +1 -1
  48. package/umd/graphql-http.min.js.gz +0 -0
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![GraphQL Conf 2023](/GraphQLConf-2023-Banner.png)](https://graphql.org/conf/)
2
+
1
3
  <div align="center">
2
4
  <br />
3
5
 
@@ -222,6 +224,16 @@ export default {
222
224
  };
223
225
  ```
224
226
 
227
+ ##### With [`Netlify Functions`](https://docs.netlify.com/functions/overview/)
228
+
229
+ ```js
230
+ import { createHandler } from 'graphql-http/lib/use/@netlify/functions'; // yarn add @netlify/functions
231
+ import { schema } from './previous-step';
232
+
233
+ // Create the GraphQL over HTTP native fetch handler
234
+ export const handler = createHandler({ schema });
235
+ ```
236
+
225
237
  #### Use the client
226
238
 
227
239
  ```js
@@ -487,7 +499,7 @@ const client = createClient({
487
499
  <summary><a href="#browser">🔗</a> Client usage in browser</summary>
488
500
 
489
501
  ```html
490
- <!DOCTYPE html>
502
+ <!doctype html>
491
503
  <html>
492
504
  <head>
493
505
  <meta charset="utf-8" />
package/lib/handler.d.mts CHANGED
@@ -102,8 +102,10 @@ export type OperationContext = Record<PropertyKey, unknown> | symbol | number |
102
102
  */
103
103
  export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError | Error;
104
104
  /**
105
- * The request parser for an incoming GraphQL request. It parses and validates the
106
- * request itself, including the request method and the content-type of the body.
105
+ * The request parser for an incoming GraphQL request in the handler.
106
+ *
107
+ * It should parse and validate the request itself, including the request method
108
+ * and the content-type of the body.
107
109
  *
108
110
  * In case you are extending the server to handle more request types, this is the
109
111
  * perfect place to do so.
@@ -120,6 +122,21 @@ export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError
120
122
  * @category Server
121
123
  */
122
124
  export type ParseRequestParams<RequestRaw = unknown, RequestContext = unknown> = (req: Request<RequestRaw, RequestContext>) => Promise<RequestParams | Response | void> | RequestParams | Response | void;
125
+ /**
126
+ * The GraphQL over HTTP spec compliant request parser for an incoming GraphQL request.
127
+ * It parses and validates the request itself, including the request method and the
128
+ * content-type of the body.
129
+ *
130
+ * If the HTTP request itself is invalid or malformed, the function will return an
131
+ * appropriate {@link Response}.
132
+ *
133
+ * If the HTTP request is valid, but is not a well-formatted GraphQL request, the
134
+ * function will throw an error and it is up to the user to handle and respond as
135
+ * they see fit.
136
+ *
137
+ * @category Server
138
+ */
139
+ export declare function parseRequestParams<RequestRaw = unknown, RequestContext = unknown>(req: Request<RequestRaw, RequestContext>): Promise<Response | RequestParams>;
123
140
  /** @category Server */
124
141
  export type OperationArgs<Context extends OperationContext = undefined> = ExecutionArgs & {
125
142
  contextValue?: Context;
package/lib/handler.d.ts CHANGED
@@ -102,8 +102,10 @@ export type OperationContext = Record<PropertyKey, unknown> | symbol | number |
102
102
  */
103
103
  export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError | Error;
104
104
  /**
105
- * The request parser for an incoming GraphQL request. It parses and validates the
106
- * request itself, including the request method and the content-type of the body.
105
+ * The request parser for an incoming GraphQL request in the handler.
106
+ *
107
+ * It should parse and validate the request itself, including the request method
108
+ * and the content-type of the body.
107
109
  *
108
110
  * In case you are extending the server to handle more request types, this is the
109
111
  * perfect place to do so.
@@ -120,6 +122,21 @@ export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError
120
122
  * @category Server
121
123
  */
122
124
  export type ParseRequestParams<RequestRaw = unknown, RequestContext = unknown> = (req: Request<RequestRaw, RequestContext>) => Promise<RequestParams | Response | void> | RequestParams | Response | void;
125
+ /**
126
+ * The GraphQL over HTTP spec compliant request parser for an incoming GraphQL request.
127
+ * It parses and validates the request itself, including the request method and the
128
+ * content-type of the body.
129
+ *
130
+ * If the HTTP request itself is invalid or malformed, the function will return an
131
+ * appropriate {@link Response}.
132
+ *
133
+ * If the HTTP request is valid, but is not a well-formatted GraphQL request, the
134
+ * function will throw an error and it is up to the user to handle and respond as
135
+ * they see fit.
136
+ *
137
+ * @category Server
138
+ */
139
+ export declare function parseRequestParams<RequestRaw = unknown, RequestContext = unknown>(req: Request<RequestRaw, RequestContext>): Promise<Response | RequestParams>;
123
140
  /** @category Server */
124
141
  export type OperationArgs<Context extends OperationContext = undefined> = ExecutionArgs & {
125
142
  contextValue?: Context;
package/lib/handler.js CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.createHandler = void 0;
8
+ exports.createHandler = exports.parseRequestParams = void 0;
9
9
  const graphql_1 = require("graphql");
10
10
  const utils_1 = require("./utils");
11
11
  /** Checks whether the passed value is the `graphql-http` server agnostic response. */
@@ -15,6 +15,117 @@ function isResponse(val) {
15
15
  (typeof val[0] === 'string' || val[0] === null) &&
16
16
  (0, utils_1.isObject)(val[1]));
17
17
  }
18
+ /**
19
+ * The GraphQL over HTTP spec compliant request parser for an incoming GraphQL request.
20
+ * It parses and validates the request itself, including the request method and the
21
+ * content-type of the body.
22
+ *
23
+ * If the HTTP request itself is invalid or malformed, the function will return an
24
+ * appropriate {@link Response}.
25
+ *
26
+ * If the HTTP request is valid, but is not a well-formatted GraphQL request, the
27
+ * function will throw an error and it is up to the user to handle and respond as
28
+ * they see fit.
29
+ *
30
+ * @category Server
31
+ */
32
+ async function parseRequestParams(req) {
33
+ var _a, _b;
34
+ const method = req.method;
35
+ if (method !== 'GET' && method !== 'POST') {
36
+ return [
37
+ null,
38
+ {
39
+ status: 405,
40
+ statusText: 'Method Not Allowed',
41
+ headers: {
42
+ allow: 'GET, POST',
43
+ },
44
+ },
45
+ ];
46
+ }
47
+ 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)
48
+ ] = (getHeader(req, 'content-type') || '')
49
+ .replace(/\s/g, '')
50
+ .toLowerCase()
51
+ .split(';');
52
+ const partParams = {};
53
+ switch (true) {
54
+ case method === 'GET': {
55
+ // TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
56
+ try {
57
+ const [, search] = req.url.split('?');
58
+ const searchParams = new URLSearchParams(search);
59
+ partParams.operationName =
60
+ (_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
61
+ partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
62
+ const variables = searchParams.get('variables');
63
+ if (variables)
64
+ partParams.variables = JSON.parse(variables);
65
+ const extensions = searchParams.get('extensions');
66
+ if (extensions)
67
+ partParams.extensions = JSON.parse(extensions);
68
+ }
69
+ catch (_c) {
70
+ throw new Error('Unparsable URL');
71
+ }
72
+ break;
73
+ }
74
+ case method === 'POST' &&
75
+ mediaType === 'application/json' &&
76
+ charset === 'charset=utf-8':
77
+ {
78
+ if (!req.body) {
79
+ throw new Error('Missing body');
80
+ }
81
+ let data;
82
+ try {
83
+ const body = typeof req.body === 'function' ? await req.body() : req.body;
84
+ data = typeof body === 'string' ? JSON.parse(body) : body;
85
+ }
86
+ catch (err) {
87
+ throw new Error('Unparsable JSON body');
88
+ }
89
+ if (!(0, utils_1.isObject)(data)) {
90
+ throw new Error('JSON body must be an object');
91
+ }
92
+ partParams.operationName = data.operationName;
93
+ partParams.query = data.query;
94
+ partParams.variables = data.variables;
95
+ partParams.extensions = data.extensions;
96
+ break;
97
+ }
98
+ default: // graphql-http doesnt support any other content type
99
+ return [
100
+ null,
101
+ {
102
+ status: 415,
103
+ statusText: 'Unsupported Media Type',
104
+ },
105
+ ];
106
+ }
107
+ if (partParams.query == null)
108
+ throw new Error('Missing query');
109
+ if (typeof partParams.query !== 'string')
110
+ throw new Error('Invalid query');
111
+ if (partParams.variables != null &&
112
+ (typeof partParams.variables !== 'object' ||
113
+ Array.isArray(partParams.variables))) {
114
+ throw new Error('Invalid variables');
115
+ }
116
+ if (partParams.operationName != null &&
117
+ typeof partParams.operationName !== 'string') {
118
+ throw new Error('Invalid operationName');
119
+ }
120
+ if (partParams.extensions != null &&
121
+ (typeof partParams.extensions !== 'object' ||
122
+ Array.isArray(partParams.extensions))) {
123
+ throw new Error('Invalid extensions');
124
+ }
125
+ // request parameters are checked and now complete
126
+ return partParams;
127
+ }
128
+ exports.parseRequestParams = parseRequestParams;
18
129
  /**
19
130
  * Makes a GraphQL over HTTP spec compliant server handler. The handler can
20
131
  * be used with your favorite server library.
@@ -71,7 +182,7 @@ function isResponse(val) {
71
182
  * @category Server
72
183
  */
73
184
  function createHandler(options) {
74
- 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;
185
+ 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: optionsParseRequestParams = parseRequestParams, } = options;
75
186
  return async function handler(req) {
76
187
  let acceptedMediaType = null;
77
188
  const accepts = (getHeader(req, 'accept') || '*/*')
@@ -111,9 +222,9 @@ function createHandler(options) {
111
222
  }
112
223
  let params;
113
224
  try {
114
- let paramsOrRes = await parseRequestParams(req);
225
+ let paramsOrRes = await optionsParseRequestParams(req);
115
226
  if (!paramsOrRes)
116
- paramsOrRes = await defaultParseRequestParams(req);
227
+ paramsOrRes = await parseRequestParams(req);
117
228
  if (isResponse(paramsOrRes))
118
229
  return paramsOrRes;
119
230
  params = paramsOrRes;
@@ -126,7 +237,7 @@ function createHandler(options) {
126
237
  if (isResponse(maybeResErrsOrArgs))
127
238
  return maybeResErrsOrArgs;
128
239
  else if ((0, utils_1.isExecutionResult)(maybeResErrsOrArgs) ||
129
- (0, utils_1.areGraphQLErrors)(maybeResErrsOrArgs))
240
+ areGraphQLErrors(maybeResErrsOrArgs))
130
241
  return makeResponse(maybeResErrsOrArgs, acceptedMediaType, formatError);
131
242
  else if (maybeResErrsOrArgs)
132
243
  args = maybeResErrsOrArgs;
@@ -222,110 +333,6 @@ function createHandler(options) {
222
333
  };
223
334
  }
224
335
  exports.createHandler = createHandler;
225
- /**
226
- * The default request params parser. Used when no custom one is provided or if it
227
- * returns nothing.
228
- *
229
- * Read more about it in {@link ParseRequestParams}.
230
- *
231
- * TODO: should graphql-http itself care about content-encoding? I'd say unzipping should happen before handler is reached
232
- */
233
- async function defaultParseRequestParams(req) {
234
- var _a, _b;
235
- const method = req.method;
236
- if (method !== 'GET' && method !== 'POST') {
237
- return [
238
- null,
239
- {
240
- status: 405,
241
- statusText: 'Method Not Allowed',
242
- headers: {
243
- allow: 'GET, POST',
244
- },
245
- },
246
- ];
247
- }
248
- 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)
249
- ] = (getHeader(req, 'content-type') || '')
250
- .replace(/\s/g, '')
251
- .toLowerCase()
252
- .split(';');
253
- const partParams = {};
254
- switch (true) {
255
- case method === 'GET': {
256
- // TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
257
- try {
258
- const [, search] = req.url.split('?');
259
- const searchParams = new URLSearchParams(search);
260
- partParams.operationName =
261
- (_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
262
- partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
263
- const variables = searchParams.get('variables');
264
- if (variables)
265
- partParams.variables = JSON.parse(variables);
266
- const extensions = searchParams.get('extensions');
267
- if (extensions)
268
- partParams.extensions = JSON.parse(extensions);
269
- }
270
- catch (_c) {
271
- throw new Error('Unparsable URL');
272
- }
273
- break;
274
- }
275
- case method === 'POST' &&
276
- mediaType === 'application/json' &&
277
- charset === 'charset=utf-8':
278
- {
279
- if (!req.body) {
280
- throw new Error('Missing body');
281
- }
282
- let data;
283
- try {
284
- const body = typeof req.body === 'function' ? await req.body() : req.body;
285
- data = typeof body === 'string' ? JSON.parse(body) : body;
286
- }
287
- catch (err) {
288
- throw new Error('Unparsable JSON body');
289
- }
290
- if (!(0, utils_1.isObject)(data)) {
291
- throw new Error('JSON body must be an object');
292
- }
293
- partParams.operationName = data.operationName;
294
- partParams.query = data.query;
295
- partParams.variables = data.variables;
296
- partParams.extensions = data.extensions;
297
- break;
298
- }
299
- default: // graphql-http doesnt support any other content type
300
- return [
301
- null,
302
- {
303
- status: 415,
304
- statusText: 'Unsupported Media Type',
305
- },
306
- ];
307
- }
308
- if (partParams.query == null)
309
- throw new Error('Missing query');
310
- if (typeof partParams.query !== 'string')
311
- throw new Error('Invalid query');
312
- if (partParams.variables != null &&
313
- (typeof partParams.variables !== 'object' ||
314
- Array.isArray(partParams.variables))) {
315
- throw new Error('Invalid variables');
316
- }
317
- if (partParams.operationName != null &&
318
- typeof partParams.operationName !== 'string') {
319
- throw new Error('Invalid operationName');
320
- }
321
- if (partParams.extensions != null &&
322
- (typeof partParams.extensions !== 'object' ||
323
- Array.isArray(partParams.extensions))) {
324
- throw new Error('Invalid extensions');
325
- }
326
- // request parameters are checked and now complete
327
- return partParams;
328
- }
329
336
  /**
330
337
  * Creates an appropriate GraphQL over HTTP response following the provided arguments.
331
338
  *
@@ -340,9 +347,9 @@ async function defaultParseRequestParams(req) {
340
347
  function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
341
348
  if (resultOrErrors instanceof Error &&
342
349
  // because GraphQLError extends the Error class
343
- !(0, utils_1.isGraphQLError)(resultOrErrors)) {
350
+ !isGraphQLError(resultOrErrors)) {
344
351
  return [
345
- JSON.stringify({ errors: [formatError(resultOrErrors)] }, utils_1.jsonErrorReplacer),
352
+ JSON.stringify({ errors: [formatError(resultOrErrors)] }, jsonErrorReplacer),
346
353
  {
347
354
  status: 400,
348
355
  statusText: 'Bad Request',
@@ -352,14 +359,14 @@ function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
352
359
  },
353
360
  ];
354
361
  }
355
- const errors = (0, utils_1.isGraphQLError)(resultOrErrors)
362
+ const errors = isGraphQLError(resultOrErrors)
356
363
  ? [resultOrErrors]
357
- : (0, utils_1.areGraphQLErrors)(resultOrErrors)
364
+ : areGraphQLErrors(resultOrErrors)
358
365
  ? resultOrErrors
359
366
  : null;
360
367
  if (errors) {
361
368
  return [
362
- JSON.stringify({ errors: errors.map(formatError) }, utils_1.jsonErrorReplacer),
369
+ JSON.stringify({ errors: errors.map(formatError) }, jsonErrorReplacer),
363
370
  Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
364
371
  ? {
365
372
  status: 200,
@@ -377,7 +384,7 @@ function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
377
384
  }
378
385
  return [
379
386
  JSON.stringify('errors' in resultOrErrors && resultOrErrors.errors
380
- ? Object.assign(Object.assign({}, resultOrErrors), { errors: resultOrErrors.errors.map(formatError) }) : resultOrErrors, utils_1.jsonErrorReplacer),
387
+ ? Object.assign(Object.assign({}, resultOrErrors), { errors: resultOrErrors.errors.map(formatError) }) : resultOrErrors, jsonErrorReplacer),
381
388
  {
382
389
  status: 200,
383
390
  statusText: 'OK',
@@ -395,3 +402,26 @@ function getHeader(req, key) {
395
402
  }
396
403
  return Object(req.headers)[key];
397
404
  }
405
+ function areGraphQLErrors(obj) {
406
+ return (Array.isArray(obj) &&
407
+ obj.length > 0 &&
408
+ // if one item in the array is a GraphQLError, we're good
409
+ obj.some(isGraphQLError));
410
+ }
411
+ function isGraphQLError(obj) {
412
+ return obj instanceof graphql_1.GraphQLError;
413
+ }
414
+ function jsonErrorReplacer(_key,
415
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
416
+ val) {
417
+ if (val instanceof Error &&
418
+ // GraphQL errors implement their own stringer
419
+ !isGraphQLError(val)) {
420
+ return {
421
+ // name: val.name, name is included in message
422
+ message: val.message,
423
+ // stack: val.stack, can leak sensitive details
424
+ };
425
+ }
426
+ return val;
427
+ }