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.
- package/{LICENSE → LICENSE.md} +2 -2
- package/README.md +693 -35
- package/lib/client.d.ts +155 -0
- package/lib/client.js +189 -0
- package/lib/client.mjs +170 -0
- package/lib/common.d.ts +31 -0
- package/lib/common.js +7 -0
- package/lib/common.mjs +6 -0
- package/lib/handler.d.ts +279 -0
- package/lib/handler.js +353 -0
- package/lib/handler.mjs +346 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +18 -188
- package/lib/index.mjs +3 -0
- package/lib/utils.d.ts +14 -0
- package/lib/utils.js +33 -0
- package/lib/utils.mjs +26 -0
- package/package.json +87 -25
- package/umd/graphql-http.js +190 -0
- package/umd/graphql-http.min.js +1 -0
- package/umd/graphql-http.min.js.gz +0 -0
- package/.eslintrc.json +0 -8
- package/.npmignore +0 -33
- package/index.js +0 -1
- package/lib/__tests__/index.js +0 -90
package/lib/handler.d.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* handler
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError } from 'graphql';
|
|
7
|
+
import { RequestParams } from './common';
|
|
8
|
+
/**
|
|
9
|
+
* The incoming request headers the implementing server should provide.
|
|
10
|
+
*
|
|
11
|
+
* @category Common
|
|
12
|
+
*/
|
|
13
|
+
export interface RequestHeaders {
|
|
14
|
+
accept?: string | undefined;
|
|
15
|
+
allow?: string | undefined;
|
|
16
|
+
'content-type'?: string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Always an array in Node. Duplicates are added to it.
|
|
19
|
+
* Not necessarily true for other environments, make sure
|
|
20
|
+
* to check the type during runtime.
|
|
21
|
+
*/
|
|
22
|
+
'set-cookie'?: string | string[] | undefined;
|
|
23
|
+
[key: string]: string | string[] | undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Server agnostic request interface containing the raw request
|
|
27
|
+
* which is server dependant.
|
|
28
|
+
*
|
|
29
|
+
* @category Common
|
|
30
|
+
*/
|
|
31
|
+
export interface Request<RawRequest, Context> {
|
|
32
|
+
readonly method: string;
|
|
33
|
+
readonly url: string;
|
|
34
|
+
readonly headers: RequestHeaders;
|
|
35
|
+
readonly body: string | Record<string, unknown> | null;
|
|
36
|
+
/**
|
|
37
|
+
* The raw request itself from the implementing server.
|
|
38
|
+
*
|
|
39
|
+
* For example: `express.Request` when using Express, or maybe
|
|
40
|
+
* `http.IncomingMessage` when just using Node with `http.createServer`.
|
|
41
|
+
*/
|
|
42
|
+
readonly raw: RawRequest;
|
|
43
|
+
/**
|
|
44
|
+
* Context value about the incoming request, you're free to pass any information here.
|
|
45
|
+
*/
|
|
46
|
+
readonly context: Context;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The response headers that get returned from graphql-http.
|
|
50
|
+
*
|
|
51
|
+
* @category Common
|
|
52
|
+
*/
|
|
53
|
+
export declare type ResponseHeaders = {
|
|
54
|
+
accept?: string;
|
|
55
|
+
allow?: string;
|
|
56
|
+
'content-type'?: string;
|
|
57
|
+
} & Record<string, string>;
|
|
58
|
+
/**
|
|
59
|
+
* Server agnostic response body returned from `graphql-http` needing
|
|
60
|
+
* to be coerced to the server implementation in use.
|
|
61
|
+
*
|
|
62
|
+
* @category Common
|
|
63
|
+
*/
|
|
64
|
+
export declare type ResponseBody = string;
|
|
65
|
+
/**
|
|
66
|
+
* Server agnostic response options (ex. status and headers) returned from
|
|
67
|
+
* `graphql-http` needing to be coerced to the server implementation in use.
|
|
68
|
+
*
|
|
69
|
+
* @category Common
|
|
70
|
+
*/
|
|
71
|
+
export interface ResponseInit {
|
|
72
|
+
readonly status: number;
|
|
73
|
+
readonly statusText: string;
|
|
74
|
+
readonly headers?: ResponseHeaders;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Server agnostic response returned from `graphql-http` containing the
|
|
78
|
+
* body and init options needing to be coerced to the server implementation in use.
|
|
79
|
+
*
|
|
80
|
+
* @category Common
|
|
81
|
+
*/
|
|
82
|
+
export declare type Response = readonly [body: ResponseBody | null, init: ResponseInit];
|
|
83
|
+
/**
|
|
84
|
+
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
85
|
+
*
|
|
86
|
+
* @category Common
|
|
87
|
+
*/
|
|
88
|
+
export declare function isResponse(val: unknown): val is Response;
|
|
89
|
+
/**
|
|
90
|
+
* A concrete GraphQL execution context value type.
|
|
91
|
+
*
|
|
92
|
+
* Mainly used because TypeScript collapes unions
|
|
93
|
+
* with `any` or `unknown` to `any` or `unknown`. So,
|
|
94
|
+
* we use a custom type to allow definitions such as
|
|
95
|
+
* the `context` server option.
|
|
96
|
+
*
|
|
97
|
+
* @category Server
|
|
98
|
+
*/
|
|
99
|
+
export declare type ExecutionContext = object | symbol | number | string | boolean | undefined | null;
|
|
100
|
+
/** @category Server */
|
|
101
|
+
export interface HandlerOptions<RawRequest = unknown, Context = unknown> {
|
|
102
|
+
/**
|
|
103
|
+
* The GraphQL schema on which the operations will
|
|
104
|
+
* be executed and validated against.
|
|
105
|
+
*
|
|
106
|
+
* If a function is provided, it will be called on every
|
|
107
|
+
* operation request allowing you to manipulate schema
|
|
108
|
+
* dynamically.
|
|
109
|
+
*
|
|
110
|
+
* If the schema is left undefined, you're trusted to
|
|
111
|
+
* provide one in the returned `ExecutionArgs` from the
|
|
112
|
+
* `onSubscribe` callback.
|
|
113
|
+
*
|
|
114
|
+
* If you want to respond to the client with a custom status and/or body,
|
|
115
|
+
* you should do by returning a `Request` argument which will stop
|
|
116
|
+
* further execution.
|
|
117
|
+
*/
|
|
118
|
+
schema?: GraphQLSchema | ((req: Request<RawRequest, Context>, args: Omit<ExecutionArgs, 'schema'>) => Promise<GraphQLSchema | Response> | GraphQLSchema | Response);
|
|
119
|
+
/**
|
|
120
|
+
* A value which is provided to every resolver and holds
|
|
121
|
+
* important contextual information like the currently
|
|
122
|
+
* logged in user, or access to a database.
|
|
123
|
+
*/
|
|
124
|
+
context?: ExecutionContext | ((req: Request<RawRequest, Context>, args: ExecutionArgs) => Promise<ExecutionContext | Response> | ExecutionContext | Response);
|
|
125
|
+
/**
|
|
126
|
+
* A custom GraphQL validate function allowing you to apply your
|
|
127
|
+
* own validation rules.
|
|
128
|
+
*
|
|
129
|
+
* Will not be used when implementing a custom `onSubscribe`.
|
|
130
|
+
*/
|
|
131
|
+
validate?: typeof graphqlValidate;
|
|
132
|
+
/**
|
|
133
|
+
* Is the `execute` function from GraphQL which is
|
|
134
|
+
* used to execute the query and mutation operations.
|
|
135
|
+
*/
|
|
136
|
+
execute?: typeof graphqlExecute;
|
|
137
|
+
/**
|
|
138
|
+
* GraphQL parse function allowing you to apply a custom parser.
|
|
139
|
+
*/
|
|
140
|
+
parse?: typeof graphqlParse;
|
|
141
|
+
/**
|
|
142
|
+
* GraphQL operation AST getter used for detecting the operation type.
|
|
143
|
+
*/
|
|
144
|
+
getOperationAST?: typeof graphqlGetOperationAST;
|
|
145
|
+
/**
|
|
146
|
+
* The subscribe callback executed right after processing the request
|
|
147
|
+
* before proceeding with the GraphQL operation execution.
|
|
148
|
+
*
|
|
149
|
+
* If you return `ExecutionResult` from the callback, it will be used
|
|
150
|
+
* directly for responding to the request. Useful for implementing a response
|
|
151
|
+
* cache.
|
|
152
|
+
*
|
|
153
|
+
* If you return `ExecutionArgs` from the callback, it will be used instead of
|
|
154
|
+
* trying to build one internally. In this case, you are responsible for providing
|
|
155
|
+
* a ready set of arguments which will be directly plugged in the operation execution.
|
|
156
|
+
*
|
|
157
|
+
* You *must* validate the `ExecutionArgs` yourself if returning them.
|
|
158
|
+
*
|
|
159
|
+
* If you return an array of `GraphQLError` from the callback, they will be reported
|
|
160
|
+
* to the client while complying with the spec.
|
|
161
|
+
*
|
|
162
|
+
* Omitting the fields `contextValue` from the returned `ExecutionArgs` will use the
|
|
163
|
+
* provided `context` option, if available.
|
|
164
|
+
*
|
|
165
|
+
* Useful for preparing the execution arguments following a custom logic. A typical
|
|
166
|
+
* use-case is persisted queries. You can identify the query from the request parameters
|
|
167
|
+
* and supply the appropriate GraphQL operation execution arguments.
|
|
168
|
+
*
|
|
169
|
+
* If you want to respond to the client with a custom status and/or body,
|
|
170
|
+
* you should do by returning a `Request` argument which will stop
|
|
171
|
+
* further execution.
|
|
172
|
+
*/
|
|
173
|
+
onSubscribe?: (req: Request<RawRequest, Context>, params: RequestParams) => Promise<ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void> | ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void;
|
|
174
|
+
/**
|
|
175
|
+
* Executed after the operation call resolves.
|
|
176
|
+
*
|
|
177
|
+
* The `OperationResult` argument is the result of operation
|
|
178
|
+
* execution. It can be an iterator or already a value.
|
|
179
|
+
*
|
|
180
|
+
* Use this callback to listen for GraphQL operations and
|
|
181
|
+
* execution result manipulation.
|
|
182
|
+
*
|
|
183
|
+
* If you want to respond to the client with a custom status and/or body,
|
|
184
|
+
* you should do by returning a `Request` argument which will stop
|
|
185
|
+
* further execution.
|
|
186
|
+
*/
|
|
187
|
+
onOperation?: (req: Request<RawRequest, Context>, args: ExecutionArgs, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The ready-to-use handler. Simply plug it in your favourite HTTP framework
|
|
191
|
+
* and enjoy.
|
|
192
|
+
*
|
|
193
|
+
* Errors thrown from **any** of the provided options or callbacks (or even due to
|
|
194
|
+
* library misuse or potential bugs) will reject the handler's promise. They are
|
|
195
|
+
* considered internal errors and you should take care of them accordingly.
|
|
196
|
+
*
|
|
197
|
+
* @category Server
|
|
198
|
+
*/
|
|
199
|
+
export declare type Handler<RawRequest = unknown, Context = unknown> = (req: Request<RawRequest, Context>) => Promise<Response>;
|
|
200
|
+
/**
|
|
201
|
+
* Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
|
|
202
|
+
* be used with your favourite server library.
|
|
203
|
+
*
|
|
204
|
+
* Beware that the handler resolves only after the whole operation completes.
|
|
205
|
+
*
|
|
206
|
+
* Errors thrown from **any** of the provided options or callbacks (or even due to
|
|
207
|
+
* library misuse or potential bugs) will reject the handler's promise. They are
|
|
208
|
+
* considered internal errors and you should take care of them accordingly.
|
|
209
|
+
*
|
|
210
|
+
* For production environments, its recommended not to transmit the exact internal
|
|
211
|
+
* error details to the client, but instead report to an error logging tool or simply
|
|
212
|
+
* the console.
|
|
213
|
+
*
|
|
214
|
+
* Simple example usage with Node:
|
|
215
|
+
*
|
|
216
|
+
* ```js
|
|
217
|
+
* import http from 'http';
|
|
218
|
+
* import { createHandler } from 'graphql-http';
|
|
219
|
+
* import { schema } from './my-graphql-schema';
|
|
220
|
+
*
|
|
221
|
+
* // Create the GraphQL over HTTP handler
|
|
222
|
+
* const handler = createHandler({ schema });
|
|
223
|
+
*
|
|
224
|
+
* // Create a HTTP server using the handler on `/graphql`
|
|
225
|
+
* const server = http.createServer(async (req, res) => {
|
|
226
|
+
* if (!req.url.startsWith('/graphql')) {
|
|
227
|
+
* return res.writeHead(404).end();
|
|
228
|
+
* }
|
|
229
|
+
*
|
|
230
|
+
* try {
|
|
231
|
+
* const [body, init] = await handler({
|
|
232
|
+
* url: req.url,
|
|
233
|
+
* method: req.method,
|
|
234
|
+
* headers: req.headers,
|
|
235
|
+
* body: await new Promise((resolve) => {
|
|
236
|
+
* let body = '';
|
|
237
|
+
* req.on('data', (chunk) => (body += chunk));
|
|
238
|
+
* req.on('end', () => resolve(body));
|
|
239
|
+
* }),
|
|
240
|
+
* raw: req,
|
|
241
|
+
* });
|
|
242
|
+
* res.writeHead(init.status, init.statusText, init.headers).end(body);
|
|
243
|
+
* } catch (err) {
|
|
244
|
+
* // BEWARE not to transmit the exact internal error message in production environments
|
|
245
|
+
* res.writeHead(500).end(err.message);
|
|
246
|
+
* }
|
|
247
|
+
* });
|
|
248
|
+
*
|
|
249
|
+
* server.listen(4000);
|
|
250
|
+
* console.log('Listening to port 4000');
|
|
251
|
+
* ```
|
|
252
|
+
*
|
|
253
|
+
* @category Server
|
|
254
|
+
*/
|
|
255
|
+
export declare function createHandler<RawRequest = unknown, Context = unknown>(options: HandlerOptions<RawRequest, Context>): Handler<RawRequest, Context>;
|
|
256
|
+
/**
|
|
257
|
+
* Request's Media-Type that the server accepts.
|
|
258
|
+
*
|
|
259
|
+
* @category Server
|
|
260
|
+
*/
|
|
261
|
+
export declare type AcceptableMediaType = 'application/graphql+json' | 'application/json';
|
|
262
|
+
/**
|
|
263
|
+
* Inspects the request and detects the appropriate/acceptable Media-Type
|
|
264
|
+
* looking at the `Accept` header while complying with the GraphQL over HTTP Protocol.
|
|
265
|
+
*
|
|
266
|
+
* @category Server
|
|
267
|
+
*/
|
|
268
|
+
export declare function getAcceptableMediaType(acceptHeader: string | null | undefined): AcceptableMediaType | null;
|
|
269
|
+
/**
|
|
270
|
+
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
271
|
+
*
|
|
272
|
+
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
273
|
+
*
|
|
274
|
+
* If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
|
|
275
|
+
* and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
|
|
276
|
+
*
|
|
277
|
+
* @category Server
|
|
278
|
+
*/
|
|
279
|
+
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError>, acceptedMediaType: AcceptableMediaType): Response;
|
package/lib/handler.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* handler
|
|
5
|
+
*
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.makeResponse = exports.getAcceptableMediaType = exports.createHandler = exports.isResponse = void 0;
|
|
9
|
+
const graphql_1 = require("graphql");
|
|
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;
|
|
23
|
+
/**
|
|
24
|
+
* Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
|
|
25
|
+
* be used with your favourite server library.
|
|
26
|
+
*
|
|
27
|
+
* Beware that the handler resolves only after the whole operation completes.
|
|
28
|
+
*
|
|
29
|
+
* Errors thrown from **any** of the provided options or callbacks (or even due to
|
|
30
|
+
* library misuse or potential bugs) will reject the handler's promise. They are
|
|
31
|
+
* considered internal errors and you should take care of them accordingly.
|
|
32
|
+
*
|
|
33
|
+
* For production environments, its recommended not to transmit the exact internal
|
|
34
|
+
* error details to the client, but instead report to an error logging tool or simply
|
|
35
|
+
* the console.
|
|
36
|
+
*
|
|
37
|
+
* Simple example usage with Node:
|
|
38
|
+
*
|
|
39
|
+
* ```js
|
|
40
|
+
* import http from 'http';
|
|
41
|
+
* import { createHandler } from 'graphql-http';
|
|
42
|
+
* import { schema } from './my-graphql-schema';
|
|
43
|
+
*
|
|
44
|
+
* // Create the GraphQL over HTTP handler
|
|
45
|
+
* const handler = createHandler({ schema });
|
|
46
|
+
*
|
|
47
|
+
* // Create a HTTP server using the handler on `/graphql`
|
|
48
|
+
* const server = http.createServer(async (req, res) => {
|
|
49
|
+
* if (!req.url.startsWith('/graphql')) {
|
|
50
|
+
* return res.writeHead(404).end();
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* try {
|
|
54
|
+
* const [body, init] = await handler({
|
|
55
|
+
* url: req.url,
|
|
56
|
+
* method: req.method,
|
|
57
|
+
* headers: req.headers,
|
|
58
|
+
* body: await new Promise((resolve) => {
|
|
59
|
+
* let body = '';
|
|
60
|
+
* req.on('data', (chunk) => (body += chunk));
|
|
61
|
+
* req.on('end', () => resolve(body));
|
|
62
|
+
* }),
|
|
63
|
+
* raw: req,
|
|
64
|
+
* });
|
|
65
|
+
* res.writeHead(init.status, init.statusText, init.headers).end(body);
|
|
66
|
+
* } catch (err) {
|
|
67
|
+
* // BEWARE not to transmit the exact internal error message in production environments
|
|
68
|
+
* res.writeHead(500).end(err.message);
|
|
69
|
+
* }
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* server.listen(4000);
|
|
73
|
+
* console.log('Listening to port 4000');
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @category Server
|
|
77
|
+
*/
|
|
78
|
+
function createHandler(options) {
|
|
79
|
+
const { schema, context, validate = graphql_1.validate, execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, onSubscribe, onOperation, } = options;
|
|
80
|
+
return async function handler(req) {
|
|
81
|
+
var _a, _b;
|
|
82
|
+
const method = req.method;
|
|
83
|
+
if (method !== 'GET' && method !== 'POST') {
|
|
84
|
+
return [
|
|
85
|
+
null,
|
|
86
|
+
{
|
|
87
|
+
status: 405,
|
|
88
|
+
statusText: 'Method Not Allowed',
|
|
89
|
+
headers: {
|
|
90
|
+
allow: 'GET, POST',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
const acceptedMediaType = getAcceptableMediaType(req.headers.accept);
|
|
96
|
+
if (!acceptedMediaType) {
|
|
97
|
+
return [
|
|
98
|
+
null,
|
|
99
|
+
{
|
|
100
|
+
status: 406,
|
|
101
|
+
statusText: 'Not Acceptable',
|
|
102
|
+
headers: {
|
|
103
|
+
accept: 'application/graphql+json; charset=utf-8, application/json; charset=utf-8',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
];
|
|
107
|
+
}
|
|
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
|
+
] = (req.headers['content-type'] || '')
|
|
111
|
+
.replace(/\s/g, '')
|
|
112
|
+
.toLowerCase()
|
|
113
|
+
.split(';');
|
|
114
|
+
let params;
|
|
115
|
+
try {
|
|
116
|
+
const partParams = {};
|
|
117
|
+
switch (true) {
|
|
118
|
+
case method === 'GET': {
|
|
119
|
+
// TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
|
|
120
|
+
try {
|
|
121
|
+
const [, search] = req.url.split('?');
|
|
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
|
+
try {
|
|
146
|
+
const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
|
147
|
+
partParams.operationName = data.operationName;
|
|
148
|
+
partParams.query = data.query;
|
|
149
|
+
partParams.variables = data.variables;
|
|
150
|
+
partParams.extensions = data.extensions;
|
|
151
|
+
}
|
|
152
|
+
catch (_d) {
|
|
153
|
+
throw new Error('Unparsable JSON body');
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
default: // graphql-http doesnt support any other content type
|
|
158
|
+
return [
|
|
159
|
+
null,
|
|
160
|
+
{
|
|
161
|
+
status: 415,
|
|
162
|
+
statusText: 'Unsupported Media Type',
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
if (partParams.query == null)
|
|
167
|
+
throw new Error('Missing query');
|
|
168
|
+
if (typeof partParams.query !== 'string')
|
|
169
|
+
throw new Error('Invalid query');
|
|
170
|
+
if (partParams.variables != null &&
|
|
171
|
+
(typeof partParams.variables !== 'object' ||
|
|
172
|
+
Array.isArray(partParams.variables))) {
|
|
173
|
+
throw new Error('Invalid variables');
|
|
174
|
+
}
|
|
175
|
+
if (partParams.extensions != null &&
|
|
176
|
+
(typeof partParams.extensions !== 'object' ||
|
|
177
|
+
Array.isArray(partParams.extensions))) {
|
|
178
|
+
throw new Error('Invalid extensions');
|
|
179
|
+
}
|
|
180
|
+
// request parameters are checked and now complete
|
|
181
|
+
params = partParams;
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
return makeResponse(new graphql_1.GraphQLError(err.message), acceptedMediaType);
|
|
185
|
+
}
|
|
186
|
+
let args;
|
|
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;
|
|
195
|
+
else {
|
|
196
|
+
if (!schema)
|
|
197
|
+
throw new Error('The GraphQL schema is not provided');
|
|
198
|
+
const { operationName, query, variables } = params;
|
|
199
|
+
let document;
|
|
200
|
+
try {
|
|
201
|
+
document = parse(query);
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
return makeResponse(err, acceptedMediaType);
|
|
205
|
+
}
|
|
206
|
+
const argsWithoutSchema = {
|
|
207
|
+
operationName,
|
|
208
|
+
document,
|
|
209
|
+
variableValues: variables,
|
|
210
|
+
};
|
|
211
|
+
if (typeof schema === 'function') {
|
|
212
|
+
const resOrSchema = await schema(req, argsWithoutSchema);
|
|
213
|
+
if (isResponse(resOrSchema))
|
|
214
|
+
return resOrSchema;
|
|
215
|
+
args = Object.assign(Object.assign({}, argsWithoutSchema), { schema: resOrSchema });
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
|
|
219
|
+
}
|
|
220
|
+
const validationErrs = validate(args.schema, args.document);
|
|
221
|
+
if (validationErrs.length) {
|
|
222
|
+
return makeResponse(validationErrs, acceptedMediaType);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
let operation;
|
|
226
|
+
try {
|
|
227
|
+
const ast = getOperationAST(args.document, args.operationName);
|
|
228
|
+
if (!ast)
|
|
229
|
+
throw null;
|
|
230
|
+
operation = ast.operation;
|
|
231
|
+
}
|
|
232
|
+
catch (_e) {
|
|
233
|
+
return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType);
|
|
234
|
+
}
|
|
235
|
+
if (operation === 'subscription') {
|
|
236
|
+
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
237
|
+
}
|
|
238
|
+
// mutations cannot happen over GETs
|
|
239
|
+
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
240
|
+
if (operation === 'mutation' && method === 'GET') {
|
|
241
|
+
return [
|
|
242
|
+
JSON.stringify({
|
|
243
|
+
errors: [new graphql_1.GraphQLError('Cannot perform mutations over GET')],
|
|
244
|
+
}),
|
|
245
|
+
{
|
|
246
|
+
status: 405,
|
|
247
|
+
statusText: 'Method Not Allowed',
|
|
248
|
+
headers: {
|
|
249
|
+
allow: 'POST',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
if (!('contextValue' in args)) {
|
|
255
|
+
const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
|
|
256
|
+
if (isResponse(maybeResOrContext))
|
|
257
|
+
return maybeResOrContext;
|
|
258
|
+
args.contextValue = maybeResOrContext;
|
|
259
|
+
}
|
|
260
|
+
let result = await execute(args);
|
|
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+json' ||
|
|
295
|
+
mediaType === 'application/*' ||
|
|
296
|
+
mediaType === '*/*') &&
|
|
297
|
+
charset === 'charset=utf8') {
|
|
298
|
+
acceptedMediaType = 'application/graphql+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)) {
|
|
317
|
+
return [
|
|
318
|
+
JSON.stringify(resultOrErrors),
|
|
319
|
+
{
|
|
320
|
+
status: 200,
|
|
321
|
+
statusText: 'OK',
|
|
322
|
+
headers: {
|
|
323
|
+
'content-type': acceptedMediaType === 'application/json'
|
|
324
|
+
? 'application/json; charset=utf-8'
|
|
325
|
+
: 'application/graphql+json; charset=utf-8',
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
];
|
|
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+json; charset=utf-8',
|
|
350
|
+
} }),
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
exports.makeResponse = makeResponse;
|