graphql-http 1.1.0 → 1.4.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.
package/README.md CHANGED
@@ -190,11 +190,7 @@ fastify.all('/graphql', async (req, res) => {
190
190
  url: req.url,
191
191
  method: req.method,
192
192
  headers: req.headers,
193
- body: await new Promise((resolve) => {
194
- let body = '';
195
- req.on('data', (chunk) => (body += chunk));
196
- req.on('end', () => resolve(body));
197
- }),
193
+ body: req.body, // fastify reads the body for you
198
194
  raw: req,
199
195
  });
200
196
  res.writeHead(init.status, init.statusText, init.headers).end(body);
@@ -214,8 +210,10 @@ import { serve } from 'https://deno.land/std@0.151.0/http/server.ts';
214
210
  import { createHandler } from 'https://esm.sh/graphql-http';
215
211
  import { schema } from './previous-step';
216
212
 
213
+ // Create the GraphQL over HTTP handler
217
214
  const handler = createHandler<Request>({ schema });
218
215
 
216
+ // Start serving on `/graphql` using the handler
219
217
  await serve(
220
218
  async (req: Request) => {
221
219
  const [path, _search] = req.url.split('?');
package/lib/client.js CHANGED
@@ -105,7 +105,7 @@ function createClient(options) {
105
105
  res = await fetchFn(url, {
106
106
  signal: control.signal,
107
107
  method: 'POST',
108
- headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql+json, application/json' }),
108
+ headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql-response+json, application/json' }),
109
109
  credentials,
110
110
  referrer,
111
111
  referrerPolicy,
@@ -122,7 +122,7 @@ function createClient(options) {
122
122
  const contentType = res.headers.get('content-type');
123
123
  if (!contentType)
124
124
  throw new Error('Missing response content-type');
125
- if (!contentType.includes('application/graphql+json') &&
125
+ if (!contentType.includes('application/graphql-response+json') &&
126
126
  !contentType.includes('application/json')) {
127
127
  throw new Error(`Unsupported response content-type ${contentType}`);
128
128
  }
package/lib/client.mjs CHANGED
@@ -88,7 +88,7 @@ export function createClient(options) {
88
88
  res = await fetchFn(url, {
89
89
  signal: control.signal,
90
90
  method: 'POST',
91
- headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql+json, application/json' }),
91
+ headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql-response+json, application/json' }),
92
92
  credentials,
93
93
  referrer,
94
94
  referrerPolicy,
@@ -105,7 +105,7 @@ export function createClient(options) {
105
105
  const contentType = res.headers.get('content-type');
106
106
  if (!contentType)
107
107
  throw new Error('Missing response content-type');
108
- if (!contentType.includes('application/graphql+json') &&
108
+ if (!contentType.includes('application/graphql-response+json') &&
109
109
  !contentType.includes('application/json')) {
110
110
  throw new Error(`Unsupported response content-type ${contentType}`);
111
111
  }
package/lib/common.d.ts CHANGED
@@ -3,42 +3,6 @@
3
3
  * common
4
4
  *
5
5
  */
6
- /**
7
- * The incoming request headers the implementing server should provide.
8
- *
9
- * @category Common
10
- */
11
- export interface RequestHeaders {
12
- accept?: string | undefined;
13
- allow?: string | undefined;
14
- 'content-type'?: string | undefined;
15
- /**
16
- * Always an array in Node. Duplicates are added to it.
17
- * Not necessarily true for other environments, make sure
18
- * to check the type during runtime.
19
- */
20
- 'set-cookie'?: string | string[] | undefined;
21
- [key: string]: string | string[] | undefined;
22
- }
23
- /**
24
- * Server agnostic request interface containing the raw request
25
- * which is server dependant.
26
- *
27
- * @category Common
28
- */
29
- export interface Request<RawRequest> {
30
- readonly method: string;
31
- readonly url: string;
32
- readonly headers: RequestHeaders;
33
- readonly body: string | Record<string, unknown> | null;
34
- /**
35
- * The raw request itself from the implementing server.
36
- *
37
- * For example: `express.Request` when using Express, or maybe
38
- * `http.IncomingMessage` when just using Node with `http.createServer`.
39
- */
40
- readonly raw: RawRequest;
41
- }
42
6
  /**
43
7
  * Parameters for GraphQL's request for execution.
44
8
  *
@@ -52,47 +16,6 @@ export interface RequestParams {
52
16
  variables?: Record<string, unknown> | undefined;
53
17
  extensions?: Record<string, unknown> | undefined;
54
18
  }
55
- /**
56
- * The response headers that get returned from graphql-http.
57
- *
58
- * @category Common
59
- */
60
- export declare type ResponseHeaders = {
61
- accept?: string;
62
- allow?: string;
63
- 'content-type'?: string;
64
- } & Record<string, string>;
65
- /**
66
- * Server agnostic response body returned from `graphql-http` needing
67
- * to be coerced to the server implementation in use.
68
- *
69
- * @category Common
70
- */
71
- export declare type ResponseBody = string;
72
- /**
73
- * Server agnostic response options (ex. status and headers) returned from
74
- * `graphql-http` needing to be coerced to the server implementation in use.
75
- *
76
- * @category Common
77
- */
78
- export interface ResponseInit {
79
- readonly status: number;
80
- readonly statusText: string;
81
- readonly headers?: ResponseHeaders;
82
- }
83
- /**
84
- * Server agnostic response returned from `graphql-http` containing the
85
- * body and init options needing to be coerced to the server implementation in use.
86
- *
87
- * @category Common
88
- */
89
- export declare type Response = readonly [body: ResponseBody | null, init: ResponseInit];
90
- /**
91
- * Checks whether the passed value is the `graphql-http` server agnostic response.
92
- *
93
- * @category Common
94
- */
95
- export declare function isResponse(val: unknown): val is Response;
96
19
  /**
97
20
  * A representation of any set of values over any amount of time.
98
21
  *
package/lib/common.js CHANGED
@@ -5,17 +5,3 @@
5
5
  *
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.isResponse = void 0;
9
- const utils_1 = require("./utils");
10
- /**
11
- * Checks whether the passed value is the `graphql-http` server agnostic response.
12
- *
13
- * @category Common
14
- */
15
- function isResponse(val) {
16
- // TODO: make sure the contents of init match ResponseInit
17
- return (Array.isArray(val) &&
18
- (typeof val[0] === 'string' || val[0] === null) &&
19
- (0, utils_1.isObject)(val[1]));
20
- }
21
- exports.isResponse = isResponse;
package/lib/common.mjs CHANGED
@@ -3,15 +3,4 @@
3
3
  * common
4
4
  *
5
5
  */
6
- import { isObject } from './utils.mjs';
7
- /**
8
- * Checks whether the passed value is the `graphql-http` server agnostic response.
9
- *
10
- * @category Common
11
- */
12
- export function isResponse(val) {
13
- // TODO: make sure the contents of init match ResponseInit
14
- return (Array.isArray(val) &&
15
- (typeof val[0] === 'string' || val[0] === null) &&
16
- isObject(val[1]));
17
- }
6
+ export {};
package/lib/handler.d.ts CHANGED
@@ -4,7 +4,94 @@
4
4
  *
5
5
  */
6
6
  import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError } from 'graphql';
7
- import { Request, RequestParams, Response } from './common';
7
+ import { RequestParams } from './common';
8
+ /**
9
+ * The incoming request headers the implementing server should provide.
10
+ *
11
+ * @category Common
12
+ */
13
+ export interface RequestHeaders {
14
+ accept?: string | undefined;
15
+ allow?: string | undefined;
16
+ 'content-type'?: string | undefined;
17
+ /**
18
+ * Always an array in Node. Duplicates are added to it.
19
+ * Not necessarily true for other environments, make sure
20
+ * to check the type during runtime.
21
+ */
22
+ 'set-cookie'?: string | string[] | undefined;
23
+ [key: string]: string | string[] | undefined;
24
+ }
25
+ /**
26
+ * Server agnostic request interface containing the raw request
27
+ * which is server dependant.
28
+ *
29
+ * @category Common
30
+ */
31
+ export interface Request<RawRequest, Context> {
32
+ readonly method: string;
33
+ readonly url: string;
34
+ readonly headers: RequestHeaders;
35
+ /**
36
+ * Parsed request body or a parser function.
37
+ *
38
+ * If the provided function throws, the error message "Unparsable JSON body" will
39
+ * be in the erroneous response.
40
+ */
41
+ readonly body: string | Record<string, unknown> | null | (() => string | Record<string, unknown> | null | Promise<string | Record<string, unknown> | null>);
42
+ /**
43
+ * The raw request itself from the implementing server.
44
+ *
45
+ * For example: `express.Request` when using Express, or maybe
46
+ * `http.IncomingMessage` when just using Node with `http.createServer`.
47
+ */
48
+ readonly raw: RawRequest;
49
+ /**
50
+ * Context value about the incoming request, you're free to pass any information here.
51
+ */
52
+ readonly context: Context;
53
+ }
54
+ /**
55
+ * The response headers that get returned from graphql-http.
56
+ *
57
+ * @category Common
58
+ */
59
+ export declare type ResponseHeaders = {
60
+ accept?: string;
61
+ allow?: string;
62
+ 'content-type'?: string;
63
+ } & Record<string, string>;
64
+ /**
65
+ * Server agnostic response body returned from `graphql-http` needing
66
+ * to be coerced to the server implementation in use.
67
+ *
68
+ * @category Common
69
+ */
70
+ export declare type ResponseBody = string;
71
+ /**
72
+ * Server agnostic response options (ex. status and headers) returned from
73
+ * `graphql-http` needing to be coerced to the server implementation in use.
74
+ *
75
+ * @category Common
76
+ */
77
+ export interface ResponseInit {
78
+ readonly status: number;
79
+ readonly statusText: string;
80
+ readonly headers?: ResponseHeaders;
81
+ }
82
+ /**
83
+ * Server agnostic response returned from `graphql-http` containing the
84
+ * body and init options needing to be coerced to the server implementation in use.
85
+ *
86
+ * @category Common
87
+ */
88
+ export declare type Response = readonly [body: ResponseBody | null, init: ResponseInit];
89
+ /**
90
+ * Checks whether the passed value is the `graphql-http` server agnostic response.
91
+ *
92
+ * @category Common
93
+ */
94
+ export declare function isResponse(val: unknown): val is Response;
8
95
  /**
9
96
  * A concrete GraphQL execution context value type.
10
97
  *
@@ -17,7 +104,7 @@ import { Request, RequestParams, Response } from './common';
17
104
  */
18
105
  export declare type ExecutionContext = object | symbol | number | string | boolean | undefined | null;
19
106
  /** @category Server */
20
- export interface HandlerOptions<RawRequest = unknown> {
107
+ export interface HandlerOptions<RawRequest = unknown, Context = unknown> {
21
108
  /**
22
109
  * The GraphQL schema on which the operations will
23
110
  * be executed and validated against.
@@ -34,16 +121,18 @@ export interface HandlerOptions<RawRequest = unknown> {
34
121
  * you should do by returning a `Request` argument which will stop
35
122
  * further execution.
36
123
  */
37
- schema?: GraphQLSchema | ((req: Request<RawRequest>, args: Omit<ExecutionArgs, 'schema'>) => Promise<GraphQLSchema | Response> | GraphQLSchema | Response);
124
+ schema?: GraphQLSchema | ((req: Request<RawRequest, Context>, args: Omit<ExecutionArgs, 'schema'>) => Promise<GraphQLSchema | Response> | GraphQLSchema | Response);
38
125
  /**
39
126
  * A value which is provided to every resolver and holds
40
127
  * important contextual information like the currently
41
128
  * logged in user, or access to a database.
42
129
  */
43
- context?: ExecutionContext | ((req: Request<RawRequest>, args: ExecutionArgs) => Promise<ExecutionContext | Response> | ExecutionContext | Response);
130
+ context?: ExecutionContext | ((req: Request<RawRequest, Context>, args: ExecutionArgs) => Promise<ExecutionContext | Response> | ExecutionContext | Response);
44
131
  /**
45
132
  * A custom GraphQL validate function allowing you to apply your
46
133
  * own validation rules.
134
+ *
135
+ * Will not be used when implementing a custom `onSubscribe`.
47
136
  */
48
137
  validate?: typeof graphqlValidate;
49
138
  /**
@@ -63,10 +152,16 @@ export interface HandlerOptions<RawRequest = unknown> {
63
152
  * The subscribe callback executed right after processing the request
64
153
  * before proceeding with the GraphQL operation execution.
65
154
  *
155
+ * If you return `ExecutionResult` from the callback, it will be used
156
+ * directly for responding to the request. Useful for implementing a response
157
+ * cache.
158
+ *
66
159
  * If you return `ExecutionArgs` from the callback, it will be used instead of
67
160
  * trying to build one internally. In this case, you are responsible for providing
68
161
  * a ready set of arguments which will be directly plugged in the operation execution.
69
162
  *
163
+ * You *must* validate the `ExecutionArgs` yourself if returning them.
164
+ *
70
165
  * If you return an array of `GraphQLError` from the callback, they will be reported
71
166
  * to the client while complying with the spec.
72
167
  *
@@ -81,7 +176,7 @@ export interface HandlerOptions<RawRequest = unknown> {
81
176
  * you should do by returning a `Request` argument which will stop
82
177
  * further execution.
83
178
  */
84
- onSubscribe?: (req: Request<RawRequest>, params: RequestParams) => Promise<ExecutionArgs | GraphQLError[] | Response | void> | ExecutionArgs | GraphQLError[] | Response | void;
179
+ onSubscribe?: (req: Request<RawRequest, Context>, params: RequestParams) => Promise<ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void> | ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void;
85
180
  /**
86
181
  * Executed after the operation call resolves.
87
182
  *
@@ -95,7 +190,7 @@ export interface HandlerOptions<RawRequest = unknown> {
95
190
  * you should do by returning a `Request` argument which will stop
96
191
  * further execution.
97
192
  */
98
- onOperation?: (req: Request<RawRequest>, args: ExecutionArgs, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
193
+ onOperation?: (req: Request<RawRequest, Context>, args: ExecutionArgs, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
99
194
  }
100
195
  /**
101
196
  * The ready-to-use handler. Simply plug it in your favourite HTTP framework
@@ -107,7 +202,7 @@ export interface HandlerOptions<RawRequest = unknown> {
107
202
  *
108
203
  * @category Server
109
204
  */
110
- export declare type Handler<RawRequest = unknown> = (req: Request<RawRequest>) => Promise<Response>;
205
+ export declare type Handler<RawRequest = unknown, Context = unknown> = (req: Request<RawRequest, Context>) => Promise<Response>;
111
206
  /**
112
207
  * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
113
208
  * be used with your favourite server library.
@@ -163,4 +258,28 @@ export declare type Handler<RawRequest = unknown> = (req: Request<RawRequest>) =
163
258
  *
164
259
  * @category Server
165
260
  */
166
- export declare function createHandler<RawRequest = unknown>(options: HandlerOptions<RawRequest>): Handler<RawRequest>;
261
+ export declare function createHandler<RawRequest = unknown, Context = unknown>(options: HandlerOptions<RawRequest, Context>): Handler<RawRequest, Context>;
262
+ /**
263
+ * Request's Media-Type that the server accepts.
264
+ *
265
+ * @category Server
266
+ */
267
+ export declare type AcceptableMediaType = 'application/graphql-response+json' | 'application/json';
268
+ /**
269
+ * Inspects the request and detects the appropriate/acceptable Media-Type
270
+ * looking at the `Accept` header while complying with the GraphQL over HTTP Protocol.
271
+ *
272
+ * @category Server
273
+ */
274
+ export declare function getAcceptableMediaType(acceptHeader: string | null | undefined): AcceptableMediaType | null;
275
+ /**
276
+ * Creates an appropriate GraphQL over HTTP response following the provided arguments.
277
+ *
278
+ * If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
279
+ *
280
+ * If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
281
+ * and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
282
+ *
283
+ * @category Server
284
+ */
285
+ export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError>, acceptedMediaType: AcceptableMediaType): Response;
package/lib/handler.js CHANGED
@@ -5,10 +5,21 @@
5
5
  *
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.createHandler = void 0;
8
+ exports.makeResponse = exports.getAcceptableMediaType = exports.createHandler = exports.isResponse = void 0;
9
9
  const graphql_1 = require("graphql");
10
- const common_1 = require("./common");
11
10
  const utils_1 = require("./utils");
11
+ /**
12
+ * Checks whether the passed value is the `graphql-http` server agnostic response.
13
+ *
14
+ * @category Common
15
+ */
16
+ function isResponse(val) {
17
+ // TODO: make sure the contents of init match ResponseInit
18
+ return (Array.isArray(val) &&
19
+ (typeof val[0] === 'string' || val[0] === null) &&
20
+ (0, utils_1.isObject)(val[1]));
21
+ }
22
+ exports.isResponse = isResponse;
12
23
  /**
13
24
  * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
14
25
  * be used with your favourite server library.
@@ -81,28 +92,7 @@ function createHandler(options) {
81
92
  },
82
93
  ];
83
94
  }
84
- let acceptedMediaType;
85
- const accepts = (req.headers.accept || '*/*')
86
- .replace(/\s/g, '')
87
- .toLowerCase()
88
- .split(',');
89
- for (const accept of accepts) {
90
- // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
91
- // TODO: handle the weight parameter "q"
92
- const [mediaType, ...params] = accept.split(';');
93
- const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
94
- if (mediaType === 'application/json' && charset === 'charset=utf8') {
95
- acceptedMediaType = 'application/json';
96
- break;
97
- }
98
- if ((mediaType === 'application/graphql+json' ||
99
- mediaType === 'application/*' ||
100
- mediaType === '*/*') &&
101
- charset === 'charset=utf8') {
102
- acceptedMediaType = 'application/graphql+json';
103
- break;
104
- }
105
- }
95
+ const acceptedMediaType = getAcceptableMediaType(req.headers.accept);
106
96
  if (!acceptedMediaType) {
107
97
  return [
108
98
  null,
@@ -110,7 +100,7 @@ function createHandler(options) {
110
100
  status: 406,
111
101
  statusText: 'Not Acceptable',
112
102
  headers: {
113
- accept: 'application/graphql+json; charset=utf-8, application/json; charset=utf-8',
103
+ accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8',
114
104
  },
115
105
  },
116
106
  ];
@@ -152,16 +142,21 @@ function createHandler(options) {
152
142
  if (!req.body) {
153
143
  throw new Error('Missing body');
154
144
  }
145
+ let data;
155
146
  try {
156
- const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
157
- partParams.operationName = data.operationName;
158
- partParams.query = data.query;
159
- partParams.variables = data.variables;
160
- partParams.extensions = data.extensions;
147
+ const body = typeof req.body === 'function' ? await req.body() : req.body;
148
+ data = typeof body === 'string' ? JSON.parse(body) : body;
161
149
  }
162
- catch (_d) {
150
+ catch (err) {
163
151
  throw new Error('Unparsable JSON body');
164
152
  }
153
+ if (!(0, utils_1.isObject)(data)) {
154
+ throw new Error('JSON body must be an object');
155
+ }
156
+ partParams.operationName = data.operationName;
157
+ partParams.query = data.query;
158
+ partParams.variables = data.variables;
159
+ partParams.extensions = data.extensions;
165
160
  break;
166
161
  }
167
162
  default: // graphql-http doesnt support any other content type
@@ -173,8 +168,10 @@ function createHandler(options) {
173
168
  },
174
169
  ];
175
170
  }
176
- if (!partParams.query)
171
+ if (partParams.query == null)
177
172
  throw new Error('Missing query');
173
+ if (typeof partParams.query !== 'string')
174
+ throw new Error('Invalid query');
178
175
  if (partParams.variables != null &&
179
176
  (typeof partParams.variables !== 'object' ||
180
177
  Array.isArray(partParams.variables))) {
@@ -189,40 +186,15 @@ function createHandler(options) {
189
186
  params = partParams;
190
187
  }
191
188
  catch (err) {
192
- return [
193
- err.message,
194
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
195
- ? {
196
- status: 200,
197
- statusText: 'OK',
198
- }
199
- : {
200
- status: 400,
201
- statusText: 'Bad Request',
202
- })), { statusText: 'Bad Request' }),
203
- ];
189
+ return makeResponse(new graphql_1.GraphQLError(err.message), acceptedMediaType);
204
190
  }
205
191
  let args;
206
192
  const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
207
- if ((0, common_1.isResponse)(maybeResErrsOrArgs))
193
+ if (isResponse(maybeResErrsOrArgs))
208
194
  return maybeResErrsOrArgs;
209
- else if ((0, utils_1.areGraphQLErrors)(maybeResErrsOrArgs))
210
- return [
211
- JSON.stringify({ errors: maybeResErrsOrArgs }),
212
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
213
- ? {
214
- status: 200,
215
- statusText: 'OK',
216
- }
217
- : {
218
- status: 400,
219
- statusText: 'Bad Request',
220
- })), { headers: {
221
- 'content-type': acceptedMediaType === 'application/json'
222
- ? 'application/json; charset=utf-8'
223
- : 'application/graphql+json; charset=utf-8',
224
- } }),
225
- ];
195
+ else if ((0, utils_1.isExecutionResult)(maybeResErrsOrArgs) ||
196
+ (0, utils_1.areGraphQLErrors)(maybeResErrsOrArgs))
197
+ return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
226
198
  else if (maybeResErrsOrArgs)
227
199
  args = maybeResErrsOrArgs;
228
200
  else {
@@ -234,22 +206,7 @@ function createHandler(options) {
234
206
  document = parse(query);
235
207
  }
236
208
  catch (err) {
237
- return [
238
- JSON.stringify({ errors: [err] }),
239
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
240
- ? {
241
- status: 200,
242
- statusText: 'OK',
243
- }
244
- : {
245
- status: 400,
246
- statusText: 'Bad Request',
247
- })), { statusText: 'Bad Request', headers: {
248
- 'content-type': acceptedMediaType === 'application/json'
249
- ? 'application/json; charset=utf-8'
250
- : 'application/graphql+json; charset=utf-8',
251
- } }),
252
- ];
209
+ return makeResponse(err, acceptedMediaType);
253
210
  }
254
211
  const argsWithoutSchema = {
255
212
  operationName,
@@ -258,13 +215,17 @@ function createHandler(options) {
258
215
  };
259
216
  if (typeof schema === 'function') {
260
217
  const resOrSchema = await schema(req, argsWithoutSchema);
261
- if ((0, common_1.isResponse)(resOrSchema))
218
+ if (isResponse(resOrSchema))
262
219
  return resOrSchema;
263
220
  args = Object.assign(Object.assign({}, argsWithoutSchema), { schema: resOrSchema });
264
221
  }
265
222
  else {
266
223
  args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
267
224
  }
225
+ const validationErrs = validate(args.schema, args.document);
226
+ if (validationErrs.length) {
227
+ return makeResponse(validationErrs, acceptedMediaType);
228
+ }
268
229
  }
269
230
  let operation;
270
231
  try {
@@ -273,34 +234,19 @@ function createHandler(options) {
273
234
  throw null;
274
235
  operation = ast.operation;
275
236
  }
276
- catch (_e) {
277
- return [
278
- 'Unable to detect operation AST',
279
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
280
- ? {
281
- status: 200,
282
- statusText: 'OK',
283
- }
284
- : {
285
- status: 400,
286
- statusText: 'Bad Request',
287
- })), { statusText: 'Bad Request' }),
288
- ];
237
+ catch (_d) {
238
+ return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType);
289
239
  }
290
240
  if (operation === 'subscription') {
291
- return [
292
- 'Subscriptions are not supported',
293
- {
294
- status: 400,
295
- statusText: 'Bad Request',
296
- },
297
- ];
241
+ return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
298
242
  }
299
243
  // mutations cannot happen over GETs
300
244
  // https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
301
245
  if (operation === 'mutation' && method === 'GET') {
302
246
  return [
303
- 'Cannot perform mutations over GET',
247
+ JSON.stringify({
248
+ errors: [new graphql_1.GraphQLError('Cannot perform mutations over GET')],
249
+ }),
304
250
  {
305
251
  status: 405,
306
252
  statusText: 'Method Not Allowed',
@@ -312,47 +258,101 @@ function createHandler(options) {
312
258
  }
313
259
  if (!('contextValue' in args)) {
314
260
  const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
315
- if ((0, common_1.isResponse)(maybeResOrContext))
261
+ if (isResponse(maybeResOrContext))
316
262
  return maybeResOrContext;
317
263
  args.contextValue = maybeResOrContext;
318
264
  }
319
- const validationErrs = validate(args.schema, args.document);
320
- if (validationErrs.length) {
321
- return [
322
- JSON.stringify({ errors: validationErrs }),
323
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
324
- ? {
325
- status: 200,
326
- statusText: 'OK',
327
- }
328
- : {
329
- status: 400,
330
- statusText: 'Bad Request',
331
- })), { headers: {
332
- 'content-type': acceptedMediaType === 'application/json'
333
- ? 'application/json; charset=utf-8'
334
- : 'application/graphql+json; charset=utf-8',
335
- } }),
336
- ];
337
- }
338
265
  let result = await execute(args);
339
- const maybeResOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
340
- if ((0, common_1.isResponse)(maybeResOrResult))
341
- return maybeResOrResult;
342
- else if (maybeResOrResult)
343
- result = maybeResOrResult;
266
+ const maybeResponseOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
267
+ if (isResponse(maybeResponseOrResult))
268
+ return maybeResponseOrResult;
269
+ else if (maybeResponseOrResult)
270
+ result = maybeResponseOrResult;
271
+ if ((0, utils_1.isAsyncIterable)(result)) {
272
+ return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
273
+ }
274
+ return makeResponse(result, acceptedMediaType);
275
+ };
276
+ }
277
+ exports.createHandler = createHandler;
278
+ /**
279
+ * Inspects the request and detects the appropriate/acceptable Media-Type
280
+ * looking at the `Accept` header while complying with the GraphQL over HTTP Protocol.
281
+ *
282
+ * @category Server
283
+ */
284
+ function getAcceptableMediaType(acceptHeader) {
285
+ let acceptedMediaType = null;
286
+ const accepts = (acceptHeader || '*/*')
287
+ .replace(/\s/g, '')
288
+ .toLowerCase()
289
+ .split(',');
290
+ for (const accept of accepts) {
291
+ // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
292
+ // TODO: handle the weight parameter "q"
293
+ const [mediaType, ...params] = accept.split(';');
294
+ const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
295
+ if (mediaType === 'application/json' && charset === 'charset=utf8') {
296
+ acceptedMediaType = 'application/json';
297
+ break;
298
+ }
299
+ if ((mediaType === 'application/graphql-response+json' ||
300
+ mediaType === 'application/*' ||
301
+ mediaType === '*/*') &&
302
+ charset === 'charset=utf8') {
303
+ acceptedMediaType = 'application/graphql-response+json';
304
+ break;
305
+ }
306
+ }
307
+ return acceptedMediaType;
308
+ }
309
+ exports.getAcceptableMediaType = getAcceptableMediaType;
310
+ /**
311
+ * Creates an appropriate GraphQL over HTTP response following the provided arguments.
312
+ *
313
+ * If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
314
+ *
315
+ * If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
316
+ * and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
317
+ *
318
+ * @category Server
319
+ */
320
+ function makeResponse(resultOrErrors, acceptedMediaType) {
321
+ if ((0, utils_1.isExecutionResult)(resultOrErrors)) {
344
322
  return [
345
- JSON.stringify(result),
323
+ JSON.stringify(resultOrErrors),
346
324
  {
347
325
  status: 200,
348
326
  statusText: 'OK',
349
327
  headers: {
350
328
  'content-type': acceptedMediaType === 'application/json'
351
329
  ? 'application/json; charset=utf-8'
352
- : 'application/graphql+json; charset=utf-8',
330
+ : 'application/graphql-response+json; charset=utf-8',
353
331
  },
354
332
  },
355
333
  ];
356
- };
334
+ }
335
+ return [
336
+ JSON.stringify({
337
+ errors: Array.isArray(resultOrErrors)
338
+ ? (0, utils_1.isObject)(resultOrErrors)
339
+ ? resultOrErrors
340
+ : new graphql_1.GraphQLError(String(resultOrErrors))
341
+ : [resultOrErrors],
342
+ }),
343
+ Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
344
+ ? {
345
+ status: 200,
346
+ statusText: 'OK',
347
+ }
348
+ : {
349
+ status: 400,
350
+ statusText: 'Bad Request',
351
+ })), { headers: {
352
+ 'content-type': acceptedMediaType === 'application/json'
353
+ ? 'application/json; charset=utf-8'
354
+ : 'application/graphql-response+json; charset=utf-8',
355
+ } }),
356
+ ];
357
357
  }
