graphql-http 1.1.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/README.md +2 -0
- package/lib/common.d.ts +0 -77
- package/lib/common.js +0 -14
- package/lib/common.mjs +1 -12
- package/lib/handler.d.ts +121 -8
- package/lib/handler.js +115 -120
- package/lib/handler.mjs +109 -117
- package/lib/utils.d.ts +6 -2
- package/lib/utils.js +12 -1
- package/lib/utils.mjs +9 -0
- package/package.json +4 -4
- package/umd/graphql-http.js +0 -18
- package/umd/graphql-http.min.js +1 -1
- package/umd/graphql-http.min.js.gz +0 -0
package/README.md
CHANGED
|
@@ -214,8 +214,10 @@ import { serve } from 'https://deno.land/std@0.151.0/http/server.ts';
|
|
|
214
214
|
import { createHandler } from 'https://esm.sh/graphql-http';
|
|
215
215
|
import { schema } from './previous-step';
|
|
216
216
|
|
|
217
|
+
// Create the GraphQL over HTTP handler
|
|
217
218
|
const handler = createHandler<Request>({ schema });
|
|
218
219
|
|
|
220
|
+
// Start serving on `/graphql` using the handler
|
|
219
221
|
await serve(
|
|
220
222
|
async (req: Request) => {
|
|
221
223
|
const [path, _search] = req.url.split('?');
|
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,88 @@
|
|
|
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
|
+
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;
|
|
8
89
|
/**
|
|
9
90
|
* A concrete GraphQL execution context value type.
|
|
10
91
|
*
|
|
@@ -17,7 +98,7 @@ import { Request, RequestParams, Response } from './common';
|
|
|
17
98
|
*/
|
|
18
99
|
export declare type ExecutionContext = object | symbol | number | string | boolean | undefined | null;
|
|
19
100
|
/** @category Server */
|
|
20
|
-
export interface HandlerOptions<RawRequest = unknown> {
|
|
101
|
+
export interface HandlerOptions<RawRequest = unknown, Context = unknown> {
|
|
21
102
|
/**
|
|
22
103
|
* The GraphQL schema on which the operations will
|
|
23
104
|
* be executed and validated against.
|
|
@@ -34,16 +115,18 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
34
115
|
* you should do by returning a `Request` argument which will stop
|
|
35
116
|
* further execution.
|
|
36
117
|
*/
|
|
37
|
-
schema?: GraphQLSchema | ((req: Request<RawRequest>, args: Omit<ExecutionArgs, 'schema'>) => Promise<GraphQLSchema | Response> | GraphQLSchema | Response);
|
|
118
|
+
schema?: GraphQLSchema | ((req: Request<RawRequest, Context>, args: Omit<ExecutionArgs, 'schema'>) => Promise<GraphQLSchema | Response> | GraphQLSchema | Response);
|
|
38
119
|
/**
|
|
39
120
|
* A value which is provided to every resolver and holds
|
|
40
121
|
* important contextual information like the currently
|
|
41
122
|
* logged in user, or access to a database.
|
|
42
123
|
*/
|
|
43
|
-
context?: ExecutionContext | ((req: Request<RawRequest>, args: ExecutionArgs) => Promise<ExecutionContext | Response> | ExecutionContext | Response);
|
|
124
|
+
context?: ExecutionContext | ((req: Request<RawRequest, Context>, args: ExecutionArgs) => Promise<ExecutionContext | Response> | ExecutionContext | Response);
|
|
44
125
|
/**
|
|
45
126
|
* A custom GraphQL validate function allowing you to apply your
|
|
46
127
|
* own validation rules.
|
|
128
|
+
*
|
|
129
|
+
* Will not be used when implementing a custom `onSubscribe`.
|
|
47
130
|
*/
|
|
48
131
|
validate?: typeof graphqlValidate;
|
|
49
132
|
/**
|
|
@@ -63,10 +146,16 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
63
146
|
* The subscribe callback executed right after processing the request
|
|
64
147
|
* before proceeding with the GraphQL operation execution.
|
|
65
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
|
+
*
|
|
66
153
|
* If you return `ExecutionArgs` from the callback, it will be used instead of
|
|
67
154
|
* trying to build one internally. In this case, you are responsible for providing
|
|
68
155
|
* a ready set of arguments which will be directly plugged in the operation execution.
|
|
69
156
|
*
|
|
157
|
+
* You *must* validate the `ExecutionArgs` yourself if returning them.
|
|
158
|
+
*
|
|
70
159
|
* If you return an array of `GraphQLError` from the callback, they will be reported
|
|
71
160
|
* to the client while complying with the spec.
|
|
72
161
|
*
|
|
@@ -81,7 +170,7 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
81
170
|
* you should do by returning a `Request` argument which will stop
|
|
82
171
|
* further execution.
|
|
83
172
|
*/
|
|
84
|
-
onSubscribe?: (req: Request<RawRequest>, params: RequestParams) => Promise<ExecutionArgs | GraphQLError[] | Response | void> | ExecutionArgs | GraphQLError[] | Response | void;
|
|
173
|
+
onSubscribe?: (req: Request<RawRequest, Context>, params: RequestParams) => Promise<ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void> | ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void;
|
|
85
174
|
/**
|
|
86
175
|
* Executed after the operation call resolves.
|
|
87
176
|
*
|
|
@@ -95,7 +184,7 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
95
184
|
* you should do by returning a `Request` argument which will stop
|
|
96
185
|
* further execution.
|
|
97
186
|
*/
|
|
98
|
-
onOperation?: (req: Request<RawRequest>, args: ExecutionArgs, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
187
|
+
onOperation?: (req: Request<RawRequest, Context>, args: ExecutionArgs, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
99
188
|
}
|
|
100
189
|
/**
|
|
101
190
|
* The ready-to-use handler. Simply plug it in your favourite HTTP framework
|
|
@@ -107,7 +196,7 @@ export interface HandlerOptions<RawRequest = unknown> {
|
|
|
107
196
|
*
|
|
108
197
|
* @category Server
|
|
109
198
|
*/
|
|
110
|
-
export declare type Handler<RawRequest = unknown> = (req: Request<RawRequest>) => Promise<Response>;
|
|
199
|
+
export declare type Handler<RawRequest = unknown, Context = unknown> = (req: Request<RawRequest, Context>) => Promise<Response>;
|
|
111
200
|
/**
|
|
112
201
|
* Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
|
|
113
202
|
* be used with your favourite server library.
|
|
@@ -163,4 +252,28 @@ export declare type Handler<RawRequest = unknown> = (req: Request<RawRequest>) =
|
|
|
163
252
|
*
|
|
164
253
|
* @category Server
|
|
165
254
|
*/
|
|
166
|
-
export declare function createHandler<RawRequest = unknown>(options: HandlerOptions<RawRequest>): Handler<RawRequest>;
|
|
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
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,
|
|
@@ -173,8 +163,10 @@ function createHandler(options) {
|
|
|
173
163
|
},
|
|
174
164
|
];
|
|
175
165
|
}
|
|
176
|
-
if (
|
|
166
|
+
if (partParams.query == null)
|
|
177
167
|
throw new Error('Missing query');
|
|
168
|
+
if (typeof partParams.query !== 'string')
|
|
169
|
+
throw new Error('Invalid query');
|
|
178
170
|
if (partParams.variables != null &&
|
|
179
171
|
(typeof partParams.variables !== 'object' ||
|
|
180
172
|
Array.isArray(partParams.variables))) {
|
|
@@ -189,40 +181,15 @@ function createHandler(options) {
|
|
|
189
181
|
params = partParams;
|
|
190
182
|
}
|
|
191
183
|
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
|
-
];
|
|
184
|
+
return makeResponse(new graphql_1.GraphQLError(err.message), acceptedMediaType);
|
|
204
185
|
}
|
|
205
186
|
let args;
|
|
206
187
|
const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
|
|
207
|
-
if (
|
|
188
|
+
if (isResponse(maybeResErrsOrArgs))
|
|
208
189
|
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
|
-
];
|
|
190
|
+
else if ((0, utils_1.isExecutionResult)(maybeResErrsOrArgs) ||
|
|
191
|
+
(0, utils_1.areGraphQLErrors)(maybeResErrsOrArgs))
|
|
192
|
+
return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
|
|
226
193
|
else if (maybeResErrsOrArgs)
|
|
227
194
|
args = maybeResErrsOrArgs;
|
|
228
195
|
else {
|
|
@@ -234,22 +201,7 @@ function createHandler(options) {
|
|
|
234
201
|
document = parse(query);
|
|
235
202
|
}
|
|
236
203
|
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
|
-
];
|
|
204
|
+
return makeResponse(err, acceptedMediaType);
|
|
253
205
|
}
|
|
254
206
|
const argsWithoutSchema = {
|
|
255
207
|
operationName,
|
|
@@ -258,13 +210,17 @@ function createHandler(options) {
|
|
|
258
210
|
};
|
|
259
211
|
if (typeof schema === 'function') {
|
|
260
212
|
const resOrSchema = await schema(req, argsWithoutSchema);
|
|
261
|
-
if (
|
|
213
|
+
if (isResponse(resOrSchema))
|
|
262
214
|
return resOrSchema;
|
|
263
215
|
args = Object.assign(Object.assign({}, argsWithoutSchema), { schema: resOrSchema });
|
|
264
216
|
}
|
|
265
217
|
else {
|
|
266
218
|
args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
|
|
267
219
|
}
|
|
220
|
+
const validationErrs = validate(args.schema, args.document);
|
|
221
|
+
if (validationErrs.length) {
|
|
222
|
+
return makeResponse(validationErrs, acceptedMediaType);
|
|
223
|
+
}
|
|
268
224
|
}
|
|
269
225
|
let operation;
|
|
270
226
|
try {
|
|
@@ -274,33 +230,18 @@ function createHandler(options) {
|
|
|
274
230
|
operation = ast.operation;
|
|
275
231
|
}
|
|
276
232
|
catch (_e) {
|
|
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
|
-
];
|
|
233
|
+
return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType);
|
|
289
234
|
}
|
|
290
235
|
if (operation === 'subscription') {
|
|
291
|
-
return
|
|
292
|
-
'Subscriptions are not supported',
|
|
293
|
-
{
|
|
294
|
-
status: 400,
|
|
295
|
-
statusText: 'Bad Request',
|
|
296
|
-
},
|
|
297
|
-
];
|
|
236
|
+
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
298
237
|
}
|
|
299
238
|
// mutations cannot happen over GETs
|
|
300
239
|
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
301
240
|
if (operation === 'mutation' && method === 'GET') {
|
|
302
241
|
return [
|
|
303
|
-
|
|
242
|
+
JSON.stringify({
|
|
243
|
+
errors: [new graphql_1.GraphQLError('Cannot perform mutations over GET')],
|
|
244
|
+
}),
|
|
304
245
|
{
|
|
305
246
|
status: 405,
|
|
306
247
|
statusText: 'Method Not Allowed',
|
|
@@ -312,37 +253,69 @@ function createHandler(options) {
|
|
|
312
253
|
}
|
|
313
254
|
if (!('contextValue' in args)) {
|
|
314
255
|
const maybeResOrContext = typeof context === 'function' ? await context(req, args) : context;
|
|
315
|
-
if (
|
|
256
|
+
if (isResponse(maybeResOrContext))
|
|
316
257
|
return maybeResOrContext;
|
|
317
258
|
args.contextValue = maybeResOrContext;
|
|
318
259
|
}
|
|
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
260
|
let result = await execute(args);
|
|
339
|
-
const
|
|
340
|
-
if (
|
|
341
|
-
return
|
|
342
|
-
else if (
|
|
343
|
-
result =
|
|
261
|
+
const maybeResponseOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
|
|
262
|
+
if (isResponse(maybeResponseOrResult))
|
|
263
|
+
return maybeResponseOrResult;
|
|
264
|
+
else if (maybeResponseOrResult)
|
|
265
|
+
result = maybeResponseOrResult;
|
|
266
|
+
if ((0, utils_1.isAsyncIterable)(result)) {
|
|
267
|
+
return makeResponse(new graphql_1.GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
268
|
+
}
|
|
269
|
+
return makeResponse(result, acceptedMediaType);
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
exports.createHandler = createHandler;
|
|
273
|
+
/**
|
|
274
|
+
* Inspects the request and detects the appropriate/acceptable Media-Type
|
|
275
|
+
* looking at the `Accept` header while complying with the GraphQL over HTTP Protocol.
|
|
276
|
+
*
|
|
277
|
+
* @category Server
|
|
278
|
+
*/
|
|
279
|
+
function getAcceptableMediaType(acceptHeader) {
|
|
280
|
+
let acceptedMediaType = null;
|
|
281
|
+
const accepts = (acceptHeader || '*/*')
|
|
282
|
+
.replace(/\s/g, '')
|
|
283
|
+
.toLowerCase()
|
|
284
|
+
.split(',');
|
|
285
|
+
for (const accept of accepts) {
|
|
286
|
+
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
287
|
+
// TODO: handle the weight parameter "q"
|
|
288
|
+
const [mediaType, ...params] = accept.split(';');
|
|
289
|
+
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
290
|
+
if (mediaType === 'application/json' && charset === 'charset=utf8') {
|
|
291
|
+
acceptedMediaType = 'application/json';
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
if ((mediaType === 'application/graphql+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)) {
|
|
344
317
|
return [
|
|
345
|
-
JSON.stringify(
|
|
318
|
+
JSON.stringify(resultOrErrors),
|
|
346
319
|
{
|
|
347
320
|
status: 200,
|
|
348
321
|
statusText: 'OK',
|
|
@@ -353,6 +326,28 @@ function createHandler(options) {
|
|
|
353
326
|
},
|
|
354
327
|
},
|
|
355
328
|
];
|
|
356
|
-
}
|
|
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
|
+
];
|
|
357
352
|
}
|
|
358
|
-
exports.
|
|
353
|
+
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,
|
|
@@ -170,8 +159,10 @@ export function createHandler(options) {
|
|
|
170
159
|
},
|
|
171
160
|
];
|
|
172
161
|
}
|
|
173
|
-
if (
|
|
162
|
+
if (partParams.query == null)
|
|
174
163
|
throw new Error('Missing query');
|
|
164
|
+
if (typeof partParams.query !== 'string')
|
|
165
|
+
throw new Error('Invalid query');
|
|
175
166
|
if (partParams.variables != null &&
|
|
176
167
|
(typeof partParams.variables !== 'object' ||
|
|
177
168
|
Array.isArray(partParams.variables))) {
|
|
@@ -186,40 +177,15 @@ export function createHandler(options) {
|
|
|
186
177
|
params = partParams;
|
|
187
178
|
}
|
|
188
179
|
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
|
-
];
|
|
180
|
+
return makeResponse(new GraphQLError(err.message), acceptedMediaType);
|
|
201
181
|
}
|
|
202
182
|
let args;
|
|
203
183
|
const maybeResErrsOrArgs = await (onSubscribe === null || onSubscribe === void 0 ? void 0 : onSubscribe(req, params));
|
|
204
184
|
if (isResponse(maybeResErrsOrArgs))
|
|
205
185
|
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
|
-
];
|
|
186
|
+
else if (isExecutionResult(maybeResErrsOrArgs) ||
|
|
187
|
+
areGraphQLErrors(maybeResErrsOrArgs))
|
|
188
|
+
return makeResponse(maybeResErrsOrArgs, acceptedMediaType);
|
|
223
189
|
else if (maybeResErrsOrArgs)
|
|
224
190
|
args = maybeResErrsOrArgs;
|
|
225
191
|
else {
|
|
@@ -231,22 +197,7 @@ export function createHandler(options) {
|
|
|
231
197
|
document = parse(query);
|
|
232
198
|
}
|
|
233
199
|
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
|
-
];
|
|
200
|
+
return makeResponse(err, acceptedMediaType);
|
|
250
201
|
}
|
|
251
202
|
const argsWithoutSchema = {
|
|
252
203
|
operationName,
|
|
@@ -262,6 +213,10 @@ export function createHandler(options) {
|
|
|
262
213
|
else {
|
|
263
214
|
args = Object.assign(Object.assign({}, argsWithoutSchema), { schema });
|
|
264
215
|
}
|
|
216
|
+
const validationErrs = validate(args.schema, args.document);
|
|
217
|
+
if (validationErrs.length) {
|
|
218
|
+
return makeResponse(validationErrs, acceptedMediaType);
|
|
219
|
+
}
|
|
265
220
|
}
|
|
266
221
|
let operation;
|
|
267
222
|
try {
|
|
@@ -271,33 +226,18 @@ export function createHandler(options) {
|
|
|
271
226
|
operation = ast.operation;
|
|
272
227
|
}
|
|
273
228
|
catch (_e) {
|
|
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
|
-
];
|
|
229
|
+
return makeResponse(new GraphQLError('Unable to detect operation AST'), acceptedMediaType);
|
|
286
230
|
}
|
|
287
231
|
if (operation === 'subscription') {
|
|
288
|
-
return
|
|
289
|
-
'Subscriptions are not supported',
|
|
290
|
-
{
|
|
291
|
-
status: 400,
|
|
292
|
-
statusText: 'Bad Request',
|
|
293
|
-
},
|
|
294
|
-
];
|
|
232
|
+
return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
295
233
|
}
|
|
296
234
|
// mutations cannot happen over GETs
|
|
297
235
|
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
298
236
|
if (operation === 'mutation' && method === 'GET') {
|
|
299
237
|
return [
|
|
300
|
-
|
|
238
|
+
JSON.stringify({
|
|
239
|
+
errors: [new GraphQLError('Cannot perform mutations over GET')],
|
|
240
|
+
}),
|
|
301
241
|
{
|
|
302
242
|
status: 405,
|
|
303
243
|
statusText: 'Method Not Allowed',
|
|
@@ -313,33 +253,63 @@ export function createHandler(options) {
|
|
|
313
253
|
return maybeResOrContext;
|
|
314
254
|
args.contextValue = maybeResOrContext;
|
|
315
255
|
}
|
|
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
256
|
let result = await execute(args);
|
|
336
|
-
const
|
|
337
|
-
if (isResponse(
|
|
338
|
-
return
|
|
339
|
-
else if (
|
|
340
|
-
result =
|
|
257
|
+
const maybeResponseOrResult = await (onOperation === null || onOperation === void 0 ? void 0 : onOperation(req, args, result));
|
|
258
|
+
if (isResponse(maybeResponseOrResult))
|
|
259
|
+
return maybeResponseOrResult;
|
|
260
|
+
else if (maybeResponseOrResult)
|
|
261
|
+
result = maybeResponseOrResult;
|
|
262
|
+
if (isAsyncIterable(result)) {
|
|
263
|
+
return makeResponse(new GraphQLError('Subscriptions are not supported'), acceptedMediaType);
|
|
264
|
+
}
|
|
265
|
+
return makeResponse(result, acceptedMediaType);
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Inspects the request and detects the appropriate/acceptable Media-Type
|
|
270
|
+
* looking at the `Accept` header while complying with the GraphQL over HTTP Protocol.
|
|
271
|
+
*
|
|
272
|
+
* @category Server
|
|
273
|
+
*/
|
|
274
|
+
export function getAcceptableMediaType(acceptHeader) {
|
|
275
|
+
let acceptedMediaType = null;
|
|
276
|
+
const accepts = (acceptHeader || '*/*')
|
|
277
|
+
.replace(/\s/g, '')
|
|
278
|
+
.toLowerCase()
|
|
279
|
+
.split(',');
|
|
280
|
+
for (const accept of accepts) {
|
|
281
|
+
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
282
|
+
// TODO: handle the weight parameter "q"
|
|
283
|
+
const [mediaType, ...params] = accept.split(';');
|
|
284
|
+
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
285
|
+
if (mediaType === 'application/json' && charset === 'charset=utf8') {
|
|
286
|
+
acceptedMediaType = 'application/json';
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
if ((mediaType === 'application/graphql+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)) {
|
|
341
311
|
return [
|
|
342
|
-
JSON.stringify(
|
|
312
|
+
JSON.stringify(resultOrErrors),
|
|
343
313
|
{
|
|
344
314
|
status: 200,
|
|
345
315
|
statusText: 'OK',
|
|
@@ -350,5 +320,27 @@ export function createHandler(options) {
|
|
|
350
320
|
},
|
|
351
321
|
},
|
|
352
322
|
];
|
|
353
|
-
}
|
|
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
|
+
];
|
|
354
346
|
}
|
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
8
|
export declare function isObject(val: unknown): val is Record<PropertyKey, unknown>;
|
|
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.2.0",
|
|
4
4
|
"description": "Simple, pluggable, zero-dependency, GraphQL over HTTP Protocol compliant server and client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"@semantic-release/changelog": "^6.0.1",
|
|
77
77
|
"@semantic-release/git": "^10.0.1",
|
|
78
78
|
"@types/jest": "^28.1.6",
|
|
79
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
80
|
-
"@typescript-eslint/parser": "^5.
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
|
80
|
+
"@typescript-eslint/parser": "^5.33.0",
|
|
81
81
|
"babel-jest": "^28.1.3",
|
|
82
82
|
"eslint": "^8.21.0",
|
|
83
83
|
"eslint-config-prettier": "^8.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.77.
|
|
91
|
+
"rollup": "^2.77.3",
|
|
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
|
|
@@ -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(l,p){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(l):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+json, application/json"}),credentials:r,referrer:o,referrerPolicy:n,body:JSON.stringify(l)})}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+json")&&!d.includes("application/json"))throw new Error(`Unsupported response content-type ${d}`);const h=await u.json();return p.next(h),f.abort()}catch(e){if(f.signal.aborted)return;if(!(e instanceof t))throw e;c=e}}})().then((()=>p.complete())).catch((e=>p.error(e))),()=>f.abort()},dispose(){c.dispose()}}},Object.defineProperty(e,"__esModule",{value:!0})}));
|
|
Binary file
|