graphql-http 1.0.0 → 1.1.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
@@ -3,7 +3,7 @@
3
3
 
4
4
  <h3>graphql-http</h3>
5
5
 
6
- <h6>Simple, plugable, zero-dependency, <a href="https://graphql.github.io/graphql-over-http">GraphQL over HTTP Protocol</a> compliant server and client.</h6>
6
+ <h6>Simple, pluggable, zero-dependency, <a href="https://graphql.github.io/graphql-over-http">GraphQL over HTTP Protocol</a> compliant server and client.</h6>
7
7
 
8
8
  [![Continuous integration](https://github.com/enisdenjo/graphql-http/workflows/Continuous%20integration/badge.svg)](https://github.com/enisdenjo/graphql-http/actions?query=workflow%3A%22Continuous+integration%22) [![graphql-http](https://img.shields.io/npm/v/graphql-http.svg?label=graphql-http&logo=npm)](https://www.npmjs.com/package/graphql-http)
9
9
 
@@ -207,6 +207,41 @@ fastify.listen(4000);
207
207
  console.log('Listening to port 4000');
208
208
  ```
209
209
 
210
+ ##### With [`Deno`](https://deno.land/)
211
+
212
+ ```ts
213
+ import { serve } from 'https://deno.land/std@0.151.0/http/server.ts';
214
+ import { createHandler } from 'https://esm.sh/graphql-http';
215
+ import { schema } from './previous-step';
216
+
217
+ const handler = createHandler<Request>({ schema });
218
+
219
+ await serve(
220
+ async (req: Request) => {
221
+ const [path, _search] = req.url.split('?');
222
+ if (!path.endsWith('/graphql')) {
223
+ return new Response(null, { status: 404, statusText: 'Not Found' });
224
+ }
225
+
226
+ const headers: Record<string, string> = {};
227
+ req.headers.forEach((value, key) => (headers[key] = value));
228
+ const [body, init] = await handler({
229
+ url: req.url,
230
+ method: req.method,
231
+ headers,
232
+ body: await req.text(),
233
+ raw: req,
234
+ });
235
+ return new Response(body, init);
236
+ },
237
+ {
238
+ port: 4000,
239
+ },
240
+ );
241
+
242
+ // Listening to port 4000
243
+ ```
244
+
210
245
  #### Use the client
211
246
 
212
247
  ```js
@@ -515,6 +550,21 @@ const client = createClient({
515
550
 
516
551
  </details>
517
552
 
553
+ <details id="deno-client">
554
+ <summary><a href="#deno-client">🔗</a> Client usage in Deno</summary>
555
+
556
+ ```js
557
+ import { createClient } from 'graphql-http';
558
+
559
+ const client = createClient({
560
+ url: 'http://deno.earth:4000/graphql',
561
+ });
562
+
563
+ // consider other recipes for usage inspiration
564
+ ```
565
+
566
+ </details>
567
+
518
568
  <details id="auth">
519
569
  <summary><a href="#auth">🔗</a> Server handler usage with authentication</summary>
520
570
 
package/lib/client.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * client
4
4
  *
5
5
  */
6
- import { ExecutionResult } from 'graphql';
6
+ import type { ExecutionResult } from 'graphql';
7
7
  import { RequestParams, Sink } from './common';
8
8
  /** This file is the entry point for browsers, re-export common elements. */
9
9
  export * from './common';
@@ -18,8 +18,11 @@ export interface ClientOptions {
18
18
  *
19
19
  * A good use-case for having a function is when using the URL for authentication,
20
20
  * where subsequent requests (due to auth) may have a refreshed identity token.
21
+ *
22
+ * Function receives the request params. Useful for example, to ease up debugging and DevTools
23
+ * navigation you might want to use the operation name in the URL's search params (`/graphql?MyQuery`).
21
24
  */
22
- url: string | (() => Promise<string> | string);
25
+ url: string | ((request: RequestParams) => Promise<string> | string);
23
26
  /**
24
27
  * Indicates whether the user agent should send cookies from the other domain in the case
25
28
  * of cross-origin requests.
@@ -140,7 +143,12 @@ export declare class NetworkError<Response extends ResponseLike = ResponseLike>
140
143
  response: Response | undefined;
141
144
  constructor(msgOrErrOrResponse: string | Error | Response);
142
145
  }
143
- interface ResponseLike {
146
+ /**
147
+ * Concrete interface a response needs to implement for the client.
148
+ *
149
+ * @category Client
150
+ */
151
+ export interface ResponseLike {
144
152
  readonly ok: boolean;
145
153
  readonly status: number;
146
154
  readonly statusText: string;
package/lib/client.js CHANGED
@@ -91,7 +91,7 @@ function createClient(options) {
91
91
  }
92
92
  try {
93
93
  const url = typeof options.url === 'function'
94
- ? await options.url()
94
+ ? await options.url(request)
95
95
  : options.url;
96
96
  if (control.signal.aborted)
97
97
  return;
package/lib/client.mjs CHANGED
@@ -74,7 +74,7 @@ export function createClient(options) {
74
74
  }
75
75
  try {
76
76
  const url = typeof options.url === 'function'
77
- ? await options.url()
77
+ ? await options.url(request)
78
78
  : options.url;
79
79
  if (control.signal.aborted)
80
80
  return;
package/lib/common.d.ts CHANGED
@@ -4,11 +4,11 @@
4
4
  *
5
5
  */
6
6
  /**
7
- * Concrete interface that the headers map should implement.
7
+ * The incoming request headers the implementing server should provide.
8
8
  *
9
9
  * @category Common
10
10
  */
11
- export interface Headers {
11
+ export interface RequestHeaders {
12
12
  accept?: string | undefined;
13
13
  allow?: string | undefined;
14
14
  'content-type'?: string | undefined;
@@ -29,7 +29,7 @@ export interface Headers {
29
29
  export interface Request<RawRequest> {
30
30
  readonly method: string;
31
31
  readonly url: string;
32
- readonly headers: Headers;
32
+ readonly headers: RequestHeaders;
33
33
  readonly body: string | Record<string, unknown> | null;
34
34
  /**
35
35
  * The raw request itself from the implementing server.
@@ -52,6 +52,16 @@ export interface RequestParams {
52
52
  variables?: Record<string, unknown> | undefined;
53
53
  extensions?: Record<string, unknown> | undefined;
54
54
  }
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>;
55
65
  /**
56
66
  * Server agnostic response body returned from `graphql-http` needing
57
67
  * to be coerced to the server implementation in use.
@@ -67,8 +77,8 @@ export declare type ResponseBody = string;
67
77
  */
68
78
  export interface ResponseInit {
69
79
  readonly status: number;
70
- readonly statusText?: string;
71
- readonly headers?: Headers;
80
+ readonly statusText: string;
81
+ readonly headers?: ResponseHeaders;
72
82
  }
73
83
  /**
74
84
  * Server agnostic response returned from `graphql-http` containing the
package/lib/common.js CHANGED
@@ -14,6 +14,8 @@ const utils_1 = require("./utils");
14
14
  */
15
15
  function isResponse(val) {
16
16
  // TODO: make sure the contents of init match ResponseInit
17
- return Array.isArray(val) && typeof val[0] === 'string' && (0, utils_1.isObject)(val[1]);
17
+ return (Array.isArray(val) &&
18
+ (typeof val[0] === 'string' || val[0] === null) &&
19
+ (0, utils_1.isObject)(val[1]));
18
20
  }
19
21
  exports.isResponse = isResponse;
package/lib/common.mjs CHANGED
@@ -11,5 +11,7 @@ import { isObject } from './utils.mjs';
11
11
  */
12
12
  export function isResponse(val) {
13
13
  // TODO: make sure the contents of init match ResponseInit
14
- return Array.isArray(val) && typeof val[0] === 'string' && isObject(val[1]);
14
+ return (Array.isArray(val) &&
15
+ (typeof val[0] === 'string' || val[0] === null) &&
16
+ isObject(val[1]));
15
17
  }
package/lib/handler.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * handler
4
4
  *
5
5
  */
6
- import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, execute as graphqlExecute } from 'graphql';
6
+ import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError } from 'graphql';
7
7
  import { Request, RequestParams, Response } from './common';
8
8
  /**
9
9
  * A concrete GraphQL execution context value type.
@@ -51,6 +51,14 @@ export interface HandlerOptions<RawRequest = unknown> {
51
51
  * used to execute the query and mutation operations.
52
52
  */
53
53
  execute?: typeof graphqlExecute;
54
+ /**
55
+ * GraphQL parse function allowing you to apply a custom parser.
56
+ */
57
+ parse?: typeof graphqlParse;
58
+ /**
59
+ * GraphQL operation AST getter used for detecting the operation type.
60
+ */
61
+ getOperationAST?: typeof graphqlGetOperationAST;
54
62
  /**
55
63
  * The subscribe callback executed right after processing the request
56
64
  * before proceeding with the GraphQL operation execution.
@@ -59,6 +67,9 @@ export interface HandlerOptions<RawRequest = unknown> {
59
67
  * trying to build one internally. In this case, you are responsible for providing
60
68
  * a ready set of arguments which will be directly plugged in the operation execution.
61
69
  *
70
+ * If you return an array of `GraphQLError` from the callback, they will be reported
71
+ * to the client while complying with the spec.
72
+ *
62
73
  * Omitting the fields `contextValue` from the returned `ExecutionArgs` will use the
63
74
  * provided `context` option, if available.
64
75
  *
@@ -70,7 +81,7 @@ export interface HandlerOptions<RawRequest = unknown> {
70
81
  * you should do by returning a `Request` argument which will stop
71
82
  * further execution.
72
83
  */
73
- onSubscribe?: (req: Request<RawRequest>, params: RequestParams) => Promise<ExecutionArgs | Response | void> | ExecutionArgs | Response | void;
84
+ onSubscribe?: (req: Request<RawRequest>, params: RequestParams) => Promise<ExecutionArgs | GraphQLError[] | Response | void> | ExecutionArgs | GraphQLError[] | Response | void;
74
85
  /**
75
86
  * Executed after the operation call resolves.
76
87
  *
package/lib/handler.js CHANGED
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.createHandler = void 0;
9
9
  const graphql_1 = require("graphql");
10
10
  const common_1 = require("./common");
11
+ const utils_1 = require("./utils");
11
12
  /**
12
13
  * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
13
14
  * be used with your favourite server library.
@@ -64,7 +65,7 @@ const common_1 = require("./common");
64
65
  * @category Server
65
66
  */
66
67
  function createHandler(options) {
67
- const { schema, context, validate = graphql_1.validate, execute = graphql_1.execute, onSubscribe, onOperation, } = options;
68
+ const { schema, context, validate = graphql_1.validate, execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, onSubscribe, onOperation, } = options;
68
69
  return async function handler(req) {
69
70
  var _a, _b;
70
71
  const method = req.method;
@@ -127,14 +128,15 @@ function createHandler(options) {
127
128
  case method === 'GET': {
128
129
  // TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
129
130
  try {
130
- const url = new URL(req.url || '', 'http://localhost/');
131
+ const [, search] = req.url.split('?');
132
+ const searchParams = new URLSearchParams(search);
131
133
  partParams.operationName =
132
- (_a = url.searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
133
- partParams.query = (_b = url.searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
134
- const variables = url.searchParams.get('variables');
134
+ (_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
135
+ partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
136
+ const variables = searchParams.get('variables');
135
137
  if (variables)
136
138
  partParams.variables = JSON.parse(variables);
137
- const extensions = url.searchParams.get('extensions');
139
+ const extensions = searchParams.get('extensions');
138
140
  if (extensions)
139
141
  partParams.extensions = JSON.parse(extensions);
140
142
  }
@@ -189,33 +191,64 @@ function createHandler(options) {
189
191
  catch (err) {
190
192
  return [
191
193
  err.message,
192
- {
193
- status: acceptedMediaType === 'application/json' ? 200 : 400,
194
- statusText: 'Bad Request',
195
- },
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' }),
196
203
  ];
197
204
  }
198
205
  let args;
199
- const maybeResOrExecArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
200
- if ((0, common_1.isResponse)(maybeResOrExecArgs))
201
- return maybeResOrExecArgs;
202
- else if (maybeResOrExecArgs)
203
- args = maybeResOrExecArgs;
206
+ const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
207
+ if ((0, common_1.isResponse)(maybeResErrsOrArgs))
208
+ 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
+ ];
226
+ else if (maybeResErrsOrArgs)
227
+ args = maybeResErrsOrArgs;
204
228
  else {
205
229
  if (!schema)
206
230
  throw new Error('The GraphQL schema is not provided');
207
231
  const { operationName, query, variables } = params;
208
232
  let document;
209
233
  try {
210
- document = (0, graphql_1.parse)(query);
234
+ document = parse(query);
211
235
  }
212
- catch (_e) {
236
+ catch (err) {
213
237
  return [
214
- 'GraphQL query syntax error',
215
- {
216
- status: acceptedMediaType === 'application/json' ? 200 : 400,
217
- statusText: 'Bad Request',
218
- },
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
+ } }),
219
252
  ];
220
253
  }
221
254
  const argsWithoutSchema = {
@@ -235,18 +268,23 @@ function createHandler(options) {
235
268
  }
236
269
  let operation;
237
270
  try {
238
- const ast = (0, graphql_1.getOperationAST)(args.document, args.operationName);
271
+ const ast = getOperationAST(args.document, args.operationName);
239
272
  if (!ast)
240
273
  throw null;
241
274
  operation = ast.operation;
242
275
  }
243
- catch (_f) {
276
+ catch (_e) {
244
277
  return [
245
278
  'Unable to detect operation AST',
246
- {
247
- status: acceptedMediaType === 'application/json' ? 200 : 400,
248
- statusText: 'Bad Request',
249
- },
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' }),
250
288
  ];
251
289
  }
252
290
  if (operation === 'subscription') {
@@ -273,22 +311,28 @@ function createHandler(options) {
273
311
  ];
274
312
  }
275
313
  if (!('contextValue' in args)) {
276
- args.contextValue =
277
- typeof context === 'function' ? await context(req, args) : context;
314
+ const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
315
+ if ((0, common_1.isResponse)(maybeResOrContext))
316
+ return maybeResOrContext;
317
+ args.contextValue = maybeResOrContext;
278
318
  }
279
319
  const validationErrs = validate(args.schema, args.document);
280
320
  if (validationErrs.length) {
281
321
  return [
282
322
  JSON.stringify({ errors: validationErrs }),
283
- {
284
- status: acceptedMediaType === 'application/json' ? 200 : 400,
285
- statusText: 'Bad Request',
286
- headers: {
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: {
287
332
  'content-type': acceptedMediaType === 'application/json'
288
333
  ? 'application/json; charset=utf-8'
289
334
  : 'application/graphql+json; charset=utf-8',
290
- },
291
- },
335
+ } }),
292
336
  ];
293
337
  }
294
338
  let result = await execute(args);
package/lib/handler.mjs CHANGED
@@ -3,8 +3,9 @@
3
3
  * handler
4
4
  *
5
5
  */
6
- import { validate as graphqlValidate, execute as graphqlExecute, parse, getOperationAST, } from 'graphql';
6
+ import { validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, } from 'graphql';
7
7
  import { isResponse } from './common.mjs';
8
+ import { areGraphQLErrors } from './utils.mjs';
8
9
  /**
9
10
  * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
10
11
  * be used with your favourite server library.
@@ -61,7 +62,7 @@ import { isResponse } from './common.mjs';
61
62
  * @category Server
62
63
  */
63
64
  export function createHandler(options) {
64
- const { schema, context, validate = graphqlValidate, execute = graphqlExecute, onSubscribe, onOperation, } = options;
65
+ const { schema, context, validate = graphqlValidate, execute = graphqlExecute, parse = graphqlParse, getOperationAST = graphqlGetOperationAST, onSubscribe, onOperation, } = options;
65
66
  return async function handler(req) {
66
67
  var _a, _b;
67
68
  const method = req.method;
@@ -124,14 +125,15 @@ export function createHandler(options) {
124
125
  case method === 'GET': {
125
126
  // TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
126
127
  try {
127
- const url = new URL(req.url || '', 'http://localhost/');
128
+ const [, search] = req.url.split('?');
129
+ const searchParams = new URLSearchParams(search);
128
130
  partParams.operationName =
129
- (_a = url.searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
130
- partParams.query = (_b = url.searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
131
- const variables = url.searchParams.get('variables');
131
+ (_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
132
+ partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
133
+ const variables = searchParams.get('variables');
132
134
  if (variables)
133
135
  partParams.variables = JSON.parse(variables);
134
- const extensions = url.searchParams.get('extensions');
136
+ const extensions = searchParams.get('extensions');
135
137
  if (extensions)
136
138
  partParams.extensions = JSON.parse(extensions);
137
139
  }
@@ -186,18 +188,40 @@ export function createHandler(options) {
186
188
  catch (err) {
187
189
  return [
188
190
  err.message,
189
- {
190
- status: acceptedMediaType === 'application/json' ? 200 : 400,
191
- statusText: 'Bad Request',
192
- },
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' }),
193
200
  ];
194
201
  }
195
202
  let args;
196
- const maybeResOrExecArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
197
- if (isResponse(maybeResOrExecArgs))
198
- return maybeResOrExecArgs;
199
- else if (maybeResOrExecArgs)
200
- args = maybeResOrExecArgs;
203
+ const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
204
+ if (isResponse(maybeResErrsOrArgs))
205
+ 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
+ ];
223
+ else if (maybeResErrsOrArgs)
224
+ args = maybeResErrsOrArgs;
201
225
  else {
202
226
  if (!schema)
203
227
  throw new Error('The GraphQL schema is not provided');
@@ -206,13 +230,22 @@ export function createHandler(options) {
206
230
  try {
207
231
  document = parse(query);
208
232
  }
209
- catch (_e) {
233
+ catch (err) {
210
234
  return [
211
- 'GraphQL query syntax error',
212
- {
213
- status: acceptedMediaType === 'application/json' ? 200 : 400,
214
- statusText: 'Bad Request',
215
- },
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
+ } }),
216
249
  ];
217
250
  }
218
251
  const argsWithoutSchema = {
@@ -237,13 +270,18 @@ export function createHandler(options) {
237
270
  throw null;
238
271
  operation = ast.operation;
239
272
  }
240
- catch (_f) {
273
+ catch (_e) {
241
274
  return [
242
275
  'Unable to detect operation AST',
243
- {
244
- status: acceptedMediaType === 'application/json' ? 200 : 400,
245
- statusText: 'Bad Request',
246
- },
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' }),
247
285
  ];
248
286
  }
249
287
  if (operation === 'subscription') {
@@ -270,22 +308,28 @@ export function createHandler(options) {
270
308
  ];
271
309
  }
272
310
  if (!('contextValue' in args)) {
273
- args.contextValue =
274
- typeof context === 'function' ? await context(req, args) : context;
311
+ const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
312
+ if (isResponse(maybeResOrContext))
313
+ return maybeResOrContext;
314
+ args.contextValue = maybeResOrContext;
275
315
  }
276
316
  const validationErrs = validate(args.schema, args.document);
277
317
  if (validationErrs.length) {
278
318
  return [
279
319
  JSON.stringify({ errors: validationErrs }),
280
- {
281
- status: acceptedMediaType === 'application/json' ? 200 : 400,
282
- statusText: 'Bad Request',
283
- headers: {
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: {
284
329
  'content-type': acceptedMediaType === 'application/json'
285
330
  ? 'application/json; charset=utf-8'
286
331
  : 'application/graphql+json; charset=utf-8',
287
- },
288
- },
332
+ } }),
289
333
  ];
290
334
  }
291
335
  let result = await execute(args);
package/lib/utils.d.ts CHANGED
@@ -3,5 +3,8 @@
3
3
  * utils
4
4
  *
5
5
  */
6
+ import type { GraphQLError } from 'graphql';
6
7
  /** @private */
7
8
  export declare function isObject(val: unknown): val is Record<PropertyKey, unknown>;
9
+ /** @private */
10
+ export declare function areGraphQLErrors(obj: unknown): obj is GraphQLError[];
package/lib/utils.js CHANGED
@@ -5,9 +5,18 @@
5
5
  *
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.isObject = void 0;
8
+ exports.areGraphQLErrors = exports.isObject = void 0;
9
9
  /** @private */
10
10
  function isObject(val) {
11
11
  return typeof val === 'object' && val !== null;
12
12
  }
13
13
  exports.isObject = isObject;
14
+ /** @private */
15
+ function areGraphQLErrors(obj) {
16
+ return (Array.isArray(obj) &&
17
+ // must be at least one error
18
+ obj.length > 0 &&
19
+ // error has at least a message
20
+ obj.every((ob) => 'message' in ob));
21
+ }
22
+ exports.areGraphQLErrors = areGraphQLErrors;
package/lib/utils.mjs CHANGED
@@ -7,3 +7,11 @@
7
7
  export function isObject(val) {
8
8
  return typeof val === 'object' && val !== null;
9
9
  }
10
+ /** @private */
11
+ export function areGraphQLErrors(obj) {
12
+ return (Array.isArray(obj) &&
13
+ // must be at least one error
14
+ obj.length > 0 &&
15
+ // error has at least a message
16
+ obj.every((ob) => 'message' in ob));
17
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "graphql-http",
3
- "version": "1.0.0",
4
- "description": "Simple, plugable, zero-dependency, GraphQL over HTTP Protocol compliant server and client",
3
+ "version": "1.1.0",
4
+ "description": "Simple, pluggable, zero-dependency, GraphQL over HTTP Protocol compliant server and client",
5
5
  "keywords": [
6
6
  "graphql",
7
7
  "client",
@@ -26,7 +26,9 @@
26
26
  */
27
27
  function isResponse(val) {
28
28
  // TODO: make sure the contents of init match ResponseInit
29
- return Array.isArray(val) && typeof val[0] === 'string' && isObject(val[1]);
29
+ return (Array.isArray(val) &&
30
+ (typeof val[0] === 'string' || val[0] === null) &&
31
+ isObject(val[1]));
30
32
  }
31
33
 
32
34
  /**
@@ -102,7 +104,7 @@
102
104
  }
103
105
  try {
104
106
  const url = typeof options.url === 'function'
105
- ? await options.url()
107
+ ? await options.url(request)
106
108
  : options.url;
107
109
  if (control.signal.aborted)
108
110
  return;
@@ -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(p,f){if(c.disposed)throw new Error("Client has been disposed");const l=new a,u=c.onDispose((()=>{u(),l.abort()}));return(async()=>{var a;let c=null,u=0;for(;;){if(c){const e=await s(c,u);if(l.signal.aborted)return;if(!e)throw c;u++}try{const s="function"==typeof e.url?await e.url():e.url;if(l.signal.aborted)return;const c="function"==typeof e.headers?await e.headers():null!==(a=e.headers)&&void 0!==a?a:{};if(l.signal.aborted)return;let u;try{u=await i(s,{signal:l.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(p)})}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 f.next(h),l.abort()}catch(e){if(l.signal.aborted)return;if(!(e instanceof r))throw e;c=e}}})().then((()=>f.complete())).catch((e=>f.error(e))),()=>l.abort()},dispose(){c.dispose()}}},e.isResponse=function(e){return Array.isArray(e)&&"string"==typeof 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";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})}));
Binary file