358
- exports.createHandler = createHandler;
358
+ exports.makeResponse = makeResponse;
package/lib/handler.mjs CHANGED
@@ -3,9 +3,19 @@
3
3
  * handler
4
4
  *
5
5
  */
6
- import { validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, } from 'graphql';
7
- import { isResponse } from './common.mjs';
8
- import { areGraphQLErrors } from './utils.mjs';
6
+ import { validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError, } from 'graphql';
7
+ import { areGraphQLErrors, isAsyncIterable, isExecutionResult, isObject, } from './utils.mjs';
8
+ /**
9
+ * Checks whether the passed value is the `graphql-http` server agnostic response.
10
+ *
11
+ * @category Common
12
+ */
13
+ export function isResponse(val) {
14
+ // TODO: make sure the contents of init match ResponseInit
15
+ return (Array.isArray(val) &&
16
+ (typeof val[0] === 'string' || val[0] === null) &&
17
+ isObject(val[1]));
18
+ }
9
19
  /**
10
20
  * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
11
21
  * be used with your favourite server library.
@@ -78,28 +88,7 @@ export function createHandler(options) {
78
88
  },
79
89
  ];
80
90
  }
81
- let acceptedMediaType;
82
- const accepts = (req.headers.accept || '*/*')
83
- .replace(/\s/g, '')
84
- .toLowerCase()
85
- .split(',');
86
- for (const accept of accepts) {
87
- // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
88
- // TODO: handle the weight parameter "q"
89
- const [mediaType, ...params] = accept.split(';');
90
- const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
91
- if (mediaType === 'application/json' && charset === 'charset=utf8') {
92
- acceptedMediaType = 'application/json';
93
- break;
94
- }
95
- if ((mediaType === 'application/graphql+json' ||
96
- mediaType === 'application/*' ||
97
- mediaType === '*/*') &&
98
- charset === 'charset=utf8') {
99
- acceptedMediaType = 'application/graphql+json';
100
- break;
101
- }
102
- }
91
+ const acceptedMediaType = getAcceptableMediaType(req.headers.accept);
103
92
  if (!acceptedMediaType) {
104
93
  return [
105
94
  null,
@@ -107,7 +96,7 @@ export function createHandler(options) {
107
96
  status: 406,
108
97
  statusText: 'Not Acceptable',
109
98
  headers: {
110
- accept: 'application/graphql+json; charset=utf-8, application/json; charset=utf-8',
99
+ accept: 'application/graphql-response+json; charset=utf-8, application/json; charset=utf-8',
111
100
  },
112
101
  },
113
102
  ];
@@ -149,16 +138,21 @@ export function createHandler(options) {
149
138
  if (!req.body) {
150
139
  throw new Error('Missing body');
151
140
  }
141
+ let data;
152
142
  try {
153
- const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
154
- partParams.operationName = data.operationName;
155
- partParams.query = data.query;
156
- partParams.variables = data.variables;
157
- partParams.extensions = data.extensions;
143
+ const body = typeof req.body === 'function' ? await req.body() : req.body;
144
+ data = typeof body === 'string' ? JSON.parse(body) : body;
158
145
  }
159
- catch (_d) {
146
+ catch (err) {
160
147
  throw new Error('Unparsable JSON body');
161
148
  }
149
+ if (!isObject(data)) {
150
+ throw new Error('JSON body must be an object');
151
+ }
152
+ partParams.operationName = data.operationName;
153
+ partParams.query = data.query;
154
+ partParams.variables = data.variables;
155
+ partParams.extensions = data.extensions;
162
156
  break;
163
157
  }
164
158
  default: // graphql-http doesnt support any other content type
@@ -170,8 +164,10 @@ export function createHandler(options) {
170
164
  },
171
165
  ];
172
166
  }
