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/README.md +53 -1
- package/lib/client.d.ts +11 -3
- package/lib/client.js +3 -3
- package/lib/client.mjs +3 -3
- package/lib/common.d.ts +0 -67
- package/lib/common.js +0 -12
- package/lib/common.mjs +1 -10
- package/lib/handler.d.ts +133 -9
- package/lib/handler.js +136 -97
- package/lib/handler.mjs +129 -93
- package/lib/utils.d.ts +7 -0
- package/lib/utils.js +21 -1
- package/lib/utils.mjs +17 -0
- package/package.json +7 -7
- package/umd/graphql-http.js +3 -19
- package/umd/graphql-http.min.js +1 -1
- package/umd/graphql-http.min.js.gz +0 -0
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
|
|
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
|
-
|
|
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
|
|
121
|
+
const [, search] = req.url.split('?');
|
|
122
|
+
const searchParams = new URLSearchParams(search);
|
|
131
123
|
partParams.operationName =
|
|
132
|
-
(_a =
|
|
133
|
-
partParams.query = (_b =
|
|
134
|
-
const 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 =
|
|
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 (
|
|
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
|
|
200
|
-
if (
|
|
201
|
-
return
|
|
202
|
-
else if (
|
|
203
|
-
|
|
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 =
|
|
201
|
+
document = parse(query);
|
|
211
202
|
}
|
|
212
|
-
catch (
|
|
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 (
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
296
|
-
if (
|
|
297
|
-
return
|
|
298
|
-
else if (
|
|
299
|
-
result =
|
|
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(
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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
|
|
117
|
+
const [, search] = req.url.split('?');
|
|
118
|
+
const searchParams = new URLSearchParams(search);
|
|
128
119
|
partParams.operationName =
|
|
129
|
-
(_a =
|
|
130
|
-
partParams.query = (_b =
|
|
131
|
-
const 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 =
|
|
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 (
|
|
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
|
|
197
|
-
if (isResponse(
|
|
198
|
-
return
|
|
199
|
-
else if (
|
|
200
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
293
|
-
if (isResponse(
|
|
294
|
-
return
|
|
295
|
-
else if (
|
|
296
|
-
result =
|
|
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(
|
|
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>;
|