graphql-http 1.19.0 → 1.21.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 +86 -4
- package/lib/client.d.mts +2 -2
- package/lib/client.d.ts +2 -2
- package/lib/common.d.mts +4 -4
- package/lib/common.d.ts +4 -4
- package/lib/handler.d.mts +30 -38
- package/lib/handler.d.ts +30 -38
- package/lib/handler.js +160 -140
- package/lib/handler.mjs +155 -132
- package/lib/use/@netlify/functions.d.mts +14 -0
- package/lib/use/@netlify/functions.d.ts +14 -0
- package/lib/use/@netlify/functions.js +37 -0
- package/lib/use/@netlify/functions.mjs +33 -0
- package/lib/utils.d.mts +0 -7
- package/lib/utils.d.ts +0 -7
- package/lib/utils.js +1 -29
- package/lib/utils.mjs +0 -25
- package/package.json +36 -37
- package/umd/graphql-http-audits.js +2 -2
- package/umd/graphql-http-audits.min.js +1 -1
- package/umd/graphql-http-audits.min.js.gz +0 -0
- package/umd/graphql-http.js +3 -2
- package/umd/graphql-http.min.js +1 -1
- package/umd/graphql-http.min.js.gz +0 -0
package/lib/handler.js
CHANGED
|
@@ -5,24 +5,19 @@
|
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.
|
|
8
|
+
exports.createHandler = void 0;
|
|
9
9
|
const graphql_1 = require("graphql");
|
|
10
10
|
const utils_1 = require("./utils");
|
|
11
|
-
/**
|
|
12
|
-
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
13
|
-
*
|
|
14
|
-
* @category Server
|
|
15
|
-
*/
|
|
11
|
+
/** Checks whether the passed value is the `graphql-http` server agnostic response. */
|
|
16
12
|
function isResponse(val) {
|
|
17
13
|
// TODO: make sure the contents of init match ResponseInit
|
|
18
14
|
return (Array.isArray(val) &&
|
|
19
15
|
(typeof val[0] === 'string' || val[0] === null) &&
|
|
20
16
|
(0, utils_1.isObject)(val[1]));
|
|
21
17
|
}
|
|
22
|
-
exports.isResponse = isResponse;
|
|
23
18
|
/**
|
|
24
19
|
* Makes a GraphQL over HTTP spec compliant server handler. The handler can
|
|
25
|
-
* be used with your
|
|
20
|
+
* be used with your favorite server library.
|
|
26
21
|
*
|
|
27
22
|
* Beware that the handler resolves only after the whole operation completes.
|
|
28
23
|
*
|
|
@@ -76,23 +71,32 @@ exports.isResponse = isResponse;
|
|
|
76
71
|
* @category Server
|
|
77
72
|
*/
|
|
78
73
|
function createHandler(options) {
|
|
79
|
-
const { schema, context, validate = graphql_1.validate, validationRules = [], execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, } = options;
|
|
74
|
+
const { schema, context, validate = graphql_1.validate, validationRules = [], execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, parseRequestParams = defaultParseRequestParams, } = options;
|
|
80
75
|
return async function handler(req) {
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
76
|
+
let acceptedMediaType = null;
|
|
77
|
+
const accepts = (getHeader(req, 'accept') || '*/*')
|
|
78
|
+
.replace(/\s/g, '')
|
|
79
|
+
.toLowerCase()
|
|
80
|
+
.split(',');
|
|
81
|
+
for (const accept of accepts) {
|
|
82
|
+
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
83
|
+
// TODO: handle the weight parameter "q"
|
|
84
|
+
const [mediaType, ...params] = accept.split(';');
|
|
85
|
+
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
86
|
+
if (mediaType === 'application/graphql-response+json' &&
|
|
87
|
+
charset === 'charset=utf8') {
|
|
88
|
+
acceptedMediaType = 'application/graphql-response+json';
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
// application/json should be the default until watershed
|
|
92
|
+
if ((mediaType === 'application/json' ||
|
|
93
|
+
mediaType === 'application/*' ||
|
|
94
|
+
mediaType === '*/*') &&
|
|
95
|
+
charset === 'charset=utf8') {
|
|
96
|
+
acceptedMediaType = 'application/json';
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
94
99
|
}
|
|
95
|
-
const acceptedMediaType = getAcceptableMediaType(getHeader(req, 'accept'));
|
|
96
100
|
if (!acceptedMediaType) {
|
|
97
101
|
return [
|
|
98
102
|
null,
|
|
@@ -105,89 +109,14 @@ function createHandler(options) {
|
|
|
105
109
|
},
|
|
106
110
|
];
|
|
107
111
|
}
|
|
108
|
-
// TODO: should graphql-http care about content-encoding? I'd say unzipping should happen before handler is reached
|
|
109
|
-
const [mediaType, charset = 'charset=utf-8', // utf-8 is assumed when not specified. this parameter is either "charset" or "boundary" (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
|
|
110
|
-
] = (getHeader(req, 'content-type') || '')
|
|
111
|
-
.replace(/\s/g, '')
|
|
112
|
-
.toLowerCase()
|
|
113
|
-
.split(';');
|
|
114
112
|
let params;
|
|
115
113
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const searchParams = new URLSearchParams(search);
|
|
123
|
-
partParams.operationName =
|
|
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');
|
|
127
|
-
if (variables)
|
|
128
|
-
partParams.variables = JSON.parse(variables);
|
|
129
|
-
const extensions = searchParams.get('extensions');
|
|
130
|
-
if (extensions)
|
|
131
|
-
partParams.extensions = JSON.parse(extensions);
|
|
132
|
-
}
|
|
133
|
-
catch (_c) {
|
|
134
|
-
throw new Error('Unparsable URL');
|
|
135
|
-
}
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
case method === 'POST' &&
|
|
139
|
-
mediaType === 'application/json' &&
|
|
140
|
-
charset === 'charset=utf-8':
|
|
141
|
-
{
|
|
142
|
-
if (!req.body) {
|
|
143
|
-
throw new Error('Missing body');
|
|
144
|
-
}
|
|
145
|
-
let data;
|
|
146
|
-
try {
|
|
147
|
-
const body = typeof req.body === 'function' ? await req.body() : req.body;
|
|
148
|
-
data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
throw new Error('Unparsable JSON body');
|
|
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;
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
default: // graphql-http doesnt support any other content type
|
|
163
|
-
return [
|
|
164
|
-
null,
|
|
165
|
-
{
|
|
166
|
-
status: 415,
|
|
167
|
-
statusText: 'Unsupported Media Type',
|
|
168
|
-
},
|
|
169
|
-
];
|
|
170
|
-
}
|
|
171
|
-
if (partParams.query == null)
|
|
172
|
-
throw new Error('Missing query');
|
|
173
|
-
if (typeof partParams.query !== 'string')
|
|
174
|
-
throw new Error('Invalid query');
|
|
175
|
-
if (partParams.variables != null &&
|
|
176
|
-
(typeof partParams.variables !== 'object' ||
|
|
177
|
-
Array.isArray(partParams.variables))) {
|
|
178
|
-
throw new Error('Invalid variables');
|
|
179
|
-
}
|
|
180
|
-
if (partParams.operationName != null &&
|
|
181
|
-
typeof partParams.operationName !== 'string') {
|
|
182
|
-
throw new Error('Invalid operationName');
|
|
183
|
-
}
|
|
184
|
-
if (partParams.extensions != null &&
|
|
185
|
-
(typeof partParams.extensions !== 'object' ||
|
|
186
|
-
Array.isArray(partParams.extensions))) {
|
|
187
|
-
throw new Error('Invalid extensions');
|
|
188
|
-
}
|
|
189
|
-
// request parameters are checked and now complete
|
|
190
|
-
params = partParams;
|
|
114
|
+
let paramsOrRes = await parseRequestParams(req);
|
|
115
|
+
if (!paramsOrRes)
|
|
116
|
+
paramsOrRes = await defaultParseRequestParams(req);
|
|
117
|
+
if (isResponse(paramsOrRes))
|
|
118
|
+
return paramsOrRes;
|
|
119
|
+
params = paramsOrRes;
|
|
191
120
|
}
|
|
192
121
|
catch (err) {
|
|
193
122
|
return makeResponse(err, acceptedMediaType, formatError);
|
|
@@ -197,7 +126,7 @@ function createHandler(options) {
|
|
|
197
126
|
if (isResponse(maybeResErrsOrArgs))
|
|
198
127
|
return maybeResErrsOrArgs;
|
|
199
128
|
else if ((0, utils_1.isExecutionResult)(maybeResErrsOrArgs) ||
|
|
200
|
-
|
|
129
|
+
areGraphQLErrors(maybeResErrsOrArgs))
|
|
201
130
|
return makeResponse(maybeResErrsOrArgs, acceptedMediaType, formatError);
|
|
202
131
|
else if (maybeResErrsOrArgs)
|
|
203
132
|
args = maybeResErrsOrArgs;
|
|
@@ -249,7 +178,7 @@ function createHandler(options) {
|
|
|
249
178
|
throw null;
|
|
250
179
|
operation = ast.operation;
|
|
251
180
|
}
|
|
252
|
-
catch (
|
|
181
|
+
catch (_a) {
|
|
253
182
|
return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType, formatError);
|
|
254
183
|
}
|
|
255
184
|
if (operation === 'subscription') {
|
|
@@ -257,7 +186,7 @@ function createHandler(options) {
|
|
|
257
186
|
}
|
|
258
187
|
// mutations cannot happen over GETs
|
|
259
188
|
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
260
|
-
if (operation === 'mutation' && method === 'GET') {
|
|
189
|
+
if (operation === 'mutation' && req.method === 'GET') {
|
|
261
190
|
return [
|
|
262
191
|
JSON.stringify({
|
|
263
192
|
errors: [new graphql_1.GraphQLError('Cannot perform mutations over GET')],
|
|
@@ -294,38 +223,109 @@ function createHandler(options) {
|
|
|
294
223
|
}
|
|
295
224
|
exports.createHandler = createHandler;
|
|
296
225
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
226
|
+
* The default request params parser. Used when no custom one is provided or if it
|
|
227
|
+
* returns nothing.
|
|
299
228
|
*
|
|
300
|
-
* @
|
|
229
|
+
* Read more about it in {@link ParseRequestParams}.
|
|
230
|
+
*
|
|
231
|
+
* TODO: should graphql-http itself care about content-encoding? I'd say unzipping should happen before handler is reached
|
|
301
232
|
*/
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
const
|
|
233
|
+
async function defaultParseRequestParams(req) {
|
|
234
|
+
var _a, _b;
|
|
235
|
+
const method = req.method;
|
|
236
|
+
if (method !== 'GET' && method !== 'POST') {
|
|
237
|
+
return [
|
|
238
|
+
null,
|
|
239
|
+
{
|
|
240
|
+
status: 405,
|
|
241
|
+
statusText: 'Method Not Allowed',
|
|
242
|
+
headers: {
|
|
243
|
+
allow: 'GET, POST',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
const [mediaType, charset = 'charset=utf-8', // utf-8 is assumed when not specified. this parameter is either "charset" or "boundary" (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
|
|
249
|
+
] = (getHeader(req, 'content-type') || '')
|
|
305
250
|
.replace(/\s/g, '')
|
|
306
251
|
.toLowerCase()
|
|
307
|
-
.split('
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
252
|
+
.split(';');
|
|
253
|
+
const partParams = {};
|
|
254
|
+
switch (true) {
|
|
255
|
+
case method === 'GET': {
|
|
256
|
+
// TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
|
|
257
|
+
try {
|
|
258
|
+
const [, search] = req.url.split('?');
|
|
259
|
+
const searchParams = new URLSearchParams(search);
|
|
260
|
+
partParams.operationName =
|
|
261
|
+
(_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
|
|
262
|
+
partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
|
|
263
|
+
const variables = searchParams.get('variables');
|
|
264
|
+
if (variables)
|
|
265
|
+
partParams.variables = JSON.parse(variables);
|
|
266
|
+
const extensions = searchParams.get('extensions');
|
|
267
|
+
if (extensions)
|
|
268
|
+
partParams.extensions = JSON.parse(extensions);
|
|
269
|
+
}
|
|
270
|
+
catch (_c) {
|
|
271
|
+
throw new Error('Unparsable URL');
|
|
272
|
+
}
|
|
323
273
|
break;
|
|
324
274
|
}
|
|
275
|
+
case method === 'POST' &&
|
|
276
|
+
mediaType === 'application/json' &&
|
|
277
|
+
charset === 'charset=utf-8':
|
|
278
|
+
{
|
|
279
|
+
if (!req.body) {
|
|
280
|
+
throw new Error('Missing body');
|
|
281
|
+
}
|
|
282
|
+
let data;
|
|
283
|
+
try {
|
|
284
|
+
const body = typeof req.body === 'function' ? await req.body() : req.body;
|
|
285
|
+
data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
throw new Error('Unparsable JSON body');
|
|
289
|
+
}
|
|
290
|
+
if (!(0, utils_1.isObject)(data)) {
|
|
291
|
+
throw new Error('JSON body must be an object');
|
|
292
|
+
}
|
|
293
|
+
partParams.operationName = data.operationName;
|
|
294
|
+
partParams.query = data.query;
|
|
295
|
+
partParams.variables = data.variables;
|
|
296
|
+
partParams.extensions = data.extensions;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
default: // graphql-http doesnt support any other content type
|
|
300
|
+
return [
|
|
301
|
+
null,
|
|
302
|
+
{
|
|
303
|
+
status: 415,
|
|
304
|
+
statusText: 'Unsupported Media Type',
|
|
305
|
+
},
|
|
306
|
+
];
|
|
307
|
+
}
|
|
308
|
+
if (partParams.query == null)
|
|
309
|
+
throw new Error('Missing query');
|
|
310
|
+
if (typeof partParams.query !== 'string')
|
|
311
|
+
throw new Error('Invalid query');
|
|
312
|
+
if (partParams.variables != null &&
|
|
313
|
+
(typeof partParams.variables !== 'object' ||
|
|
314
|
+
Array.isArray(partParams.variables))) {
|
|
315
|
+
throw new Error('Invalid variables');
|
|
316
|
+
}
|
|
317
|
+
if (partParams.operationName != null &&
|
|
318
|
+
typeof partParams.operationName !== 'string') {
|
|
319
|
+
throw new Error('Invalid operationName');
|
|
325
320
|
}
|
|
326
|
-
|
|
321
|
+
if (partParams.extensions != null &&
|
|
322
|
+
(typeof partParams.extensions !== 'object' ||
|
|
323
|
+
Array.isArray(partParams.extensions))) {
|
|
324
|
+
throw new Error('Invalid extensions');
|
|
325
|
+
}
|
|
326
|
+
// request parameters are checked and now complete
|
|
327
|
+
return partParams;
|
|
327
328
|
}
|
|
328
|
-
exports.getAcceptableMediaType = getAcceptableMediaType;
|
|
329
329
|
/**
|
|
330
330
|
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
331
331
|
*
|
|
@@ -336,15 +336,13 @@ exports.getAcceptableMediaType = getAcceptableMediaType;
|
|
|
336
336
|
*
|
|
337
337
|
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
|
|
338
338
|
* error will be present in the `ExecutionResult` style.
|
|
339
|
-
*
|
|
340
|
-
* @category Server
|
|
341
339
|
*/
|
|
342
340
|
function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
|
|
343
341
|
if (resultOrErrors instanceof Error &&
|
|
344
342
|
// because GraphQLError extends the Error class
|
|
345
|
-
!
|
|
343
|
+
!isGraphQLError(resultOrErrors)) {
|
|
346
344
|
return [
|
|
347
|
-
JSON.stringify({ errors: [formatError(resultOrErrors)] },
|
|
345
|
+
JSON.stringify({ errors: [formatError(resultOrErrors)] }, jsonErrorReplacer),
|
|
348
346
|
{
|
|
349
347
|
status: 400,
|
|
350
348
|
statusText: 'Bad Request',
|
|
@@ -354,14 +352,14 @@ function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
|
|
|
354
352
|
},
|
|
355
353
|
];
|
|
356
354
|
}
|
|
357
|
-
const errors =
|
|
355
|
+
const errors = isGraphQLError(resultOrErrors)
|
|
358
356
|
? [resultOrErrors]
|
|
359
|
-
:
|
|
357
|
+
: areGraphQLErrors(resultOrErrors)
|
|
360
358
|
? resultOrErrors
|
|
361
359
|
: null;
|
|
362
360
|
if (errors) {
|
|
363
361
|
return [
|
|
364
|
-
JSON.stringify({ errors: errors.map(formatError) },
|
|
362
|
+
JSON.stringify({ errors: errors.map(formatError) }, jsonErrorReplacer),
|
|
365
363
|
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
366
364
|
? {
|
|
367
365
|
status: 200,
|
|
@@ -379,7 +377,7 @@ function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
|
|
|
379
377
|
}
|
|
380
378
|
return [
|
|
381
379
|
JSON.stringify('errors' in resultOrErrors && resultOrErrors.errors
|
|
382
|
-
? Object.assign(Object.assign({}, resultOrErrors), { errors: resultOrErrors.errors.map(formatError) }) : resultOrErrors,
|
|
380
|
+
? Object.assign(Object.assign({}, resultOrErrors), { errors: resultOrErrors.errors.map(formatError) }) : resultOrErrors, jsonErrorReplacer),
|
|
383
381
|
{
|
|
384
382
|
status: 200,
|
|
385
383
|
statusText: 'OK',
|
|
@@ -391,10 +389,32 @@ function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
|
|
|
391
389
|
},
|
|
392
390
|
];
|
|
393
391
|
}
|
|
394
|
-
exports.makeResponse = makeResponse;
|
|
395
392
|
function getHeader(req, key) {
|
|
396
393
|
if (typeof req.headers.get === 'function') {
|
|
397
394
|
return req.headers.get(key);
|
|
398
395
|
}
|
|
399
396
|
return Object(req.headers)[key];
|
|
400
397
|
}
|
|
398
|
+
function areGraphQLErrors(obj) {
|
|
399
|
+
return (Array.isArray(obj) &&
|
|
400
|
+
obj.length > 0 &&
|
|
401
|
+
// if one item in the array is a GraphQLError, we're good
|
|
402
|
+
obj.some(isGraphQLError));
|
|
403
|
+
}
|
|
404
|
+
function isGraphQLError(obj) {
|
|
405
|
+
return obj instanceof graphql_1.GraphQLError;
|
|
406
|
+
}
|
|
407
|
+
function jsonErrorReplacer(_key,
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
409
|
+
val) {
|
|
410
|
+
if (val instanceof Error &&
|
|
411
|
+
// GraphQL errors implement their own stringer
|
|
412
|
+
!isGraphQLError(val)) {
|
|
413
|
+
return {
|
|
414
|
+
// name: val.name, name is included in message
|
|
415
|
+
message: val.message,
|
|
416
|
+
// stack: val.stack, can leak sensitive details
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
return val;
|
|
420
|
+
}
|