173
- if (!partParams.query)
167
+ if (partParams.query == null)
174
168
  throw new Error('Missing query');
169
+ if (typeof partParams.query !== 'string')
170
+ throw new Error('Invalid query');
175
171
  if (partParams.variables != null &&
176
172
  (typeof partParams.variables !== 'object' ||
177
173
  Array.isArray(partParams.variables))) {
@@ -186,40 +182,15 @@ export function createHandler(options) {
186
182
  params = partParams;
187
183
  }
188
184
  catch (err) {
189
- return [
190
- err.message,
191
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
192
- ? {
193
- status: 200,
194
- statusText: 'OK',
195
- }
196
- : {
197
- status: 400,
198
- statusText: 'Bad Request',
199
- })), { statusText: 'Bad Request' }),
200
- ];
185
+ return makeResponse(new GraphQLError(err.message), acceptedMediaType);
201
186
  }
202
187
  let args;
203
188
  const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
204
189
  if (isResponse(maybeResErrsOrArgs))
205
190
  return maybeResErrsOrArgs;
206
- else if (areGraphQLErrors(maybeResErrsOrArgs))
207
- return [
208
- JSON.stringify({ errors: maybeResErrsOrArgs }),
209
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
210
- ? {
211
- status: 200,
212
- statusText: 'OK',
213
- }
214
- : {
215
- status: 400,
216
- statusText: 'Bad Request',
217
- })), { headers: {
218
- 'content-type': acceptedMediaType === 'application/json'
219
- ? 'application/json; charset=utf-8'
220
- : 'application/graphql+json; charset=utf-8',
221
- } }),
222
- ];
191
+ else if (isExecutionResult(maybeResErrsOrArgs) ||
192
+ areGraphQLErrors(maybeResErrsOrArgs))
193
+ return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
223
194
  else if (maybeResErrsOrArgs)
