graphql-http 0.2.0 → 1.2.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.
@@ -0,0 +1,346 @@
1
+ /**
2
+ *
3
+ * handler
4
+ *
5
+ */
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
+ }
19
+ /**
20
+ * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
21
+ * be used with your favourite server library.
22
+ *
23
+ * Beware that the handler resolves only after the whole operation completes.
24
+ *
25
+ * Errors thrown from **any** of the provided options or callbacks (or even due to
26
+ * library misuse or potential bugs) will reject the handler's promise. They are
27
+ * considered internal errors and you should take care of them accordingly.
28
+ *
29
+ * For production environments, its recommended not to transmit the exact internal
30
+ * error details to the client, but instead report to an error logging tool or simply
31
+ * the console.
32
+ *
33
+ * Simple example usage with Node:
34
+ *
35
+ * ```js
36
+ * import http from 'http';
37
+ * import { createHandler } from 'graphql-http';
38
+ * import { schema } from './my-graphql-schema.mjs';
39
+ *
40
+ * // Create the GraphQL over HTTP handler
41
+ * const handler = createHandler({ schema });
42
+ *
43
+ * // Create a HTTP server using the handler on `/graphql`
44
+ * const server = http.createServer(async (req, res) => {
45
+ * if (!req.url.startsWith('/graphql')) {
46
+ * return res.writeHead(404).end();
47
+ * }
48
+ *
49
+ * try {
50
+ * const [body, init] = await handler({
51
+ * url: req.url,
52
+ * method: req.method,
53
+ * headers: req.headers,
54
+ * body: await new Promise((resolve) => {
55
+ * let body = '';
56
+ * req.on('data', (chunk) => (body += chunk));
57
+ * req.on('end', () => resolve(body));
58
+ * }),
59
+ * raw: req,
60
+ * });
61
+ * res.writeHead(init.status, init.statusText, init.headers).end(body);
62
+ * } catch (err) {
63
+ * // BEWARE not to transmit the exact internal error message in production environments
64
+ * res.writeHead(500).end(err.message);
65
+ * }
66
+ * });
67
+ *
68
+ * server.listen(4000);
69
+ * console.log('Listening to port 4000');
70
+ * ```
71
+ *
72
+ * @category Server
73
+ */
74
+ export function createHandler(options) {
75
+ const { schema, context, validate = graphqlValidate, execute = graphqlExecute, parse = graphqlParse, getOperationAST = graphqlGetOperationAST, onSubscribe, onOperation, } = options;
76
+ return async function handler(req) {
77
+ var _a, _b;
78
+ const method = req.method;
79
+ if (method !== 'GET' && method !== 'POST') {
80
+ return [
81
+ null,
82
+ {
83
+ status: 405,
84
+ statusText: 'Method Not Allowed',
85
+ headers: {
86
+ allow: 'GET, POST',
87
+ },
88
+ },
89
+ ];
90
+ }
91
+ const acceptedMediaType = getAcceptableMediaType(req.headers.accept);
92
+ if (!acceptedMediaType) {
93
+ return [
94
+ null,
95
+ {
96
+ status: 406,
97
+ statusText: 'Not Acceptable',
98
+ headers: {
99
+ accept: 'application/graphql+json; charset=utf-8, application/json; charset=utf-8',
100
+ },
101
+ },
102
+ ];
103
+ }
104
+ // TODO: should graphql-http care about content-encoding? I'd say unzipping should happen before handler is reached
105
+ 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)
106
+ ] = (req.headers['content-type'] || '')
107
+ .replace(/\s/g, '')
108
+ .toLowerCase()
109
+ .split(';');
110
+ let params;
111
+ try {
112
+ const partParams = {};
113
+ switch (true) {
114
+ case method === 'GET': {
115
+ // TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
116
+ try {
117
+ const [, search] = req.url.split('?');
118
+ const searchParams = new URLSearchParams(search);
119
+ partParams.operationName =
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');
123
+ if (variables)
124
+ partParams.variables = JSON.parse(variables);
125
+ const extensions = searchParams.get('extensions');
126
+ if (extensions)
127
+ partParams.extensions = JSON.parse(extensions);
128
+ }
129
+ catch (_c) {
130
+ throw new Error('Unparsable URL');
131
+ }
132
+ break;
133
+ }
134
+ case method === 'POST' &&
135
+ mediaType === 'application/json' &&
136
+ charset === 'charset=utf-8':
137
+ {
138
+ if (!req.body) {
139
+ throw new Error('Missing body');
140
+ }
141
+ try {
142
+ const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
143
+ partParams.operationName = data.operationName;
144
+ partParams.query = data.query;
145
+ partParams.variables = data.variables;
146
+ partParams.extensions = data.extensions;
147
+ }
148
+ catch (_d) {
149
+ throw new Error('Unparsable JSON body');
150
+ }
151
+ break;
152
+ }
153
+ default: // graphql-http doesnt support any other content type
154
+ return [
155
+ null,
156
+ {
157
+ status: 415,
158
+ statusText: 'Unsupported Media Type',
159
+ },
160
+ ];
161
+ }
162
+ if (partParams.query == null)
163
+ throw new Error('Missing query');
164
+ if (typeof partParams.query !== 'string')
165
+ throw new Error('Invalid query');
166
+ if (partParams.variables != null &&
167
+ (typeof partParams.variables !== 'object' ||
168
+ Array.isArray(partParams.variables))) {
169
+ throw new Error('Invalid variables');
170
+ }
171
+ if (partParams.extensions != null &&
172
+ (typeof partParams.extensions !== 'object' ||
173
+ Array.isArray(partParams.extensions))) {
174
+ throw new Error('Invalid extensions');
175
+ }
176
+ // request parameters are checked and now complete
177
+ params = partParams;
178
+ }
179
+ catch (err) {
180
+ return makeResponse(new GraphQLError(err.message), acceptedMediaType);
181
+ }
182
+ let args;
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;
191
+ else {
192
+ if (!schema)
193
+ throw new Error('The GraphQL schema is not provided');
194
+ const { operationName, query, variables } = params;
195
+ let document;
196
+ try {
197
+ document = parse(query);
198
+ }
199
+ catch (err) {
200
+ return makeResponse(err, acceptedMediaType);
201
+ }
202
+ const argsWithoutSchema = {
203
+ operationName,
204
+ document,
205
+ variableValues: variables,
206
+ };
207
+ if (typeof schema === 'function') {
208
+ const resOrSchema = await schema(req, argsWithoutSchema);
209
+ if (isResponse(resOrSchema))
210
+ return resOrSchema;
211
+ args = Object.assign(Object.assign({}, argsWithoutSchema), { schema: resOrSchema });
212
+ }
213
+ else {
214
+ args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
215
+ }
216
+ const validationErrs = validate(args.schema, args.document);
217
+ if (validationErrs.length) {
218
+ return makeResponse(validationErrs, acceptedMediaType);
219
+ }
220
+ }
221
+ let operation;
222
+ try {
223
+ const ast = getOperationAST(args.document, args.operationName);
224
+ if (!ast)
225
+ throw null;
226
+ operation = ast.operation;
227
+ }
228
+ catch (_e) {
229
+ return makeResponse(new GraphQLError('Unable to detect operation AST'), acceptedMediaType);
230
+ }
231
+ if (operation === 'subscription') {
232
+ return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
233
+ }
234
+ // mutations cannot happen over GETs
235
+ // https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
236
+ if (operation === 'mutation' && method === 'GET') {
237
+ return [
238
+ JSON.stringify({
239
+ errors: [new GraphQLError('Cannot perform mutations over GET')],
240
+ }),
241
+ {
242
+ status: 405,
243
+ statusText: 'Method Not Allowed',
244
+ headers: {
245
+ allow: 'POST',
246
+ },
247
+ },
248
+ ];
249
+ }
250
+ if (!('contextValue' in args)) {
251
+ const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
252
+ if (isResponse(maybeResOrContext))
253
+ return maybeResOrContext;
254
+ args.contextValue = maybeResOrContext;
255
+ }
256
+ let result = await execute(args);
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+json' ||
290
+ mediaType === 'application/*' ||
291
+ mediaType === '*/*') &&
292
+ charset === 'charset=utf8') {
293
+ acceptedMediaType = 'application/graphql+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)) {
311
+ return [
312
+ JSON.stringify(resultOrErrors),
313
+ {
314
+ status: 200,
315
+ statusText: 'OK',
316
+ headers: {
317
+ 'content-type': acceptedMediaType === 'application/json'
318
+ ? 'application/json; charset=utf-8'
319
+ : 'application/graphql+json; charset=utf-8',
320
+ },
321
+ },
322
+ ];
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+json; charset=utf-8',
344
+ } }),
345
+ ];
346
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './common';
2
+ export * from './handler';
3
+ export * from './client';
package/lib/index.js CHANGED
@@ -1,189 +1,19 @@
1
- 'use strict';
2
-
3
- var merge = require('lodash.merge');
4
- var url = require('url');
5
- var qs = require('querystring');
6
-
7
- require('isomorphic-fetch');
8
-
9
- function GQLClient (url, options) {
10
- if (!(this instanceof GQLClient)) {
11
- return new GQLClient(url, options);
12
- }
13
-
14
- if (options == null) {
15
- options = {};
16
- }
17
-
18
- if (url == null) {
19
- throw new TypeError('GQLClient must have a url');
20
- }
21
-
22
- this.url = url;
23
- this.options = Object.assign({method: 'get'}, options);
24
- }
25
-
26
- GQLClient.prototype.configure = function (options) {
27
- return new GQLClient(this.url, merge({}, this.options, options));
28
- };
29
-
30
- GQLClient.prototype.query = function (query, variables) {
31
- if (query == null) {
32
- throw new TypeError('got ' + query + ' for GQLClient#query query');
33
- }
34
-
35
- if (variables == null) {
36
- variables = {};
37
- }
38
-
39
- var params = qs.stringify({
40
- query: query,
41
- variables: JSON.stringify(variables)
42
- });
43
-
44
- var requestUrl = this.options.onlyBody ? (
45
- this.url
46
- ) : (
47
- this.url + '?' + params
48
- );
49
-
50
- var method = this.options.method.toLowerCase();
51
-
52
- if (method === 'get') {
53
- var fetchOptions = this.options.fetch;
54
- } else if (method === 'post') {
55
- var fetchOptions = merge({
56
- method: 'POST',
57
- headers: {
58
- 'content-type': 'application/json'
59
- },
60
- body: JSON.stringify({query: query, variables: variables})
61
- }, this.options.fetch);
62
- } else {
63
- throw new TypeError('Unknown value "' + method + '" for method parameter');
64
- }
65
-
66
- return fetch(requestUrl, fetchOptions)
67
- .then(handleResponse);
68
- };
69
-
70
- GQLClient.prototype.mutate = function (query, variables) {
71
- if (this.options.method.toLowerCase() !== 'post') {
72
- return this.configure({method: 'post'}).query(query, variables);
73
- } else {
74
- return this.query(query, variables);
75
- }
76
- };
77
-
78
- exports.GQLClient = GQLClient;
79
-
80
- function handleResponse (resp) {
81
- if (resp.ok) {
82
- return handleOkayResponse(resp);
83
- } else if (/json/.test(resp.headers.get('Content-Type'))) {
84
- return handleJSONErrorResponse(resp);
85
- } else {
86
- return handleUnknownErrorResponse(resp);
87
- }
88
- }
89
-
90
- function handleOkayResponse (resp) {
91
- return resp.json().then(function (body) {
92
- if (body == null) {
93
- throw formatErrors(
94
- 'Missing/null body response from GraphQL.',
95
- body
96
- );
97
- } else if (body.errors instanceof Array && body.errors.length > 0) {
98
- throw formatErrors(
99
- 'Received ' + body.errors.length + ' error(s) from GraphQL.',
100
- body
101
- );
102
- } else {
103
- return body;
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
104
7
  }
105
- });
106
- }
107
-
108
- function formatErrors (message, body) {
109
- if (body != null && body.errors instanceof Array) {
110
- var errorMessages = body.errors.map(errorToMessage);
111
-
112
- return new GraphQLResponseError(
113
- [message].concat(errorMessages).join('\n')
114
- );
115
- } else {
116
- return new GraphQLResponseError(
117
- [
118
- message,
119
- 'Unrecognized body structure "' + JSON.stringify(body) + '"'
120
- ].join('\n')
121
- );
122
- }
123
- }
124
-
125
- function handleJSONErrorResponse (resp, body) {
126
- return resp.json().then(function (body) {
127
- var message = 'Received status ' + resp.status + ' from GraphQL.';
128
-
129
- throw formatErrors(message, body);
130
- });
131
- }
132
-
133
- function errorToMessage (error) {
134
- if (error != null && error.message != null) {
135
- return error.message;
136
- } else {
137
- return JSON.stringify(error);
138
- }
139
- }
140
-
141
- function handleUnknownErrorResponse (resp) {
142
- var message = 'Received status ' + resp.status + ' from GraphQL.';
143
-
144
- throw formatErrors(message, {
145
- errors: [{
146
- message: 'Unknown content type "' + resp.headers.get('Content-Type') + '".'
147
- }]
148
- });
149
- }
150
-
151
- function GraphQLError (message) {
152
- this.message = message;
153
-
154
- if (message instanceof Error) {
155
- this.message = message.message;
156
- this.stack = message.stack;
157
- } else if (Error.captureStackTrace) {
158
- Error.captureStackTrace(this, GraphQLError);
159
- }
160
- }
161
-
162
- GraphQLError.prototype = Object.create(Error.prototype);
163
- GraphQLError.prototype.constructor = GraphQLError;
164
-
165
- Object.defineProperty(GraphQLError.prototype, 'name', {
166
- value: 'GraphQLError'
167
- });
168
-
169
- exports.GraphQLError = GraphQLError;
170
-
171
- function GraphQLResponseError (message) {
172
- this.message = message;
173
-
174
- if (message instanceof Error) {
175
- this.message = message.message;
176
- this.stack = message.stack;
177
- } else if (Error.captureStackTrace) {
178
- Error.captureStackTrace(this, GraphQLResponseError);
179
- }
180
- }
181
-
182
- GraphQLResponseError.prototype = Object.create(GraphQLError.prototype);
183
- GraphQLResponseError.prototype.constructor = GraphQLResponseError;
184
-
185
- Object.defineProperty(GraphQLResponseError.prototype, 'name', {
186
- value: 'GraphQLResponseError'
187
- });
188
-
189
- exports.GraphQLResponseError = GraphQLResponseError;
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./common"), exports);
18
+ __exportStar(require("./handler"), exports);
19
+ __exportStar(require("./client"), exports);
package/lib/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ export * from './common.mjs';
2
+ export * from './handler.mjs';
3
+ export * from './client.mjs';
package/lib/utils.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ *
3
+ * utils
4
+ *
5
+ */
6
+ import type { ExecutionResult, GraphQLError } from 'graphql';
7
+ /** @private */
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>;
package/lib/utils.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ *
4
+ * utils
5
+ *
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.isAsyncIterable = exports.isExecutionResult = exports.areGraphQLErrors = exports.isObject = void 0;
9
+ /** @private */
10
+ function isObject(val) {
11
+ return typeof val === 'object' && val !== null;
12
+ }
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;
23
+ /** @private */
24
+ function isExecutionResult(val) {
25
+ return (isObject(val) &&
26
+ ('data' in val || ('data' in val && val.data == null && 'errors' in val)));
27
+ }
28
+ exports.isExecutionResult = isExecutionResult;
29
+ /** @private */
30
+ function isAsyncIterable(val) {
31
+ return typeof Object(val)[Symbol.asyncIterator] === 'function';
32
+ }
33
+ exports.isAsyncIterable = isAsyncIterable;
package/lib/utils.mjs ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ *
3
+ * utils
4
+ *
5
+ */
6
+ /** @private */
7
+ export function isObject(val) {
8
+ return typeof val === 'object' && val !== null;
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
+ }
18
+ /** @private */
19
+ export function isExecutionResult(val) {
20
+ return (isObject(val) &&
21
+ ('data' in val || ('data' in val && val.data == null && 'errors' in val)));
22
+ }
23
+ /** @private */
24
+ export function isAsyncIterable(val) {
25
+ return typeof Object(val)[Symbol.asyncIterator] === 'function';
26
+ }