graphql-http 1.1.0 → 1.4.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 +3 -5
- package/lib/client.js +2 -2
- package/lib/client.mjs +2 -2
- package/lib/common.d.ts +0 -77
- package/lib/common.js +0 -14
- package/lib/common.mjs +1 -12
- package/lib/handler.d.ts +127 -8
- package/lib/handler.js +129 -129
- package/lib/handler.mjs +123 -126
- package/lib/utils.d.ts +7 -3
- package/lib/utils.js +12 -1
- package/lib/utils.mjs +9 -0
- package/package.json +6 -6
- package/umd/graphql-http.js +2 -20
- package/umd/graphql-http.min.js +1 -1
- package/umd/graphql-http.min.js.gz +0 -0
package/README.md
CHANGED
|
@@ -190,11 +190,7 @@ fastify.all('/graphql', async (req, res) => {
|
|
|
190
190
|
url: req.url,
|
|
191
191
|
method: req.method,
|
|
192
192
|
headers: req.headers,
|
|
193
|
-
body:
|
|
194
|
-
let body = '';
|
|
195
|
-
req.on('data', (chunk) => (body += chunk));
|
|
196
|
-
req.on('end', () => resolve(body));
|
|
197
|
-
}),
|
|
193
|
+
body: req.body, // fastify reads the body for you
|
|
198
194
|
raw: req,
|
|
199
195
|
});
|
|
200
196
|
res.writeHead(init.status, init.statusText, init.headers).end(body);
|
|
@@ -214,8 +210,10 @@ import { serve } from 'https://deno.land/std@0.151.0/http/server.ts';
|
|
|
214
210
|
import { createHandler } from 'https://esm.sh/graphql-http';
|
|
215
211
|
import { schema } from './previous-step';
|
|
216
212
|
|
|
213
|
+
// Create the GraphQL over HTTP handler
|
|
217
214
|
const handler = createHandler<Request>({ schema });
|
|
218
215
|
|
|
216
|
+
// Start serving on `/graphql` using the handler
|
|
219
217
|
await serve(
|
|
220
218
|
async (req: Request) => {
|
|
221
219
|
const [path, _search] = req.url.split('?');
|
package/lib/client.js
CHANGED
|
@@ -105,7 +105,7 @@ function createClient(options) {
|
|
|
105
105
|
res = await fetchFn(url, {
|
|
106
106
|
signal: control.signal,
|
|
107
107
|
method: 'POST',
|
|
108
|
-
headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql+json, application/json' }),
|
|
108
|
+
headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql-response+json, application/json' }),
|
|
109
109
|
credentials,
|
|
110
110
|
referrer,
|
|
111
111
|
referrerPolicy,
|
|
@@ -122,7 +122,7 @@ function createClient(options) {
|
|
|
122
122
|
const contentType = res.headers.get('content-type');
|
|
123
123
|
if (!contentType)
|
|
124
124
|
throw new Error('Missing response content-type');
|
|
125
|
-
if (!contentType.includes('application/graphql+json') &&
|
|
125
|
+
if (!contentType.includes('application/graphql-response+json') &&
|
|
126
126
|
!contentType.includes('application/json')) {
|
|
127
127
|
throw new Error(`Unsupported response content-type ${contentType}`);
|
|
128
128
|
}
|
package/lib/client.mjs
CHANGED
|
@@ -88,7 +88,7 @@ export function createClient(options) {
|
|
|
88
88
|
res = await fetchFn(url, {
|
|
89
89
|
signal: control.signal,
|
|
90
90
|
method: 'POST',
|
|
91
|
-
headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql+json, application/json' }),
|
|
91
|
+
headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql-response+json, application/json' }),
|
|
92
92
|
credentials,
|
|
93
93
|
referrer,
|
|
94
94
|
referrerPolicy,
|
|
@@ -105,7 +105,7 @@ export function createClient(options) {
|
|
|
105
105
|
const contentType = res.headers.get('content-type');
|
|
106
106
|
if (!contentType)
|
|
107
107
|
throw new Error('Missing response content-type');
|
|
108
|
-
if (!contentType.includes('application/graphql+json') &&
|
|
108
|
+
if (!contentType.includes('application/graphql-response+json') &&
|
|
109
109
|
!contentType.includes('application/json')) {
|
|
110
110
|
throw new Error(`Unsupported response content-type ${contentType}`);
|
|
111
111
|
}
|
package/lib/common.d.ts
CHANGED
|
@@ -3,42 +3,6 @@
|
|
|
3
3
|
* common
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
-
/**
|
|
7
|
-
* The incoming request headers the implementing server should provide.
|
|
8
|
-
*
|
|
9
|
-
* @category Common
|
|
10
|
-
*/
|
|
11
|
-
export interface RequestHeaders {
|
|
12
|
-
accept?: string | undefined;
|
|
13
|
-
allow?: string | undefined;
|
|
14
|
-
'content-type'?: string | undefined;
|
|
15
|
-
/**
|
|
16
|
-
* Always an array in Node. Duplicates are added to it.
|
|
17
|
-
* Not necessarily true for other environments, make sure
|
|
18
|
-
* to check the type during runtime.
|
|
19
|
-
*/
|
|
20
|
-
'set-cookie'?: string | string[] | undefined;
|
|
21
|
-
[key: string]: string | string[] | undefined;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Server agnostic request interface containing the raw request
|
|
25
|
-
* which is server dependant.
|
|
26
|
-
*
|
|
27
|
-
* @category Common
|
|
28
|
-
*/
|
|
29
|
-
export interface Request<RawRequest> {
|
|
30
|
-
readonly method: string;
|
|
31
|
-
readonly url: string;
|
|
32
|
-
readonly headers: RequestHeaders;
|
|
33
|
-
readonly body: string | Record<string, unknown> | null;
|
|
34
|
-
/**
|
|
35
|
-
* The raw request itself from the implementing server.
|
|
36
|
-
*
|
|
37
|
-
* For example: `express.Request` when using Express, or maybe
|
|
38
|
-
* `http.IncomingMessage` when just using Node with `http.createServer`.
|
|
39
|
-
*/
|
|
40
|
-
readonly raw: RawRequest;
|
|
41
|
-
}
|
|
42
6
|
/**
|
|
43
7
|
* Parameters for GraphQL's request for execution.
|
|
44
8
|
*
|
|
@@ -52,47 +16,6 @@ export interface RequestParams {
|
|
|
52
16
|
variables?: Record<string, unknown> | undefined;
|
|
53
17
|
extensions?: Record<string, unknown> | undefined;
|
|
54
18
|
}
|
|
55
|
-
/**
|
|
56
|
-
* The response headers that get returned from graphql-http.
|
|
57
|
-
*
|
|
58
|
-
* @category Common
|
|
59
|
-
*/
|
|
60
|
-
export declare type ResponseHeaders = {
|
|
61
|
-
accept?: string;
|
|
62
|
-
allow?: string;
|
|
63
|
-
'content-type'?: string;
|
|
64
|
-
} & Record<string, string>;
|
|
65
|
-
/**
|
|
66
|
-
* Server agnostic response body returned from `graphql-http` needing
|
|
67
|
-
* to be coerced to the server implementation in use.
|
|
68
|
-
*
|
|
69
|
-
* @category Common
|
|
70
|
-
*/
|
|
71
|
-
export declare type ResponseBody = string;
|
|
72
|
-
/**
|
|
73
|
-
* Server agnostic response options (ex. status and headers) returned from
|
|
74
|
-
* `graphql-http` needing to be coerced to the server implementation in use.
|
|
75
|
-
*
|
|
76
|
-
* @category Common
|
|
77
|
-
*/
|
|
78
|
-
export interface ResponseInit {
|
|
79
|
-
readonly status: number;
|
|
80
|
-
readonly statusText: string;
|
|
81
|
-
readonly headers?: ResponseHeaders;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Server agnostic response returned from `graphql-http` containing the
|
|
85
|
-
* body and init options needing to be coerced to the server implementation in use.
|
|
86
|
-
*
|
|
87
|
-
* @category Common
|
|
88
|
-
*/
|
|
89
|
-
export declare type Response = readonly [body: ResponseBody | null, init: ResponseInit];
|
|
90
|
-
/**
|
|
91
|
-
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
92
|
-
*
|
|
93
|
-
* @category Common
|
|
94
|
-
*/
|
|
95
|
-
export declare function isResponse(val: unknown): val is Response;
|
|
96
19
|
/**
|
|
97
20
|
* A representation of any set of values over any amount of time.
|
|
98
21
|
*
|
package/lib/common.js
CHANGED
|
@@ -5,17 +5,3 @@
|
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.isResponse = void 0;
|
|
9
|
-
const utils_1 = require("./utils");
|
|
10
|
-
/**
|
|
11
|
-
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
12
|
-
*
|
|
13
|
-
* @category Common
|
|
14
|
-
*/
|
|
15
|
-
function isResponse(val) {
|
|
16
|
-
// TODO: make sure the contents of init match ResponseInit
|
|
17
|
-
return (Array.isArray(val) &&
|
|
18
|
-
(typeof val[0] === 'string' || val[0] === null) &&
|
|
19
|
-
(0, utils_1.isObject)(val[1]));
|
|
20
|
-
}
|
|
21
|
-
exports.isResponse = isResponse;
|
package/lib/common.mjs
CHANGED
|
@@ -3,15 +3,4 @@
|
|
|
3
3
|
* common
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
9
|
-
*
|
|
10
|
-
* @category Common
|
|
11
|
-
*/
|
|
12
|
-
export function isResponse(val) {
|
|
13
|
-
// TODO: make sure the contents of init match ResponseInit
|
|
14
|
-
return (Array.isArray(val) &&
|
|
15
|
-
(typeof val[0] === 'string' || val[0] === null) &&
|
|
16
|
-
isObject(val[1]));
|
|
17
|
-
}
|
|
6
|
+
export {};
|
package/lib/handler.d.ts
CHANGED
|
@@ -4,7 +4,94 @@
|
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
6
|
import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError } from 'graphql';
|
|
7
|
-
import {
|
|
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
|
+
/**
|
|
36
|
+
* Parsed request body or a parser function.
|
|
37
|
+
*
|
|
38
|
+
* If the provided function throws, the error message "Unparsable JSON body" will
|
|
39
|
+
* be in the erroneous response.
|
|
40
|
+
*/
|
|
41
|
+
readonly body: string | Record<string, unknown> | null | (() => string | Record<string, unknown> | null | Promise<string | Record<string, unknown> | null>);
|
|
42
|
+
/**
|
|
43
|
+
* The raw request itself from the implementing server.
|
|
44
|
+
*
|
|
45
|
+
* For example: `express.Request` when using Express, or maybe
|
|
46
|
+
* `http.IncomingMessage` when just using Node with `http.createServer`.
|
|
47
|
+
*/
|
|
48
|
+
readonly raw: RawRequest;
|
|
49
|
+
/**
|
|
50
|
+
* Context value about the incoming request, you're free to pass any information here.
|
|
51
|
+
*/
|
|
52
|
+
readonly context: Context;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The response headers that get returned from graphql-http.
|
|
56
|
+
*
|
|
57
|
+
* @category Common
|
|
58
|
+
*/
|
|
59
|
+
export declare type ResponseHeaders = {
|
|
60
|
+
accept?: string;
|
|
61
|
+
allow?: string;
|
|
62
|
+
'content-type'?: string;
|
|
63
|
+
} & Record<string, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Server agnostic response body returned from `graphql-http` needing
|
|
66
|
+
* to be coerced to the server implementation in use.
|
|
67
|
+
*
|
|
68
|
+
* @category Common
|
|
69
|
+
*/
|
|
70
|
+
export declare type ResponseBody = string;
|
|
71
|
+
/**
|
|
72
|
+
* Server agnostic response options (ex. status and headers) returned from
|
|
73
|
+
* `graphql-http` needing to be coerced to the server implementation in use.
|
|
74
|
+
*
|
|
75
|
+
* @category Common
|
|
76
|
+
*/
|
|
77
|
+
export interface ResponseInit {
|
|
78
|
+
readonly status: number;
|
|
79
|
+
readonly statusText: string;
|
|
80
|
+
readonly headers?: ResponseHeaders;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Server agnostic response returned from `graphql-http` containing the
|
|
84
|
+
* body and init options needing to be coerced to the server implementation in use.
|
|
85
|
+
*
|
|
86
|
+
* @category Common
|
|
87
|
+
*/
|
|
88
|
+
export declare type Response = readonly [body: ResponseBody | null, init: ResponseInit];
|
|
89
|
+
/**
|
|
90
|
+
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
91
|
+
*
|
|
92
|
+
* @category Common
|
|
93
|
+
*/
|
|
94
|
+
export declare function isResponse(val: unknown): val is Response;
|
|
8
95
|
/**
|
|
9
96
|
* A concrete GraphQL execution context value type.
|
|
10
97
|
*
|
|
@@ -17,7 +104,7 @@ import { Request, RequestParams, Response } from './common';
|
|
|
17
104
|
*/
|
|
18
105
|
export declare type ExecutionContext = object | symbol | number | string | boolean | undefined | null;
|
|
19
106
|
/** @category Server */
|
|
20
|
-
export interface HandlerOptions<RawRequest = unknown> {
|
|
107
|
+
export interface HandlerOptions<RawRequest = unknown, Context = unknown> {
|
|
21
108
|
/**
|
|
22
109
|
* The GraphQL schema on which the operations will
|
|
23
110
|
* be executed and validated against.
|
|
@@ -34,16 +121,18 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
34
121
|
* you should do by returning a `Request` argument which will stop
|
|
35
122
|
* further execution.
|
|
36
123
|
*/
|
|
37
|
-
schema?: GraphQLSchema | ((req: Request<RawRequest>, args: Omit<ExecutionArgs, 'schema'>) => Promise<GraphQLSchema | Response> | GraphQLSchema | Response);
|
|
124
|
+
schema?: GraphQLSchema | ((req: Request<RawRequest, Context>, args: Omit<ExecutionArgs, 'schema'>) => Promise<GraphQLSchema | Response> | GraphQLSchema | Response);
|
|
38
125
|
/**
|
|
39
126
|
* A value which is provided to every resolver and holds
|
|
40
127
|
* important contextual information like the currently
|
|
41
128
|
* logged in user, or access to a database.
|
|
42
129
|
*/
|
|
43
|
-
context?: ExecutionContext | ((req: Request<RawRequest>, args: ExecutionArgs) => Promise<ExecutionContext | Response> | ExecutionContext | Response);
|
|
130
|
+
context?: ExecutionContext | ((req: Request<RawRequest, Context>, args: ExecutionArgs) => Promise<ExecutionContext | Response> | ExecutionContext | Response);
|
|
44
131
|
/**
|
|
45
132
|
* A custom GraphQL validate function allowing you to apply your
|
|
46
133
|
* own validation rules.
|
|
134
|
+
*
|
|
135
|
+
* Will not be used when implementing a custom `onSubscribe`.
|
|
47
136
|
*/
|
|
48
137
|
validate?: typeof graphqlValidate;
|
|
49
138
|
/**
|
|
@@ -63,10 +152,16 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
63
152
|
* The subscribe callback executed right after processing the request
|
|
64
153
|
* before proceeding with the GraphQL operation execution.
|
|
65
154
|
*
|
|
155
|
+
* If you return `ExecutionResult` from the callback, it will be used
|
|
156
|
+
* directly for responding to the request. Useful for implementing a response
|
|
157
|
+
* cache.
|
|
158
|
+
*
|
|
66
159
|
* If you return `ExecutionArgs` from the callback, it will be used instead of
|
|
67
160
|
* trying to build one internally. In this case, you are responsible for providing
|
|
68
161
|
* a ready set of arguments which will be directly plugged in the operation execution.
|
|
69
162
|
*
|
|
163
|
+
* You *must* validate the `ExecutionArgs` yourself if returning them.
|
|
164
|
+
*
|
|
70
165
|
* If you return an array of `GraphQLError` from the callback, they will be reported
|
|
71
166
|
* to the client while complying with the spec.
|
|
72
167
|
*
|
|
@@ -81,7 +176,7 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
81
176
|
* you should do by returning a `Request` argument which will stop
|
|
82
177
|
* further execution.
|
|
83
178
|
*/
|
|
84
|
-
onSubscribe?: (req: Request<RawRequest>, params: RequestParams) => Promise<ExecutionArgs | GraphQLError[] | Response | void> | ExecutionArgs | GraphQLError[] | Response | void;
|
|
179
|
+
onSubscribe?: (req: Request<RawRequest, Context>, params: RequestParams) => Promise<ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void> | ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void;
|
|
85
180
|
/**
|
|
86
181
|
* Executed after the operation call resolves.
|
|
87
182
|
*
|
|
@@ -95,7 +190,7 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
95
190
|
* you should do by returning a `Request` argument which will stop
|
|
96
191
|
* further execution.
|
|
97
192
|
*/
|
|
98
|
-
onOperation?: (req: Request<RawRequest>, args: ExecutionArgs, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
193
|
+
onOperation?: (req: Request<RawRequest, Context>, args: ExecutionArgs, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
99
194
|
}
|
|
100
195
|
/**
|
|
101
196
|
* The ready-to-use handler. Simply plug it in your favourite HTTP framework
|
|
@@ -107,7 +202,7 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
107
202
|
*
|
|
108
203
|
* @category Server
|
|
109
204
|
*/
|
|
110
|
-
export declare type Handler<RawRequest = unknown> = (req: Request<RawRequest>) => Promise<Response>;
|
|
205
|
+
export declare type Handler<RawRequest = unknown, Context = unknown> = (req: Request<RawRequest, Context>) => Promise<Response>;
|
|
111
206
|
/**
|
|
112
207
|
* Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
|
|
113
208
|
* be used with your favourite server library.
|
|
@@ -163,4 +258,28 @@ export declare type Handler<RawRequest = unknown> = (req: Request<RawRequest>) =
|
|
|
163
258
|
*
|
|
164
259
|
* @category Server
|
|
165
260
|
*/
|
|
166
|
-
export declare function createHandler<RawRequest = unknown>(options: HandlerOptions<RawRequest>): Handler<RawRequest>;
|
|
261
|
+
export declare function createHandler<RawRequest = unknown, Context = unknown>(options: HandlerOptions<RawRequest, Context>): Handler<RawRequest, Context>;
|
|
262
|
+
/**
|
|
263
|
+
* Request's Media-Type that the server accepts.
|
|
264
|
+
*
|
|
265
|
+
* @category Server
|
|
266
|
+
*/
|
|
267
|
+
export declare type AcceptableMediaType = 'application/graphql-response+json' | 'application/json';
|
|
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 declare function getAcceptableMediaType(acceptHeader: string | null | undefined): AcceptableMediaType | null;
|
|
275
|
+
/**
|
|
276
|
+
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
277
|
+
*
|
|
278
|
+
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
279
|
+
*
|
|
280
|
+
* If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
|
|
281
|
+
* and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
|
|
282
|
+
*
|
|
283
|
+
* @category Server
|
|
284
|
+
*/
|
|
285
|
+
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError>, acceptedMediaType: AcceptableMediaType): Response;
|
package/lib/handler.js
CHANGED
|
@@ -5,10 +5,21 @@
|
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.createHandler = void 0;
|
|
8
|
+
exports.makeResponse = exports.getAcceptableMediaType = exports.createHandler = exports.isResponse = void 0;
|
|
9
9
|
const graphql_1 = require("graphql");
|
|
10
|
-
const common_1 = require("./common");
|
|
11
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;
|
|
12
23
|
/**
|
|
13
24
|
* Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
|
|
14
25
|
* be used with your favourite server library.
|
|
@@ -81,28 +92,7 @@ function createHandler(options) {
|
|
|
81
92
|
},
|
|
82
93
|
];
|
|
83
94
|
}
|
|
84
|
-
|
|
85
|
-
const accepts = (req.headers.accept || '*/*')
|
|
86
|
-
.replace(/\s/g, '')
|
|
87
|
-
.toLowerCase()
|
|
88
|
-
.split(',');
|
|
89
|
-
for (const accept of accepts) {
|
|
90
|
-
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
91
|
-
// TODO: handle the weight parameter "q"
|
|
92
|
-
const [mediaType, ...params] = accept.split(';');
|
|
93
|
-
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
94
|
-
if (mediaType === 'application/json' && charset === 'charset=utf8') {
|
|
95
|
-
acceptedMediaType = 'application/json';
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
if ((mediaType === 'application/graphql+json' ||
|
|
99
|
-
mediaType === 'application/*' ||
|
|
100
|
-
mediaType === '*/*') &&
|
|
101
|
-
charset === 'charset=utf8') {
|
|
102
|
-
acceptedMediaType = 'application/graphql+json';
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
95
|
+
const acceptedMediaType = getAcceptableMediaType(req.headers.accept);
|
|
106
96
|
if (!acceptedMediaType) {
|
|
107
97
|
return [
|
|
108
98
|
null,
|
|
@@ -110,7 +100,7 @@ function createHandler(options) {
|
|
|
110
100
|
status: 406,
|
|
111
101
|
statusText: 'Not Acceptable',
|
|
112
102
|
headers: {
|
|
113
|
-
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',
|
|
114
104
|
},
|
|
115
105
|
},
|
|
116
106
|
];
|
|
@@ -152,16 +142,21 @@ function createHandler(options) {
|
|
|
152
142
|
if (!req.body) {
|
|
153
143
|
throw new Error('Missing body');
|
|
154
144
|
}
|
|
145
|
+
let data;
|
|
155
146
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
partParams.query = data.query;
|
|
159
|
-
partParams.variables = data.variables;
|
|
160
|
-
partParams.extensions = data.extensions;
|
|
147
|
+
const body = typeof req.body === 'function' ? await req.body() : req.body;
|
|
148
|
+
data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
161
149
|
}
|
|
162
|
-
catch (
|
|
150
|
+
catch (err) {
|
|
163
151
|
throw new Error('Unparsable JSON body');
|
|
164
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;
|
|
165
160
|
break;
|
|
166
161
|
}
|
|
167
162
|
default: // graphql-http doesnt support any other content type
|
|
@@ -173,8 +168,10 @@ function createHandler(options) {
|
|
|
173
168
|
},
|
|
174
169
|
];
|
|
175
170
|
}
|
|
176
|
-
if (
|
|
171
|
+
if (partParams.query == null)
|
|
177
172
|
throw new Error('Missing query');
|
|
173
|
+
if (typeof partParams.query !== 'string')
|
|
174
|
+
throw new Error('Invalid query');
|
|
178
175
|
if (partParams.variables != null &&
|
|
179
176
|
(typeof partParams.variables !== 'object' ||
|
|
180
177
|
Array.isArray(partParams.variables))) {
|
|
@@ -189,40 +186,15 @@ function createHandler(options) {
|
|
|
189
186
|
params = partParams;
|
|
190
187
|
}
|
|
191
188
|
catch (err) {
|
|
192
|
-
return
|
|
193
|
-
err.message,
|
|
194
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
195
|
-
? {
|
|
196
|
-
status: 200,
|
|
197
|
-
statusText: 'OK',
|
|
198
|
-
}
|
|
199
|
-
: {
|
|
200
|
-
status: 400,
|
|
201
|
-
statusText: 'Bad Request',
|
|
202
|
-
})), { statusText: 'Bad Request' }),
|
|
203
|
-
];
|
|
189
|
+
return makeResponse(new graphql_1.GraphQLError(err.message), acceptedMediaType);
|
|
204
190
|
}
|
|
205
191
|
let args;
|
|
206
192
|
const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
|
|
207
|
-
if (
|
|
193
|
+
if (isResponse(maybeResErrsOrArgs))
|
|
208
194
|
return maybeResErrsOrArgs;
|
|
209
|
-
else if ((0, utils_1.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
213
|
-
? {
|
|
214
|
-
status: 200,
|
|
215
|
-
statusText: 'OK',
|
|
216
|
-
}
|
|
217
|
-
: {
|
|
218
|
-
status: 400,
|
|
219
|
-
statusText: 'Bad Request',
|
|
220
|
-
})), { headers: {
|
|
221
|
-
'content-type': acceptedMediaType === 'application/json'
|
|
222
|
-
? 'application/json; charset=utf-8'
|
|
223
|
-
: 'application/graphql+json; charset=utf-8',
|
|
224
|
-
} }),
|
|
225
|
-
];
|
|
195
|
+
else if ((0, utils_1.isExecutionResult)(maybeResErrsOrArgs) ||
|
|
196
|
+
(0, utils_1.areGraphQLErrors)(maybeResErrsOrArgs))
|
|
197
|
+
return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
|
|
226
198
|
else if (maybeResErrsOrArgs)
|
|
227
199
|
args = maybeResErrsOrArgs;
|
|
228
200
|
else {
|
|
@@ -234,22 +206,7 @@ function createHandler(options) {
|
|
|
234
206
|
document = parse(query);
|
|
235
207
|
}
|
|
236
208
|
catch (err) {
|
|
237
|
-
return
|
|
238
|
-
JSON.stringify({ errors: [err] }),
|
|
239
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
240
|
-
? {
|
|
241
|
-
status: 200,
|
|
242
|
-
statusText: 'OK',
|
|
243
|
-
}
|
|
244
|
-
: {
|
|
245
|
-
status: 400,
|
|
246
|
-
statusText: 'Bad Request',
|
|
247
|
-
})), { statusText: 'Bad Request', headers: {
|
|
248
|
-
'content-type': acceptedMediaType === 'application/json'
|
|
249
|
-
? 'application/json; charset=utf-8'
|
|
250
|
-
: 'application/graphql+json; charset=utf-8',
|
|
251
|
-
} }),
|
|
252
|
-
];
|
|
209
|
+
return makeResponse(err, acceptedMediaType);
|
|
253
210
|
}
|
|
254
211
|
const argsWithoutSchema = {
|
|
255
212
|
operationName,
|
|
@@ -258,13 +215,17 @@ function createHandler(options) {
|
|
|
258
215
|
};
|
|
259
216
|
if (typeof schema === 'function') {
|
|
260
217
|
const resOrSchema = await schema(req, argsWithoutSchema);
|
|
261
|
-
if (
|
|
218
|
+
if (isResponse(resOrSchema))
|
|
262
219
|
return resOrSchema;
|
|
263
220
|
args = Object.assign(Object.assign({}, argsWithoutSchema), { schema: resOrSchema });
|
|
264
221
|
}
|
|
265
222
|
else {
|
|
266
223
|
args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
|
|
267
224
|
}
|
|
225
|
+
const validationErrs = validate(args.schema, args.document);
|
|
226
|
+
if (validationErrs.length) {
|
|
227
|
+
return makeResponse(validationErrs, acceptedMediaType);
|
|
228
|
+
}
|
|
268
229
|
}
|
|
269
230
|
let operation;
|
|
270
231
|
try {
|
|
@@ -273,34 +234,19 @@ function createHandler(options) {
|
|
|
273
234
|
throw null;
|
|
274
235
|
operation = ast.operation;
|
|
275
236
|
}
|
|
276
|
-
catch (
|
|
277
|
-
return
|
|
278
|
-
'Unable to detect operation AST',
|
|
279
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
280
|
-
? {
|
|
281
|
-
status: 200,
|
|
282
|
-
statusText: 'OK',
|
|
283
|
-
}
|
|
284
|
-
: {
|
|
285
|
-
status: 400,
|
|
286
|
-
statusText: 'Bad Request',
|
|
287
|
-
})), { statusText: 'Bad Request' }),
|
|
288
|
-
];
|
|
237
|
+
catch (_d) {
|
|
238
|
+
return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType);
|
|
289
239
|
}
|
|
290
240
|
if (operation === 'subscription') {
|
|
291
|
-
return
|
|
292
|
-
'Subscriptions are not supported',
|
|
293
|
-
{
|
|
294
|
-
status: 400,
|
|
295
|
-
statusText: 'Bad Request',
|
|
296
|
-
},
|
|
297
|
-
];
|
|
241
|
+
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
298
242
|
}
|
|
299
243
|
// mutations cannot happen over GETs
|
|
300
244
|
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
301
245
|
if (operation === 'mutation' && method === 'GET') {
|
|
302
246
|
return [
|
|
303
|
-
|
|
247
|
+
JSON.stringify({
|
|
248
|
+
errors: [new graphql_1.GraphQLError('Cannot perform mutations over GET')],
|
|
249
|
+
}),
|
|
304
250
|
{
|
|
305
251
|
status: 405,
|
|
306
252
|
statusText: 'Method Not Allowed',
|
|
@@ -312,47 +258,101 @@ function createHandler(options) {
|
|
|
312
258
|
}
|
|
313
259
|
if (!('contextValue' in args)) {
|
|
314
260
|
const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
|
|
315
|
-
if (
|
|
261
|
+
if (isResponse(maybeResOrContext))
|
|
316
262
|
return maybeResOrContext;
|
|
317
263
|
args.contextValue = maybeResOrContext;
|
|
318
264
|
}
|
|
319
|
-
const validationErrs = validate(args.schema, args.document);
|
|
320
|
-
if (validationErrs.length) {
|
|
321
|
-
return [
|
|
322
|
-
JSON.stringify({ errors: validationErrs }),
|
|
323
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
324
|
-
? {
|
|
325
|
-
status: 200,
|
|
326
|
-
statusText: 'OK',
|
|
327
|
-
}
|
|
328
|
-
: {
|
|
329
|
-
status: 400,
|
|
330
|
-
statusText: 'Bad Request',
|
|
331
|
-
})), { headers: {
|
|
332
|
-
'content-type': acceptedMediaType === 'application/json'
|
|
333
|
-
? 'application/json; charset=utf-8'
|
|
334
|
-
: 'application/graphql+json; charset=utf-8',
|
|
335
|
-
} }),
|
|
336
|
-
];
|
|
337
|
-
}
|
|
338
265
|
let result = await execute(args);
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
return
|
|
342
|
-
else if (
|
|
343
|
-
result =
|
|
266
|
+
const maybeResponseOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
|
|
267
|
+
if (isResponse(maybeResponseOrResult))
|
|
268
|
+
return maybeResponseOrResult;
|
|
269
|
+
else if (maybeResponseOrResult)
|
|
270
|
+
result = maybeResponseOrResult;
|
|
271
|
+
if ((0, utils_1.isAsyncIterable)(result)) {
|
|
272
|
+
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
273
|
+
}
|
|
274
|
+
return makeResponse(result, acceptedMediaType);
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
exports.createHandler = createHandler;
|
|
278
|
+
/**
|
|
279
|
+
* Inspects the request and detects the appropriate/acceptable Media-Type
|
|
280
|
+
* looking at the `Accept` header while complying with the GraphQL over HTTP Protocol.
|
|
281
|
+
*
|
|
282
|
+
* @category Server
|
|
283
|
+
*/
|
|
284
|
+
function getAcceptableMediaType(acceptHeader) {
|
|
285
|
+
let acceptedMediaType = null;
|
|
286
|
+
const accepts = (acceptHeader || '*/*')
|
|
287
|
+
.replace(/\s/g, '')
|
|
288
|
+
.toLowerCase()
|
|
289
|
+
.split(',');
|
|
290
|
+
for (const accept of accepts) {
|
|
291
|
+
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
292
|
+
// TODO: handle the weight parameter "q"
|
|
293
|
+
const [mediaType, ...params] = accept.split(';');
|
|
294
|
+
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
295
|
+
if (mediaType === 'application/json' && charset === 'charset=utf8') {
|
|
296
|
+
acceptedMediaType = 'application/json';
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
if ((mediaType === 'application/graphql-response+json' ||
|
|
300
|
+
mediaType === 'application/*' ||
|
|
301
|
+
mediaType === '*/*') &&
|
|
302
|
+
charset === 'charset=utf8') {
|
|
303
|
+
acceptedMediaType = 'application/graphql-response+json';
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return acceptedMediaType;
|
|
308
|
+
}
|
|
309
|
+
exports.getAcceptableMediaType = getAcceptableMediaType;
|
|
310
|
+
/**
|
|
311
|
+
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
312
|
+
*
|
|
313
|
+
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
314
|
+
*
|
|
315
|
+
* If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
|
|
316
|
+
* and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
|
|
317
|
+
*
|
|
318
|
+
* @category Server
|
|
319
|
+
*/
|
|
320
|
+
function makeResponse(resultOrErrors, acceptedMediaType) {
|
|
321
|
+
if ((0, utils_1.isExecutionResult)(resultOrErrors)) {
|
|
344
322
|
return [
|
|
345
|
-
JSON.stringify(
|
|
323
|
+
JSON.stringify(resultOrErrors),
|
|
346
324
|
{
|
|
347
325
|
status: 200,
|
|
348
326
|
statusText: 'OK',
|
|
349
327
|
headers: {
|
|
350
328
|
'content-type': acceptedMediaType === 'application/json'
|
|
351
329
|
? 'application/json; charset=utf-8'
|
|
352
|
-
: 'application/graphql+json; charset=utf-8',
|
|
330
|
+
: 'application/graphql-response+json; charset=utf-8',
|
|
353
331
|
},
|
|
354
332
|
},
|
|
355
333
|
];
|
|
356
|
-
}
|
|
334
|
+
}
|
|
335
|
+
return [
|
|
336
|
+
JSON.stringify({
|
|
337
|
+
errors: Array.isArray(resultOrErrors)
|
|
338
|
+
? (0, utils_1.isObject)(resultOrErrors)
|
|
339
|
+
? resultOrErrors
|
|
340
|
+
: new graphql_1.GraphQLError(String(resultOrErrors))
|
|
341
|
+
: [resultOrErrors],
|
|
342
|
+
}),
|
|
343
|
+
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
344
|
+
? {
|
|
345
|
+
status: 200,
|
|
346
|
+
statusText: 'OK',
|
|
347
|
+
}
|
|
348
|
+
: {
|
|
349
|
+
status: 400,
|
|
350
|
+
statusText: 'Bad Request',
|
|
351
|
+
})), { headers: {
|
|
352
|
+
'content-type': acceptedMediaType === 'application/json'
|
|
353
|
+
? 'application/json; charset=utf-8'
|
|
354
|
+
: 'application/graphql-response+json; charset=utf-8',
|
|
355
|
+
} }),
|
|
356
|
+
];
|
|
357
357
|
}
|
|
358
|
-
exports.
|
|
358
|
+
exports.makeResponse = makeResponse;
|
package/lib/handler.mjs
CHANGED
|
@@ -3,9 +3,19 @@
|
|
|
3
3
|
* handler
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
-
import { validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, } from 'graphql';
|
|
7
|
-
import {
|
|
8
|
-
|
|
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
|
+
}
|
|
9
19
|
/**
|
|
10
20
|
* Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
|
|
11
21
|
* be used with your favourite server library.
|
|
@@ -78,28 +88,7 @@ export function createHandler(options) {
|
|
|
78
88
|
},
|
|
79
89
|
];
|
|
80
90
|
}
|
|
81
|
-
|
|
82
|
-
const accepts = (req.headers.accept || '*/*')
|
|
83
|
-
.replace(/\s/g, '')
|
|
84
|
-
.toLowerCase()
|
|
85
|
-
.split(',');
|
|
86
|
-
for (const accept of accepts) {
|
|
87
|
-
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
88
|
-
// TODO: handle the weight parameter "q"
|
|
89
|
-
const [mediaType, ...params] = accept.split(';');
|
|
90
|
-
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
91
|
-
if (mediaType === 'application/json' && charset === 'charset=utf8') {
|
|
92
|
-
acceptedMediaType = 'application/json';
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
if ((mediaType === 'application/graphql+json' ||
|
|
96
|
-
mediaType === 'application/*' ||
|
|
97
|
-
mediaType === '*/*') &&
|
|
98
|
-
charset === 'charset=utf8') {
|
|
99
|
-
acceptedMediaType = 'application/graphql+json';
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
91
|
+
const acceptedMediaType = getAcceptableMediaType(req.headers.accept);
|
|
103
92
|
if (!acceptedMediaType) {
|
|
104
93
|
return [
|
|
105
94
|
null,
|
|
@@ -107,7 +96,7 @@ export function createHandler(options) {
|
|
|
107
96
|
status: 406,
|
|
108
97
|
statusText: 'Not Acceptable',
|
|
109
98
|
headers: {
|
|
110
|
-
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',
|
|
111
100
|
},
|
|
112
101
|
},
|
|
113
102
|
];
|
|
@@ -149,16 +138,21 @@ export function createHandler(options) {
|
|
|
149
138
|
if (!req.body) {
|
|
150
139
|
throw new Error('Missing body');
|
|
151
140
|
}
|
|
141
|
+
let data;
|
|
152
142
|
try {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
partParams.query = data.query;
|
|
156
|
-
partParams.variables = data.variables;
|
|
157
|
-
partParams.extensions = data.extensions;
|
|
143
|
+
const body = typeof req.body === 'function' ? await req.body() : req.body;
|
|
144
|
+
data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
158
145
|
}
|
|
159
|
-
catch (
|
|
146
|
+
catch (err) {
|
|
160
147
|
throw new Error('Unparsable JSON body');
|
|
161
148
|
}
|
|
149
|
+
if (!isObject(data)) {
|
|
150
|
+
throw new Error('JSON body must be an object');
|
|
151
|
+
}
|
|
152
|
+
partParams.operationName = data.operationName;
|
|
153
|
+
partParams.query = data.query;
|
|
154
|
+
partParams.variables = data.variables;
|
|
155
|
+
partParams.extensions = data.extensions;
|
|
162
156
|
break;
|
|
163
157
|
}
|
|
164
158
|
default: // graphql-http doesnt support any other content type
|
|
@@ -170,8 +164,10 @@ export function createHandler(options) {
|
|
|
170
164
|
},
|
|
171
165
|
];
|
|
172
166
|
}
|
|
173
|
-
if (
|
|
167
|
+
if (partParams.query == null)
|
|
174
168
|
throw new Error('Missing query');
|
|
169
|
+
if (typeof partParams.query !== 'string')
|
|
170
|
+
throw new Error('Invalid query');
|
|
175
171
|
if (partParams.variables != null &&
|
|
176
172
|
(typeof partParams.variables !== 'object' ||
|
|
177
173
|
Array.isArray(partParams.variables))) {
|
|
@@ -186,40 +182,15 @@ export function createHandler(options) {
|
|
|
186
182
|
params = partParams;
|
|
187
183
|
}
|
|
188
184
|
catch (err) {
|
|
189
|
-
return
|
|
190
|
-
err.message,
|
|
191
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
192
|
-
? {
|
|
193
|
-
status: 200,
|
|
194
|
-
statusText: 'OK',
|
|
195
|
-
}
|
|
196
|
-
: {
|
|
197
|
-
status: 400,
|
|
198
|
-
statusText: 'Bad Request',
|
|
199
|
-
})), { statusText: 'Bad Request' }),
|
|
200
|
-
];
|
|
185
|
+
return makeResponse(new GraphQLError(err.message), acceptedMediaType);
|
|
201
186
|
}
|
|
202
187
|
let args;
|
|
203
188
|
const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
|
|
204
189
|
if (isResponse(maybeResErrsOrArgs))
|
|
205
190
|
return maybeResErrsOrArgs;
|
|
206
|
-
else if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
210
|
-
? {
|
|
211
|
-
status: 200,
|
|
212
|
-
statusText: 'OK',
|
|
213
|
-
}
|
|
214
|
-
: {
|
|
215
|
-
status: 400,
|
|
216
|
-
statusText: 'Bad Request',
|
|
217
|
-
})), { headers: {
|
|
218
|
-
'content-type': acceptedMediaType === 'application/json'
|
|
219
|
-
? 'application/json; charset=utf-8'
|
|
220
|
-
: 'application/graphql+json; charset=utf-8',
|
|
221
|
-
} }),
|
|
222
|
-
];
|
|
191
|
+
else if (isExecutionResult(maybeResErrsOrArgs) ||
|
|
192
|
+
areGraphQLErrors(maybeResErrsOrArgs))
|
|
193
|
+
return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
|
|
223
194
|
else if (maybeResErrsOrArgs)
|
|
224
195
|
args = maybeResErrsOrArgs;
|
|
225
196
|
else {
|
|
@@ -231,22 +202,7 @@ export function createHandler(options) {
|
|
|
231
202
|
document = parse(query);
|
|
232
203
|
}
|
|
233
204
|
catch (err) {
|
|
234
|
-
return
|
|
235
|
-
JSON.stringify({ errors: [err] }),
|
|
236
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
237
|
-
? {
|
|
238
|
-
status: 200,
|
|
239
|
-
statusText: 'OK',
|
|
240
|
-
}
|
|
241
|
-
: {
|
|
242
|
-
status: 400,
|
|
243
|
-
statusText: 'Bad Request',
|
|
244
|
-
})), { statusText: 'Bad Request', headers: {
|
|
245
|
-
'content-type': acceptedMediaType === 'application/json'
|
|
246
|
-
? 'application/json; charset=utf-8'
|
|
247
|
-
: 'application/graphql+json; charset=utf-8',
|
|
248
|
-
} }),
|
|
249
|
-
];
|
|
205
|
+
return makeResponse(err, acceptedMediaType);
|
|
250
206
|
}
|
|
251
207
|
const argsWithoutSchema = {
|
|
252
208
|
operationName,
|
|
@@ -262,6 +218,10 @@ export function createHandler(options) {
|
|
|
262
218
|
else {
|
|
263
219
|
args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
|
|
264
220
|
}
|
|
221
|
+
const validationErrs = validate(args.schema, args.document);
|
|
222
|
+
if (validationErrs.length) {
|
|
223
|
+
return makeResponse(validationErrs, acceptedMediaType);
|
|
224
|
+
}
|
|
265
225
|
}
|
|
266
226
|
let operation;
|
|
267
227
|
try {
|
|
@@ -270,34 +230,19 @@ export function createHandler(options) {
|
|
|
270
230
|
throw null;
|
|
271
231
|
operation = ast.operation;
|
|
272
232
|
}
|
|
273
|
-
catch (
|
|
274
|
-
return
|
|
275
|
-
'Unable to detect operation AST',
|
|
276
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
277
|
-
? {
|
|
278
|
-
status: 200,
|
|
279
|
-
statusText: 'OK',
|
|
280
|
-
}
|
|
281
|
-
: {
|
|
282
|
-
status: 400,
|
|
283
|
-
statusText: 'Bad Request',
|
|
284
|
-
})), { statusText: 'Bad Request' }),
|
|
285
|
-
];
|
|
233
|
+
catch (_d) {
|
|
234
|
+
return makeResponse(new GraphQLError('Unable to detect operation AST'), acceptedMediaType);
|
|
286
235
|
}
|
|
287
236
|
if (operation === 'subscription') {
|
|
288
|
-
return
|
|
289
|
-
'Subscriptions are not supported',
|
|
290
|
-
{
|
|
291
|
-
status: 400,
|
|
292
|
-
statusText: 'Bad Request',
|
|
293
|
-
},
|
|
294
|
-
];
|
|
237
|
+
return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
295
238
|
}
|
|
296
239
|
// mutations cannot happen over GETs
|
|
297
240
|
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
298
241
|
if (operation === 'mutation' && method === 'GET') {
|
|
299
242
|
return [
|
|
300
|
-
|
|
243
|
+
JSON.stringify({
|
|
244
|
+
errors: [new GraphQLError('Cannot perform mutations over GET')],
|
|
245
|
+
}),
|
|
301
246
|
{
|
|
302
247
|
status: 405,
|
|
303
248
|
statusText: 'Method Not Allowed',
|
|
@@ -313,42 +258,94 @@ export function createHandler(options) {
|
|
|
313
258
|
return maybeResOrContext;
|
|
314
259
|
args.contextValue = maybeResOrContext;
|
|
315
260
|
}
|
|
316
|
-
const validationErrs = validate(args.schema, args.document);
|
|
317
|
-
if (validationErrs.length) {
|
|
318
|
-
return [
|
|
319
|
-
JSON.stringify({ errors: validationErrs }),
|
|
320
|
-
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
321
|
-
? {
|
|
322
|
-
status: 200,
|
|
323
|
-
statusText: 'OK',
|
|
324
|
-
}
|
|
325
|
-
: {
|
|
326
|
-
status: 400,
|
|
327
|
-
statusText: 'Bad Request',
|
|
328
|
-
})), { headers: {
|
|
329
|
-
'content-type': acceptedMediaType === 'application/json'
|
|
330
|
-
? 'application/json; charset=utf-8'
|
|
331
|
-
: 'application/graphql+json; charset=utf-8',
|
|
332
|
-
} }),
|
|
333
|
-
];
|
|
334
|
-
}
|
|
335
261
|
let result = await execute(args);
|
|
336
|
-
const
|
|
337
|
-
if (isResponse(
|
|
338
|
-
return
|
|
339
|
-
else if (
|
|
340
|
-
result =
|
|
262
|
+
const maybeResponseOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
|
|
263
|
+
if (isResponse(maybeResponseOrResult))
|
|
264
|
+
return maybeResponseOrResult;
|
|
265
|
+
else if (maybeResponseOrResult)
|
|
266
|
+
result = maybeResponseOrResult;
|
|
267
|
+
if (isAsyncIterable(result)) {
|
|
268
|
+
return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
269
|
+
}
|
|
270
|
+
return makeResponse(result, acceptedMediaType);
|
|
271
|
+
};
|
|
272
|
+
}
|
|
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
|
+
export 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
|
+
/**
|
|
305
|
+
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
306
|
+
*
|
|
307
|
+
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
308
|
+
*
|
|
309
|
+
* If the first argument is _any_ object without the `data` field, it will be treated as an error (as per the spec)
|
|
310
|
+
* and the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP Protocol.
|
|
311
|
+
*
|
|
312
|
+
* @category Server
|
|
313
|
+
*/
|
|
314
|
+
export function makeResponse(resultOrErrors, acceptedMediaType) {
|
|
315
|
+
if (isExecutionResult(resultOrErrors)) {
|
|
341
316
|
return [
|
|
342
|
-
JSON.stringify(
|
|
317
|
+
JSON.stringify(resultOrErrors),
|
|
343
318
|
{
|
|
344
319
|
status: 200,
|
|
345
320
|
statusText: 'OK',
|
|
346
321
|
headers: {
|
|
347
322
|
'content-type': acceptedMediaType === 'application/json'
|
|
348
323
|
? 'application/json; charset=utf-8'
|
|
349
|
-
: 'application/graphql+json; charset=utf-8',
|
|
324
|
+
: 'application/graphql-response+json; charset=utf-8',
|
|
350
325
|
},
|
|
351
326
|
},
|
|
352
327
|
];
|
|
353
|
-
}
|
|
328
|
+
}
|
|
329
|
+
return [
|
|
330
|
+
JSON.stringify({
|
|
331
|
+
errors: Array.isArray(resultOrErrors)
|
|
332
|
+
? isObject(resultOrErrors)
|
|
333
|
+
? resultOrErrors
|
|
334
|
+
: new GraphQLError(String(resultOrErrors))
|
|
335
|
+
: [resultOrErrors],
|
|
336
|
+
}),
|
|
337
|
+
Object.assign(Object.assign({}, (acceptedMediaType === 'application/json'
|
|
338
|
+
? {
|
|
339
|
+
status: 200,
|
|
340
|
+
statusText: 'OK',
|
|
341
|
+
}
|
|
342
|
+
: {
|
|
343
|
+
status: 400,
|
|
344
|
+
statusText: 'Bad Request',
|
|
345
|
+
})), { headers: {
|
|
346
|
+
'content-type': acceptedMediaType === 'application/json'
|
|
347
|
+
? 'application/json; charset=utf-8'
|
|
348
|
+
: 'application/graphql-response+json; charset=utf-8',
|
|
349
|
+
} }),
|
|
350
|
+
];
|
|
354
351
|
}
|
package/lib/utils.d.ts
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
* utils
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
-
import type { GraphQLError } from 'graphql';
|
|
6
|
+
import type { ExecutionResult, GraphQLError } from 'graphql';
|
|
7
7
|
/** @private */
|
|
8
|
-
export declare function isObject(val: unknown): val is Record<PropertyKey,
|
|
8
|
+
export declare function isObject(val: unknown): val is Record<PropertyKey, any>;
|
|
9
9
|
/** @private */
|
|
10
|
-
export declare function areGraphQLErrors(obj: unknown): obj is GraphQLError[];
|
|
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
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.areGraphQLErrors = exports.isObject = void 0;
|
|
8
|
+
exports.isAsyncIterable = exports.isExecutionResult = exports.areGraphQLErrors = exports.isObject = void 0;
|
|
9
9
|
/** @private */
|
|
10
10
|
function isObject(val) {
|
|
11
11
|
return typeof val === 'object' && val !== null;
|
|
@@ -20,3 +20,14 @@ function areGraphQLErrors(obj) {
|
|
|
20
20
|
obj.every((ob) => 'message' in ob));
|
|
21
21
|
}
|
|
22
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
CHANGED
|
@@ -15,3 +15,12 @@ export function areGraphQLErrors(obj) {
|
|
|
15
15
|
// error has at least a message
|
|
16
16
|
obj.every((ob) => 'message' in ob));
|
|
17
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphql-http",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Simple, pluggable, zero-dependency, GraphQL over HTTP Protocol compliant server and client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"@rollup/plugin-typescript": "^8.3.4",
|
|
76
76
|
"@semantic-release/changelog": "^6.0.1",
|
|
77
77
|
"@semantic-release/git": "^10.0.1",
|
|
78
|
-
"@types/jest": "^28.1.
|
|
79
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
80
|
-
"@typescript-eslint/parser": "^5.
|
|
78
|
+
"@types/jest": "^28.1.7",
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "^5.33.1",
|
|
80
|
+
"@typescript-eslint/parser": "^5.33.1",
|
|
81
81
|
"babel-jest": "^28.1.3",
|
|
82
|
-
"eslint": "^8.
|
|
82
|
+
"eslint": "^8.22.0",
|
|
83
83
|
"eslint-config-prettier": "^8.5.0",
|
|
84
84
|
"eslint-plugin-prettier": "^4.2.1",
|
|
85
85
|
"graphql": "^16.5.0",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"node-fetch": "^3.2.10",
|
|
89
89
|
"prettier": "^2.7.1",
|
|
90
90
|
"replacestream": "^4.0.3",
|
|
91
|
-
"rollup": "^2.
|
|
91
|
+
"rollup": "^2.78.0",
|
|
92
92
|
"rollup-plugin-terser": "^7.0.2",
|
|
93
93
|
"semantic-release": "^19.0.3",
|
|
94
94
|
"tslib": "^2.4.0",
|
package/umd/graphql-http.js
CHANGED
|
@@ -14,23 +14,6 @@
|
|
|
14
14
|
return typeof val === 'object' && val !== null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
19
|
-
* common
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
/**
|
|
23
|
-
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
24
|
-
*
|
|
25
|
-
* @category Common
|
|
26
|
-
*/
|
|
27
|
-
function isResponse(val) {
|
|
28
|
-
// TODO: make sure the contents of init match ResponseInit
|
|
29
|
-
return (Array.isArray(val) &&
|
|
30
|
-
(typeof val[0] === 'string' || val[0] === null) &&
|
|
31
|
-
isObject(val[1]));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
17
|
/**
|
|
35
18
|
*
|
|
36
19
|
* client
|
|
@@ -118,7 +101,7 @@
|
|
|
118
101
|
res = await fetchFn(url, {
|
|
119
102
|
signal: control.signal,
|
|
120
103
|
method: 'POST',
|
|
121
|
-
headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql+json, application/json' }),
|
|
104
|
+
headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql-response+json, application/json' }),
|
|
122
105
|
credentials,
|
|
123
106
|
referrer,
|
|
124
107
|
referrerPolicy,
|
|
@@ -135,7 +118,7 @@
|
|
|
135
118
|
const contentType = res.headers.get('content-type');
|
|
136
119
|
if (!contentType)
|
|
137
120
|
throw new Error('Missing response content-type');
|
|
138
|
-
if (!contentType.includes('application/graphql+json') &&
|
|
121
|
+
if (!contentType.includes('application/graphql-response+json') &&
|
|
139
122
|
!contentType.includes('application/json')) {
|
|
140
123
|
throw new Error(`Unsupported response content-type ${contentType}`);
|
|
141
124
|
}
|
|
@@ -201,7 +184,6 @@
|
|
|
201
184
|
|
|
202
185
|
exports.NetworkError = NetworkError;
|
|
203
186
|
exports.createClient = createClient;
|
|
204
|
-
exports.isResponse = isResponse;
|
|
205
187
|
|
|
206
188
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
207
189
|
|
package/umd/graphql-http.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).graphqlHttp={})}(this,(function(e){"use strict";
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).graphqlHttp={})}(this,(function(e){"use strict";class t extends Error{constructor(e){let t,r;var o;(function(e){return"object"==typeof e&&null!==e})(o=e)&&"boolean"==typeof o.ok&&"number"==typeof o.status&&"string"==typeof o.statusText?(r=e,t="Server responded with "+e.status+": "+e.statusText):t=e instanceof Error?e.message:String(e),super(t),this.name=this.constructor.name,this.response=r}}e.NetworkError=t,e.createClient=function(e){const{credentials:r="same-origin",referrer:o,referrerPolicy:n,shouldRetry:s=(()=>!1)}=e,i=e.fetchFn||fetch,a=e.abortControllerImpl||AbortController,c=(()=>{let e=!1;const t=[];return{get disposed(){return e},onDispose:r=>e?(setTimeout((()=>r()),0),()=>{}):(t.push(r),()=>{t.splice(t.indexOf(r),1)}),dispose(){if(!e){e=!0;for(const e of[...t])e()}}}})();return{subscribe(p,l){if(c.disposed)throw new Error("Client has been disposed");const f=new a,u=c.onDispose((()=>{u(),f.abort()}));return(async()=>{var a;let c=null,u=0;for(;;){if(c){const e=await s(c,u);if(f.signal.aborted)return;if(!e)throw c;u++}try{const s="function"==typeof e.url?await e.url(p):e.url;if(f.signal.aborted)return;const c="function"==typeof e.headers?await e.headers():null!==(a=e.headers)&&void 0!==a?a:{};if(f.signal.aborted)return;let u;try{u=await i(s,{signal:f.signal,method:"POST",headers:Object.assign(Object.assign({},c),{"content-type":"application/json; charset=utf-8",accept:"application/graphql-response+json, application/json"}),credentials:r,referrer:o,referrerPolicy:n,body:JSON.stringify(p)})}catch(e){throw new t(e)}if(!u.ok)throw new t(u);if(!u.body)throw new Error("Missing response body");const d=u.headers.get("content-type");if(!d)throw new Error("Missing response content-type");if(!d.includes("application/graphql-response+json")&&!d.includes("application/json"))throw new Error(`Unsupported response content-type ${d}`);const h=await u.json();return l.next(h),f.abort()}catch(e){if(f.signal.aborted)return;if(!(e instanceof t))throw e;c=e}}})().then((()=>l.complete())).catch((e=>l.error(e))),()=>f.abort()},dispose(){c.dispose()}}},Object.defineProperty(e,"__esModule",{value:!0})}));
|
|
Binary file
|