224
195
  args = maybeResErrsOrArgs;
225
196
  else {
@@ -231,22 +202,7 @@ export function createHandler(options) {
231
202
  document = parse(query);
232
203
  }
233
204
  catch (err) {
234
- return [
235
- JSON.stringify({ errors: [err] }),
236
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
237
- ? {
238
- status: 200,
239
- statusText: 'OK',
240
- }
241
- : {
242
- status: 400,
243
- statusText: 'Bad Request',
244
- })), { statusText: 'Bad Request', headers: {
245
- 'content-type': acceptedMediaType === 'application/json'
246
- ? 'application/json; charset=utf-8'
247
- : 'application/graphql+json; charset=utf-8',
248
- } }),
249
- ];
205
+ return makeResponse(err, acceptedMediaType);
250
206
  }
251
207
  const argsWithoutSchema = {
252
208
  operationName,
@@ -262,6 +218,10 @@ export function createHandler(options) {
262
218
  else {
263
219
  args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
264
220
  }
221
+ const validationErrs = validate(args.schema, args.document);
222
+ if (validationErrs.length) {
223
+ return makeResponse(validationErrs, acceptedMediaType);
224
+ }
265
225
  }
266
226
  let operation;
267
227
  try {
@@ -270,34 +230,19 @@ export function createHandler(options) {
270
230
  throw null;
271
231
  operation = ast.operation;
272
232
  }
273
- catch (_e) {
274
- return [
275
- 'Unable to detect operation AST',
276
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
277
- ? {
278
- status: 200,
279
- statusText: 'OK',
280
- }
281
- : {
282
- status: 400,
283
- statusText: 'Bad Request',
284
- })), { statusText: 'Bad Request' }),
285
- ];
233
+ catch (_d) {
234
+ return makeResponse(new GraphQLError('Unable to detect operation AST'), acceptedMediaType);
286
235
  }
