graphql-http 1.0.0 → 1.3.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/lib/handler.js CHANGED
@@ -5,9 +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");
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;
11
23
  /**
12
24
  * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
13
25
  * be used with your favourite server library.
@@ -64,7 +76,7 @@ const common_1 = require("./common");
64
76
  * @category Server
65
77
  */
66
78
  function createHandler(options) {
67
- const { schema, context, validate = graphql_1.validate, execute = graphql_1.execute, onSubscribe, onOperation, } = options;
79
+ const { schema, context, validate = graphql_1.validate, execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, onSubscribe, onOperation, } = options;
68
80
  return async function handler(req) {
69
81
  var _a, _b;
70
82
  const method = req.method;
@@ -80,28 +92,7 @@ function createHandler(options) {
80
92
  },
81
93
  ];
82
94
  }
83
- let acceptedMediaType;
84
- const accepts = (req.headers.accept || '*/*')
85
- .replace(/\s/g, '')
86
- .toLowerCase()
87
- .split(',');
88
- for (const accept of accepts) {
89
- // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
90
- // TODO: handle the weight parameter "q"
91
- const [mediaType, ...params] = accept.split(';');
92
- const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
93
- if (mediaType === 'application/json' && charset === 'charset=utf8') {
94
- acceptedMediaType = 'application/json';
95
- break;
96
- }
97
- if ((mediaType === 'application/graphql+json' ||
98
- mediaType === 'application/*' ||
99
- mediaType === '*/*') &&
100
- charset === 'charset=utf8') {
101
- acceptedMediaType = 'application/graphql+json';
102
- break;
103
- }
104
- }
95
+ const acceptedMediaType = getAcceptableMediaType(req.headers.accept);
105
96
  if (!acceptedMediaType) {
106
97
  return [
107
98
  null,
@@ -109,7 +100,7 @@ function createHandler(options) {
109
100
  status: 406,
110
101
  statusText: 'Not Acceptable',
111
102
  headers: {
112
- 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',
113
104
  },
114
105
  },
115
106
  ];
@@ -127,14 +118,15 @@ function createHandler(options) {
127
118
  case method === 'GET': {
128
119
  // TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
129
120
  try {
130
- const url = new URL(req.url || '', 'http://localhost/');
121
+ const [, search] = req.url.split('?');
122
+ const searchParams = new URLSearchParams(search);
131
123
  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');
124
+ (_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
125
+ partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
126
+ const variables = searchParams.get('variables');
135
127
  if (variables)
136
128
  partParams.variables = JSON.parse(variables);
137
- const extensions = url.searchParams.get('extensions');
129
+ const extensions = searchParams.get('extensions');
138
130
  if (extensions)
139
131
  partParams.extensions = JSON.parse(extensions);
140
132
  }
@@ -171,8 +163,10 @@ function createHandler(options) {
171
163
  },
172
164
  ];
173
165
  }
174
- if (!partParams.query)
166
+ if (partParams.query == null)
175
167
  throw new Error('Missing query');
168
+ if (typeof partParams.query !== 'string')
169
+ throw new Error('Invalid query');
176
170
  if (partParams.variables != null &&
177
171
  (typeof partParams.variables !== 'object' ||
178
172
  Array.isArray(partParams.variables))) {
@@ -187,36 +181,27 @@ function createHandler(options) {
187
181
  params = partParams;
188
182
  }
189
183
  catch (err) {
190
- return [
191
- err.message,
192
- {
193
- status: acceptedMediaType === 'application/json' ? 200 : 400,
194
- statusText: 'Bad Request',
195
- },
196
- ];
184
+ return makeResponse(new graphql_1.GraphQLError(err.message), acceptedMediaType);
197
185
  }
198
186
  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;
187
+ const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
188
+ if (isResponse(maybeResErrsOrArgs))
189
+ return maybeResErrsOrArgs;
190
+ else if ((0, utils_1.isExecutionResult)(maybeResErrsOrArgs) ||
191
+ (0, utils_1.areGraphQLErrors)(maybeResErrsOrArgs))
192
+ return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
193
+ else if (maybeResErrsOrArgs)
194
+ args = maybeResErrsOrArgs;
204
195
  else {
205
196
  if (!schema)
206
197
  throw new Error('The GraphQL schema is not provided');
207
198
  const { operationName, query, variables } = params;
208
199
  let document;
209
200
  try {
210
- document = (0, graphql_1.parse)(query);
201
+ document = parse(query);
211
202
  }
212
- catch (_e) {
213
- return [
214
- 'GraphQL query syntax error',
215
- {
216
- status: acceptedMediaType === 'application/json' ? 200 : 400,
217
- statusText: 'Bad Request',
218
- },
219
- ];
203
+ catch (err) {
204
+ return makeResponse(err, acceptedMediaType);
220
205
  }
221
206
  const argsWithoutSchema = {
222
207
  operationName,
@@ -225,44 +210,38 @@ function createHandler(options) {
225
210
  };
226
211
  if (typeof schema === 'function') {
227
212
  const resOrSchema = await schema(req, argsWithoutSchema);
228
- if ((0, common_1.isResponse)(resOrSchema))
213
+ if (isResponse(resOrSchema))
229
214
  return resOrSchema;
230
215
  args = Object.assign(Object.assign({}, argsWithoutSchema), { schema: resOrSchema });
231
216
  }
232
217
  else {
233
218
  args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
234
219
  }
220
+ const validationErrs = validate(args.schema, args.document);
221
+ if (validationErrs.length) {
222
+ return makeResponse(validationErrs, acceptedMediaType);
223
+ }
235
224
  }
236
225
  let operation;
237
226
  try {
238
- const ast = (0, graphql_1.getOperationAST)(args.document, args.operationName);
227
+ const ast = getOperationAST(args.document, args.operationName);
239
228
  if (!ast)
240
229
  throw null;
241
230
  operation = ast.operation;
242
231
  }
243
- catch (_f) {
244
- return [
245
- 'Unable to detect operation AST',
246
- {
247
- status: acceptedMediaType === 'application/json' ? 200 : 400,
248
- statusText: 'Bad Request',
249
- },
250
- ];
232
+ catch (_e) {
233
+ return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType);
251
234
  }
252
235
  if (operation === 'subscription') {
253
- return [
254
- 'Subscriptions are not supported',
255
- {
256
- status: 400,
257
- statusText: 'Bad Request',
258
- },
259
- ];
236
+ return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
260
237
  }
261
238
  // mutations cannot happen over GETs
262
239
  // https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
263
240
  if (operation === 'mutation' && method === 'GET') {
264
241
  return [
265
- 'Cannot perform mutations over GET',
242
+ JSON.stringify({
243
+ errors: [new graphql_1.GraphQLError('Cannot perform mutations over GET')],
244
+ }),
266
245
  {
267
246
  status: 405,
268
247
  statusText: 'Method Not Allowed',
@@ -273,42 +252,102 @@ function createHandler(options) {
273
252
  ];
274
253
  }
275
254
  if (!('contextValue' in args)) {
276
- args.contextValue =
277
- typeof context === 'function' ? await context(req, args) : context;
278
- }
279
- const validationErrs = validate(args.schema, args.document);
280
- if (validationErrs.length) {
281
- return [
282
- JSON.stringify({ errors: validationErrs }),
283
- {
284
- status: acceptedMediaType === 'application/json' ? 200 : 400,
285
- statusText: 'Bad Request',
286
- headers: {
287
- 'content-type': acceptedMediaType === 'application/json'
288
- ? 'application/json; charset=utf-8'
289
- : 'application/graphql+json; charset=utf-8',
290
- },
291
- },
292
- ];
255
+ const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
256
+ if (isResponse(maybeResOrContext))
257
+ return maybeResOrContext;
258
+ args.contextValue = maybeResOrContext;
293
259
  }
294
260
  let result = await execute(args);
295
- const maybeResOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
296
- if ((0, common_1.isResponse)(maybeResOrResult))
297
- return maybeResOrResult;
298
- else if (maybeResOrResult)
299
- result = maybeResOrResult;
261
+ const maybeResponseOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
262
+ if (isResponse(maybeResponseOrResult))
263
+ return maybeResponseOrResult;
264
+ else if (maybeResponseOrResult)
265
+ result = maybeResponseOrResult;
266
+ if ((0, utils_1.isAsyncIterable)(result)) {
267
+ return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
268
+ }
269
+ return makeResponse(result, acceptedMediaType);
270
+ };
271
+ }
272
+ exports.createHandler = createHandler;
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
+ 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
+ exports.getAcceptableMediaType = getAcceptableMediaType;
305
+ /**
306
+ * Creates an appropriate GraphQL over HTTP response following the provided arguments.
307
+ *
308
+ * If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
309
+ *
310
+ * If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
311
+ * and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
312
+ *
313
+ * @category Server
314
+ */
315
+ function makeResponse(resultOrErrors, acceptedMediaType) {
316
+ if ((0, utils_1.isExecutionResult)(resultOrErrors)) {
300
317
  return [
301
- JSON.stringify(result),
318
+ JSON.stringify(resultOrErrors),
302
319
  {
303
320
  status: 200,
304
321
  statusText: 'OK',
305
322
  headers: {
306
323
  'content-type': acceptedMediaType === 'application/json'
307
324
  ? 'application/json; charset=utf-8'
308
- : 'application/graphql+json; charset=utf-8',
325
+ : 'application/graphql-response+json; charset=utf-8',
309
326
  },
310
327
  },
311
328
  ];
312
- };
329
+ }
330
+ return [
331
+ JSON.stringify({
332
+ errors: Array.isArray(resultOrErrors)
333
+ ? (0, utils_1.isObject)(resultOrErrors)
334
+ ? resultOrErrors
335
+ : new graphql_1.GraphQLError(String(resultOrErrors))
336
+ : [resultOrErrors],
337
+ }),
338
+ Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
339
+ ? {
340
+ status: 200,
341
+ statusText: 'OK',
342
+ }
343
+ : {
344
+ status: 400,
345
+ statusText: 'Bad Request',
346
+ })), { headers: {
347
+ 'content-type': acceptedMediaType === 'application/json'
348
+ ? 'application/json; charset=utf-8'
349
+ : 'application/graphql-response+json; charset=utf-8',
350
+ } }),
351
+ ];
313
352
  }
314
- exports.createHandler = createHandler;
353
+ exports.makeResponse = makeResponse;
package/lib/handler.mjs CHANGED
@@ -3,8 +3,19 @@
3
3
  * handler
4
4
  *
5
5
  */
6
- import { validate as graphqlValidate, execute as graphqlExecute, parse, getOperationAST, } from 'graphql';
7
- import { isResponse } from './common.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
+ }
8
19
  /**
9
20
  * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
10
21
  * be used with your favourite server library.
@@ -61,7 +72,7 @@ import { isResponse } from './common.mjs';
61
72
  * @category Server
62
73
  */