287
236
  if (operation === 'subscription') {
288
- return [
289
- 'Subscriptions are not supported',
290
- {
291
- status: 400,
292
- statusText: 'Bad Request',
293
- },
294
- ];
237
+ return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
295
238
  }
296
239
  // mutations cannot happen over GETs
297
240
  // https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
298
241
  if (operation === 'mutation' && method === 'GET') {
299
242
  return [
300
- 'Cannot perform mutations over GET',
243
+ JSON.stringify({
244
+ errors: [new GraphQLError('Cannot perform mutations over GET')],
245
+ }),
301
246
  {
302
247
  status: 405,
303
248
  statusText: 'Method Not Allowed',
@@ -313,42 +258,94 @@ export function createHandler(options) {
313
258
  return maybeResOrContext;
314
259
  args.contextValue = maybeResOrContext;
315
260
  }
316
- const validationErrs = validate(args.schema, args.document);
317
- if (validationErrs.length) {
318
- return [
319
- JSON.stringify({ errors: validationErrs }),
320
- Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
321
- ? {
322
- status: 200,
323
- statusText: 'OK',
324
- }
325
- : {
326
- status: 400,
327
- statusText: 'Bad Request',
328
- })), { headers: {
329
- 'content-type': acceptedMediaType === 'application/json'
330
- ? 'application/json; charset=utf-8'
331
- : 'application/graphql+json; charset=utf-8',
332
- } }),
333
- ];
334
- }
335
261
  let result = await execute(args);
336
- const maybeResOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
337
- if (isResponse(maybeResOrResult))
338
- return maybeResOrResult;
339
- else if (maybeResOrResult)
340
- result = maybeResOrResult;
262
+ const maybeResponseOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
263
+ if (isResponse(maybeResponseOrResult))
264
+ return maybeResponseOrResult;
265
+ else if (maybeResponseOrResult)
266
+ result = maybeResponseOrResult;
267
+ if (isAsyncIterable(result)) {
268
+ return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
269
+ }
270
+ return makeResponse(result, acceptedMediaType);
271
+ };
272
+ }
273
+ /**
274
+ * Inspects the request and detects the appropriate/acceptable Media-Type
275
+ * looking at the `Accept` header while complying with the GraphQL over HTTP Protocol.
276
+ *
277
+ * @category Server
278
+ */
279
+ export function getAcceptableMediaType(acceptHeader) {
280
+ let acceptedMediaType = null;
281
+ const accepts = (acceptHeader || '*/*')
282
+ .replace(/\s/g, '')
283
+ .toLowerCase()
284
+ .split(',');
285
+ for (const accept of accepts) {
286
+ // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
287
+ // TODO: handle the weight parameter "q"
288
+ const [mediaType, ...params] = accept.split(';');
289
+ const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
290
+ if (mediaType === 'application/json' && charset === 'charset=utf8') {
291
+ acceptedMediaType = 'application/json';
292
+ break;
293
+ }
294
+ if ((mediaType === 'application/graphql-response+json' ||
295
+ mediaType === 'application/*' ||
296
+ mediaType === '*/*') &&
297
+ charset === 'charset=utf8') {
298
+ acceptedMediaType = 'application/graphql-response+json';
299
+ break;
300
+ }
301
+ }
302
+ return acceptedMediaType;
303
+ }
304
+ /**
305
+ * Creates an appropriate GraphQL over HTTP response following the provided arguments.
306
+ *
307
+ * If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
308
+ *
309
+ * If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
310
+ * and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
311
+ *
312
+ * @category Server
313
+ */
314
+ export function makeResponse(resultOrErrors, acceptedMediaType) {
315
+ if (isExecutionResult(resultOrErrors)) {
341
316
  return [
342
- JSON.stringify(result),
317
+ JSON.stringify(resultOrErrors),
343
318
  {
344
319
  status: 200,
345
320
  statusText: 'OK',
346
321
  headers: {
347
322
  'content-type': acceptedMediaType === 'application/json'
348
323
  ? 'application/json; charset=utf-8'
349
- : 'application/graphql+json; charset=utf-8',
324
+ : 'application/graphql-response+json; charset=utf-8',
350
325
  },
351
326
  },
352
327
  ];
353
- };
328
+ }
329
+ return [
330
+ JSON.stringify({
331
+ errors: Array.isArray(resultOrErrors)
332
+ ? isObject(resultOrErrors)
333
+ ? resultOrErrors
334
+ : new GraphQLError(String(resultOrErrors))
335
+ : [resultOrErrors],
336
+ }),
337
+ Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
338
+ ? {
339
+ status: 200,
340
+ statusText: 'OK',
341
+ }
342
+ : {
343
+ status: 400,
344
+ statusText: 'Bad Request',
345
+ })), { headers: {
346
+ 'content-type': acceptedMediaType === 'application/json'
347
+ ? 'application/json; charset=utf-8'
348
+ : 'application/graphql-response+json; charset=utf-8',
349
+ } }),
350
+ ];
354
351
  }