63
74
  export function createHandler(options) {
64
- const { schema, context, validate = graphqlValidate, execute = graphqlExecute, onSubscribe, onOperation, } = options;
75
+ const { schema, context, validate = graphqlValidate, execute = graphqlExecute, parse = graphqlParse, getOperationAST = graphqlGetOperationAST, onSubscribe, onOperation, } = options;
65
76
  return async function handler(req) {
66
77
  var _a, _b;
67
78
  const method = req.method;
@@ -77,28 +88,7 @@ export function createHandler(options) {
77
88
  },
78
89
  ];
79
90
  }
80
- let acceptedMediaType;
81
- const accepts = (req.headers.accept || '*/*')
82
- .replace(/\s/g, '')
83
- .toLowerCase()
84
- .split(',');
85
- for (const accept of accepts) {
86
- // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
87
- // TODO: handle the weight parameter "q"
88
- const [mediaType, ...params] = accept.split(';');
89
- const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
90
- if (mediaType === 'application/json' && charset === 'charset=utf8') {
91
- acceptedMediaType = 'application/json';
92
- break;
93
- }
94
- if ((mediaType === 'application/graphql+json' ||
95
- mediaType === 'application/*' ||
96
- mediaType === '*/*') &&
97
- charset === 'charset=utf8') {
98
- acceptedMediaType = 'application/graphql+json';
99
- break;
100
- }
101
- }
91
+ const acceptedMediaType = getAcceptableMediaType(req.headers.accept);
102
92
  if (!acceptedMediaType) {
103
93
  return [
104
94
  null,
@@ -106,7 +96,7 @@ export function createHandler(options) {
106
96
  status: 406,
107
97
  statusText: 'Not Acceptable',
108
98
  headers: {
109
- 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',
110
100
  },
111
101
  },
112
102
  ];
@@ -124,14 +114,15 @@ export function createHandler(options) {
124
114
  case method === 'GET': {
125
115
  // TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
126
116
  try {
127
- const url = new URL(req.url || '', 'http://localhost/');
117
+ const [, search] = req.url.split('?');
118
+ const searchParams = new URLSearchParams(search);
128
119
  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');
120
+ (_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
121
+ partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
122
+ const variables = searchParams.get('variables');
132
123
  if (variables)
133
124
  partParams.variables = JSON.parse(variables);
134
- const extensions = url.searchParams.get('extensions');
125
+ const extensions = searchParams.get('extensions');
135
126
  if (extensions)
136
127
  partParams.extensions = JSON.parse(extensions);
137
128
  }
@@ -168,8 +159,10 @@ export function createHandler(options) {
168
159
  },
169
160
  ];
170
161
  }
171
- if (!partParams.query)
162
+ if (partParams.query == null)
172
163
  throw new Error('Missing query');
164
+ if (typeof partParams.query !== 'string')
165
+ throw new Error('Invalid query');
173
166
  if (partParams.variables != null &&
174
167
  (typeof partParams.variables !== 'object' ||
175
168
  Array.isArray(partParams.variables))) {
@@ -184,20 +177,17 @@ export function createHandler(options) {
184
177
  params = partParams;
185
178
  }
186
179
  catch (err) {
187
- return [
188
- err.message,
189
- {
190
- status: acceptedMediaType === 'application/json' ? 200 : 400,
191
- statusText: 'Bad Request',
192
- },
193
- ];
180
+ return makeResponse(new GraphQLError(err.message), acceptedMediaType);
194
181
  }
195
182
  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;
183
+ const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
184
+ if (isResponse(maybeResErrsOrArgs))
185
+ return maybeResErrsOrArgs;
186
+ else if (isExecutionResult(maybeResErrsOrArgs) ||
187
+ areGraphQLErrors(maybeResErrsOrArgs))
188
+ return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
189
+ else if (maybeResErrsOrArgs)
190
+ args = maybeResErrsOrArgs;
201
191
  else {
202
192
  if (!schema)
203
193
  throw new Error('The GraphQL schema is not provided');
@@ -206,14 +196,8 @@ export function createHandler(options) {
206
196
  try {
207
197
  document = parse(query);
208
198
  }
209
- catch (_e) {
210
- return [
211
- 'GraphQL query syntax error',
212
- {
213
- status: acceptedMediaType === 'application/json' ? 200 : 400,
214
- statusText: 'Bad Request',
215
- },
216
- ];
199
+ catch (err) {
200
+ return makeResponse(err, acceptedMediaType);
217
201
  }
218
202
  const argsWithoutSchema = {
219
203
  operationName,
@@ -229,6 +213,10 @@ export function createHandler(options) {
229
213
  else {
230
214
  args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
231
215
  }
216
+ const validationErrs = validate(args.schema, args.document);
217
+ if (validationErrs.length) {
218
+ return makeResponse(validationErrs, acceptedMediaType);
219
+ }
232
220
  }
233
221
  let operation;
234
222
  try {
@@ -237,29 +225,19 @@ export function createHandler(options) {
237
225
  throw null;
238
226
  operation = ast.operation;
239
227
  }
240
- catch (_f) {
241
- return [
242
- 'Unable to detect operation AST',
243
- {
244
- status: acceptedMediaType === 'application/json' ? 200 : 400,
245
- statusText: 'Bad Request',
246
- },
247
- ];
228
+ catch (_e) {
229
+ return makeResponse(new GraphQLError('Unable to detect operation AST'), acceptedMediaType);
248
230
  }
249
231
  if (operation === 'subscription') {
250
- return [
251
- 'Subscriptions are not supported',
252
- {
253
- status: 400,
254
- statusText: 'Bad Request',
255
- },
256
- ];
232
+ return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
257
233
  }
258
234
  // mutations cannot happen over GETs
259
235
  // https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
260
236
  if (operation === 'mutation' && method === 'GET') {
261
237
  return [
262
- 'Cannot perform mutations over GET',
238
+ JSON.stringify({
239
+ errors: [new GraphQLError('Cannot perform mutations over GET')],
240
+ }),
263
241
  {
264
242
  status: 405,
265
243
  statusText: 'Method Not Allowed',
@@ -270,41 +248,99 @@ export function createHandler(options) {
270
248
  ];
271
249
  }
272
250
  if (!('contextValue' in args)) {
273
- args.contextValue =
274
- typeof context === 'function' ? await context(req, args) : context;
275
- }
276
- const validationErrs = validate(args.schema, args.document);
277
- if (validationErrs.length) {
278
- return [
279
- JSON.stringify({ errors: validationErrs }),
280
- {
281
- status: acceptedMediaType === 'application/json' ? 200 : 400,
282
- statusText: 'Bad Request',
283
- headers: {
284
- 'content-type': acceptedMediaType === 'application/json'
285
- ? 'application/json; charset=utf-8'
286
- : 'application/graphql+json; charset=utf-8',
287
- },
288
- },
289
- ];
251
+ const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
252
+ if (isResponse(maybeResOrContext))
253
+ return maybeResOrContext;
254
+ args.contextValue = maybeResOrContext;
290
255
  }
291
256
  let result = await execute(args);
292
- const maybeResOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
293
- if (isResponse(maybeResOrResult))
294
- return maybeResOrResult;
295
- else if (maybeResOrResult)
296
- result = maybeResOrResult;
257
+ const maybeResponseOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
258
+ if (isResponse(maybeResponseOrResult))
259
+ return maybeResponseOrResult;
260
+ else if (maybeResponseOrResult)
261
+ result = maybeResponseOrResult;
262
+ if (isAsyncIterable(result)) {
263
+ return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
264
+ }
265
+ return makeResponse(result, acceptedMediaType);
266
+ };
267
+ }
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 function getAcceptableMediaType(acceptHeader) {
275
+ let acceptedMediaType = null;
276
+ const accepts = (acceptHeader || '*/*')
277
+ .replace(/\s/g, '')
278
+ .toLowerCase()
279
+ .split(',');
280
+ for (const accept of accepts) {
281
+ // accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
282
+ // TODO: handle the weight parameter "q"
283
+ const [mediaType, ...params] = accept.split(';');
284
+ const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
285
+ if (mediaType === 'application/json' && charset === 'charset=utf8') {
286
+ acceptedMediaType = 'application/json';
287
+ break;
288
+ }
289
+ if ((mediaType === 'application/graphql-response+json' ||
290
+ mediaType === 'application/*' ||
291
+ mediaType === '*/*') &&
292
+ charset === 'charset=utf8') {
293
+ acceptedMediaType = 'application/graphql-response+json';
294
+ break;
295
+ }
296
+ }
297
+ return acceptedMediaType;
298
+ }
299
+ /**
300
+ * Creates an appropriate GraphQL over HTTP response following the provided arguments.
301
+ *
302
+ * If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
303
+ *
304
+ * If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
305
+ * and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
306
+ *
307
+ * @category Server
308
+ */
309
+ export function makeResponse(resultOrErrors, acceptedMediaType) {
310
+ if (isExecutionResult(resultOrErrors)) {
297
311
  return [
298
- JSON.stringify(result),
312
+ JSON.stringify(resultOrErrors),
299
313
  {
300
314
  status: 200,
301
315
  statusText: 'OK',
302
316
  headers: {
303
317
  'content-type': acceptedMediaType === 'application/json'
304
318
  ? 'application/json; charset=utf-8'
305
- : 'application/graphql+json; charset=utf-8',
319
+ : 'application/graphql-response+json; charset=utf-8',
306
320
  },
307
321
  },
308
322
  ];
309
- };
323
+ }
324
+ return [
325
+ JSON.stringify({
326
+ errors: Array.isArray(resultOrErrors)
327
+ ? isObject(resultOrErrors)
328
+ ? resultOrErrors
329
+ : new GraphQLError(String(resultOrErrors))
330
+ : [resultOrErrors],
331
+ }),
332
+ Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
333
+ ? {
334
+ status: 200,
335
+ statusText: 'OK',
336
+ }
337
+ : {
338
+ status: 400,
339
+ statusText: 'Bad Request',
340
+ })), { headers: {
341
+ 'content-type': acceptedMediaType === 'application/json'
342
+ ? 'application/json; charset=utf-8'
343
+ : 'application/graphql-response+json; charset=utf-8',
344
+ } }),
345
+ ];
310
346
  }
package/lib/utils.d.ts CHANGED
@@ -3,5 +3,12 @@
3
3
  * utils
4
4
  *
5
5
  */
6
+ import type { ExecutionResult, 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 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>;