package/lib/utils.d.ts CHANGED
@@ -3,8 +3,12 @@
3
3
  * utils
4
4
  *
5
5
  */
6
- import type { GraphQLError } from 'graphql';
6
+ import type { ExecutionResult, GraphQLError } from 'graphql';
7
7
  /** @private */
8
- export declare function isObject(val: unknown): val is Record<PropertyKey, unknown>;
8
+ export declare function isObject(val: unknown): val is Record<PropertyKey, any>;
9
9
  /** @private */
10
- export declare function areGraphQLErrors(obj: unknown): obj is GraphQLError[];
10
+ export declare function areGraphQLErrors(obj: unknown): obj is readonly GraphQLError[];
11
+ /** @private */
12
+ export declare function isExecutionResult(val: unknown): val is ExecutionResult;
13
+ /** @private */
14
+ export declare function isAsyncIterable<T = unknown>(val: unknown): val is AsyncIterable<T>;
package/lib/utils.js CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.areGraphQLErrors = exports.isObject = void 0;
8
+ exports.isAsyncIterable = exports.isExecutionResult = exports.areGraphQLErrors = exports.isObject = void 0;
9
9
  /** @private */
10
10
  function isObject(val) {
11
11
  return typeof val === 'object' && val !== null;
@@ -20,3 +20,14 @@ function areGraphQLErrors(obj) {
20
20
  obj.every((ob) => 'message' in ob));
21
21
  }
22
22
  exports.areGraphQLErrors = areGraphQLErrors;
23
+ /** @private */
24
+ function isExecutionResult(val) {
25
+ return (isObject(val) &&
26
+ ('data' in val || ('data' in val && val.data == null && 'errors' in val)));
27
+ }
28
+ exports.isExecutionResult = isExecutionResult;
29
+ /** @private */
30
+ function isAsyncIterable(val) {
31
+ return typeof Object(val)[Symbol.asyncIterator] === 'function';
32
+ }
33
+ exports.isAsyncIterable = isAsyncIterable;
package/lib/utils.mjs CHANGED
@@ -15,3 +15,12 @@ export function areGraphQLErrors(obj) {
15
15
  // error has at least a message
16
16
  obj.every((ob) => 'message' in ob));
17
17
  }
18
+ /** @private */
19
+ export function isExecutionResult(val) {
20
+ return (isObject(val) &&
21
+ ('data' in val || ('data' in val && val.data == null && 'errors' in val)));
22
+ }
23
+ /** @private */
24
+ export function isAsyncIterable(val) {
25
+ return typeof Object(val)[Symbol.asyncIterator] === 'function';
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphql-http",
3
- "version": "1.1.0",
3
+ "version": "1.4.0",
4
4
  "description": "Simple, pluggable, zero-dependency, GraphQL over HTTP Protocol compliant server and client",
5
5
  "keywords": [
6
6
  "graphql",
@@ -75,11 +75,11 @@
75
75
  "@rollup/plugin-typescript": "^8.3.4",
76
76
  "@semantic-release/changelog": "^6.0.1",
77
77
  "@semantic-release/git": "^10.0.1",
78
- "@types/jest": "^28.1.6",
79
- "@typescript-eslint/eslint-plugin": "^5.32.0",
80
- "@typescript-eslint/parser": "^5.32.0",
78
+ "@types/jest": "^28.1.7",
79
+ "@typescript-eslint/eslint-plugin": "^5.33.1",
80
+ "@typescript-eslint/parser": "^5.33.1",
81
81
  "babel-jest": "^28.1.3",
82
- "eslint": "^8.21.0",
82
+ "eslint": "^8.22.0",
83
83
  "eslint-config-prettier": "^8.5.0",
84
84
  "eslint-plugin-prettier": "^4.2.1",
85
85
  "graphql": "^16.5.0",
@@ -88,7 +88,7 @@
88
88
  "node-fetch": "^3.2.10",
89
89
  "prettier": "^2.7.1",
90
90
  "replacestream": "^4.0.3",
91
- "rollup": "^2.77.2",
91
+ "rollup": "^2.78.0",
92
92
  "rollup-plugin-terser": "^7.0.2",
93
93
  "semantic-release": "^19.0.3",
94
94
  "tslib": "^2.4.0",
@@ -14,23 +14,6 @@
14
14
  return typeof val === 'object' && val !== null;
15
15
  }
16
16
 
17
- /**
18
- *
19
- * common
20
- *
21
- */
22
- /**
23
- * Checks whether the passed value is the `graphql-http` server agnostic response.
24
- *
25
- * @category Common
26
- */
27
- function isResponse(val) {
28
- // TODO: make sure the contents of init match ResponseInit
29
- return (Array.isArray(val) &&
30
- (typeof val[0] === 'string' || val[0] === null) &&
31
- isObject(val[1]));
32
- }
33
-
34
17
  /**
35
18
  *
36
19
  * client
@@ -118,7 +101,7 @@
118
101
  res = await fetchFn(url, {
119
102
  signal: control.signal,
120
103
  method: 'POST',
121
- headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql+json, application/json' }),
104
+ headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql-response+json, application/json' }),
122
105
  credentials,
123
106
  referrer,
124
107
  referrerPolicy,
@@ -135,7 +118,7 @@
135
118
  const contentType = res.headers.get('content-type');
136
119
  if (!contentType)
137
120
  throw new Error('Missing response content-type');
138
- if (!contentType.includes('application/graphql+json') &&
121
+ if (!contentType.includes('application/graphql-response+json') &&
139
122
  !contentType.includes('application/json')) {
140
123
  throw new Error(`Unsupported response content-type ${contentType}`);
141
124
  }
@@ -201,7 +184,6 @@
201
184
 
202
185
  exports.NetworkError = NetworkError;
203
186
  exports.createClient = createClient;
204
- exports.isResponse = isResponse;
205
187
 
206
188
  Object.defineProperty(exports, '__esModule', { value: true });
207
189
 
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).graphqlHttp={})}(this,(function(e){"use strict";function t(e){return"object"==typeof e&&null!==e}class r extends Error{constructor(e){let r,o;var n;t(n=e)&&"boolean"==typeof n.ok&&"number"==typeof n.status&&"string"==typeof n.statusText?(o=e,r="Server responded with "+e.status+": "+e.statusText):r=e instanceof Error?e.message:String(e),super(r),this.name=this.constructor.name,this.response=o}}e.NetworkError=r,e.createClient=function(e){const{credentials:t="same-origin",referrer:o,referrerPolicy:n,shouldRetry:s=(()=>!1)}=e,i=e.fetchFn||fetch,a=e.abortControllerImpl||AbortController,c=(()=>{let e=!1;const t=[];return{get disposed(){return e},onDispose:r=>e?(setTimeout((()=>r()),0),()=>{}):(t.push(r),()=>{t.splice(t.indexOf(r),1)}),dispose(){if(!e){e=!0;for(const e of[...t])e()}}}})();return{subscribe(l,p){if(c.disposed)throw new Error("Client has been disposed");const f=new a,u=c.onDispose((()=>{u(),f.abort()}));return(async()=>{var a;let c=null,u=0;for(;;){if(c){const e=await s(c,u);if(f.signal.aborted)return;if(!e)throw c;u++}try{const s="function"==typeof e.url?await e.url(l):e.url;if(f.signal.aborted)return;const c="function"==typeof e.headers?await e.headers():null!==(a=e.headers)&&void 0!==a?a:{};if(f.signal.aborted)return;let u;try{u=await i(s,{signal:f.signal,method:"POST",headers:Object.assign(Object.assign({},c),{"content-type":"application/json; charset=utf-8",accept:"application/graphql+json, application/json"}),credentials:t,referrer:o,referrerPolicy:n,body:JSON.stringify(l)})}catch(e){throw new r(e)}if(!u.ok)throw new r(u);if(!u.body)throw new Error("Missing response body");const d=u.headers.get("content-type");if(!d)throw new Error("Missing response content-type");if(!d.includes("application/graphql+json")&&!d.includes("application/json"))throw new Error(`Unsupported response content-type ${d}`);const h=await u.json();return p.next(h),f.abort()}catch(e){if(f.signal.aborted)return;if(!(e instanceof r))throw e;c=e}}})().then((()=>p.complete())).catch((e=>p.error(e))),()=>f.abort()},dispose(){c.dispose()}}},e.isResponse=function(e){return Array.isArray(e)&&("string"==typeof e[0]||null===e[0])&&t(e[1])},Object.defineProperty(e,"__esModule",{value:!0})}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).graphqlHttp={})}(this,(function(e){"use strict";class t extends Error{constructor(e){let t,r;var o;(function(e){return"object"==typeof e&&null!==e})(o=e)&&"boolean"==typeof o.ok&&"number"==typeof o.status&&"string"==typeof o.statusText?(r=e,t="Server responded with "+e.status+": "+e.statusText):t=e instanceof Error?e.message:String(e),super(t),this.name=this.constructor.name,this.response=r}}e.NetworkError=t,e.createClient=function(e){const{credentials:r="same-origin",referrer:o,referrerPolicy:n,shouldRetry:s=(()=>!1)}=e,i=e.fetchFn||fetch,a=e.abortControllerImpl||AbortController,c=(()=>{let e=!1;const t=[];return{get disposed(){return e},onDispose:r=>e?(setTimeout((()=>r()),0),()=>{}):(t.push(r),()=>{t.splice(t.indexOf(r),1)}),dispose(){if(!e){e=!0;for(const e of[...t])e()}}}})();return{subscribe(p,l){if(c.disposed)throw new Error("Client has been disposed");const f=new a,u=c.onDispose((()=>{u(),f.abort()}));return(async()=>{var a;let c=null,u=0;for(;;){if(c){const e=await s(c,u);if(f.signal.aborted)return;if(!e)throw c;u++}try{const s="function"==typeof e.url?await e.url(p):e.url;if(f.signal.aborted)return;const c="function"==typeof e.headers?await e.headers():null!==(a=e.headers)&&void 0!==a?a:{};if(f.signal.aborted)return;let u;try{u=await i(s,{signal:f.signal,method:"POST",headers:Object.assign(Object.assign({},c),{"content-type":"application/json; charset=utf-8",accept:"application/graphql-response+json, application/json"}),credentials:r,referrer:o,referrerPolicy:n,body:JSON.stringify(p)})}catch(e){throw new t(e)}if(!u.ok)throw new t(u);if(!u.body)throw new Error("Missing response body");const d=u.headers.get("content-type");if(!d)throw new Error("Missing response content-type");if(!d.includes("application/graphql-response+json")&&!d.includes("application/json"))throw new Error(`Unsupported response content-type ${d}`);const h=await u.json();return l.next(h),f.abort()}catch(e){if(f.signal.aborted)return;if(!(e instanceof t))throw e;c=e}}})().then((()=>l.complete())).catch((e=>l.error(e))),()=>f.abort()},dispose(){c.dispose()}}},Object.defineProperty(e,"__esModule",{value:!0})}));
Binary file