graphql-http 1.19.0 → 1.20.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 +76 -4
- package/lib/client.d.mts +2 -2
- package/lib/client.d.ts +2 -2
- package/lib/common.d.mts +4 -4
- package/lib/common.d.ts +4 -4
- package/lib/handler.d.mts +30 -38
- package/lib/handler.d.ts +30 -38
- package/lib/handler.js +130 -133
- package/lib/handler.mjs +131 -131
- package/lib/utils.js +3 -1
- package/lib/utils.mjs +3 -1
- package/package.json +33 -30
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ import { schema } from './previous-step';
|
|
|
61
61
|
// Create the GraphQL over HTTP Node request handler
|
|
62
62
|
const handler = createHandler({ schema });
|
|
63
63
|
|
|
64
|
-
// Create a HTTP server using the
|
|
64
|
+
// Create a HTTP server using the listener on `/graphql`
|
|
65
65
|
const server = http.createServer((req, res) => {
|
|
66
66
|
if (req.url.startsWith('/graphql')) {
|
|
67
67
|
handler(req, res);
|
|
@@ -560,8 +560,8 @@ const client = createClient({
|
|
|
560
560
|
|
|
561
561
|
</details>
|
|
562
562
|
|
|
563
|
-
<details id="migrating-express-
|
|
564
|
-
<summary><a href="#migrating-express-
|
|
563
|
+
<details id="migrating-express-graphql">
|
|
564
|
+
<summary><a href="#migrating-express-graphql">🔗</a> Server handler migration from <a href="https://github.com/graphql/express-graphql">express-graphql</a></summary>
|
|
565
565
|
|
|
566
566
|
```diff
|
|
567
567
|
import express from 'express';
|
|
@@ -630,7 +630,7 @@ const handler = createHandler({
|
|
|
630
630
|
context: async (req, args) => {
|
|
631
631
|
return getDynamicContext(req, args);
|
|
632
632
|
},
|
|
633
|
-
// or static context by supplying the value
|
|
633
|
+
// or static context by supplying the value directly
|
|
634
634
|
});
|
|
635
635
|
```
|
|
636
636
|
|
|
@@ -725,6 +725,78 @@ console.log('Listening to port 4000');
|
|
|
725
725
|
|
|
726
726
|
</details>
|
|
727
727
|
|
|
728
|
+
<details id="graphql-upload-http">
|
|
729
|
+
<summary><a href="#graphql-upload-http">🔗</a> Server handler usage with <a href="https://github.com/jaydenseric/graphql-upload">graphql-upload</a> and <a href="https://nodejs.org/api/http.html">http</a></summary>
|
|
730
|
+
|
|
731
|
+
```js
|
|
732
|
+
import http from 'http';
|
|
733
|
+
import { createHandler } from 'graphql-http/lib/use/http';
|
|
734
|
+
import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload
|
|
735
|
+
import { schema } from './my-graphql';
|
|
736
|
+
|
|
737
|
+
const handler = createHandler({
|
|
738
|
+
schema,
|
|
739
|
+
async parseRequestParams(req) {
|
|
740
|
+
const params = await processRequest(req.raw, req.context.res);
|
|
741
|
+
if (Array.isArray(params)) {
|
|
742
|
+
throw new Error('Batching is not supported');
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
...params,
|
|
746
|
+
// variables must be an object as per the GraphQL over HTTP spec
|
|
747
|
+
variables: Object(params.variables),
|
|
748
|
+
};
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
const server = http.createServer((req, res) => {
|
|
753
|
+
if (req.url.startsWith('/graphql')) {
|
|
754
|
+
handler(req, res);
|
|
755
|
+
} else {
|
|
756
|
+
res.writeHead(404).end();
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
server.listen(4000);
|
|
761
|
+
console.log('Listening to port 4000');
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
</details>
|
|
765
|
+
|
|
766
|
+
<details id="graphql-upload-express">
|
|
767
|
+
<summary><a href="#graphql-upload-express">🔗</a> Server handler usage with <a href="https://github.com/jaydenseric/graphql-upload">graphql-upload</a> and <a href="https://expressjs.com/">express</a></summary>
|
|
768
|
+
|
|
769
|
+
```js
|
|
770
|
+
import express from 'express'; // yarn add express
|
|
771
|
+
import { createHandler } from 'graphql-http/lib/use/express';
|
|
772
|
+
import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload
|
|
773
|
+
import { schema } from './my-graphql';
|
|
774
|
+
|
|
775
|
+
const app = express();
|
|
776
|
+
app.all(
|
|
777
|
+
'/graphql',
|
|
778
|
+
createHandler({
|
|
779
|
+
schema,
|
|
780
|
+
async parseRequestParams(req) {
|
|
781
|
+
const params = await processRequest(req.raw, req.context.res);
|
|
782
|
+
if (Array.isArray(params)) {
|
|
783
|
+
throw new Error('Batching is not supported');
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
...params,
|
|
787
|
+
// variables must be an object as per the GraphQL over HTTP spec
|
|
788
|
+
variables: Object(params.variables),
|
|
789
|
+
};
|
|
790
|
+
},
|
|
791
|
+
}),
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
app.listen({ port: 4000 });
|
|
795
|
+
console.log('Listening to port 4000');
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
</details>
|
|
799
|
+
|
|
728
800
|
<details id="audit-jest">
|
|
729
801
|
<summary><a href="#audit-jest">🔗</a> Audit for servers usage in <a href="https://jestjs.io">Jest</a> environment</summary>
|
|
730
802
|
|
package/lib/client.d.mts
CHANGED
|
@@ -76,7 +76,7 @@ export interface ClientOptions {
|
|
|
76
76
|
*
|
|
77
77
|
* You may implement your own waiting strategy by timing the resolution of the returned promise.
|
|
78
78
|
*
|
|
79
|
-
* Useful for retrying requests that failed because the service is
|
|
79
|
+
* Useful for retrying requests that failed because the service is temporarily unavailable.
|
|
80
80
|
*
|
|
81
81
|
* `retries` argument counts actual retries, so it will begin with
|
|
82
82
|
* 0 after the first failed request.
|
|
@@ -135,7 +135,7 @@ export declare function createClient(options: ClientOptions): Client;
|
|
|
135
135
|
*/
|
|
136
136
|
export declare class NetworkError<Response extends ResponseLike = ResponseLike> extends Error {
|
|
137
137
|
/**
|
|
138
|
-
* The
|
|
138
|
+
* The underlying response thats considered an error.
|
|
139
139
|
*
|
|
140
140
|
* Will be undefined when no response is received,
|
|
141
141
|
* instead an unexpected network error.
|
package/lib/client.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export interface ClientOptions {
|
|
|
76
76
|
*
|
|
77
77
|
* You may implement your own waiting strategy by timing the resolution of the returned promise.
|
|
78
78
|
*
|
|
79
|
-
* Useful for retrying requests that failed because the service is
|
|
79
|
+
* Useful for retrying requests that failed because the service is temporarily unavailable.
|
|
80
80
|
*
|
|
81
81
|
* `retries` argument counts actual retries, so it will begin with
|
|
82
82
|
* 0 after the first failed request.
|
|
@@ -135,7 +135,7 @@ export declare function createClient(options: ClientOptions): Client;
|
|
|
135
135
|
*/
|
|
136
136
|
export declare class NetworkError<Response extends ResponseLike = ResponseLike> extends Error {
|
|
137
137
|
/**
|
|
138
|
-
* The
|
|
138
|
+
* The underlying response thats considered an error.
|
|
139
139
|
*
|
|
140
140
|
* Will be undefined when no response is received,
|
|
141
141
|
* instead an unexpected network error.
|
package/lib/common.d.mts
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* @category Common
|
|
12
12
|
*/
|
|
13
13
|
export interface RequestParams {
|
|
14
|
-
operationName?: string | undefined;
|
|
14
|
+
operationName?: string | null | undefined;
|
|
15
15
|
query: string;
|
|
16
|
-
variables?: Record<string, unknown> | undefined;
|
|
17
|
-
extensions?: Record<string, unknown> | undefined;
|
|
16
|
+
variables?: Record<string, unknown> | null | undefined;
|
|
17
|
+
extensions?: Record<string, unknown> | null | undefined;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* A representation of any set of values over any amount of time.
|
|
@@ -24,7 +24,7 @@ export interface RequestParams {
|
|
|
24
24
|
export interface Sink<T = unknown> {
|
|
25
25
|
/** Next value arriving. */
|
|
26
26
|
next(value: T): void;
|
|
27
|
-
/** An error that has
|
|
27
|
+
/** An error that has occurred. This function "closes" the sink. */
|
|
28
28
|
error(error: unknown): void;
|
|
29
29
|
/** The sink has completed. This function "closes" the sink. */
|
|
30
30
|
complete(): void;
|
package/lib/common.d.ts
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* @category Common
|
|
12
12
|
*/
|
|
13
13
|
export interface RequestParams {
|
|
14
|
-
operationName?: string | undefined;
|
|
14
|
+
operationName?: string | null | undefined;
|
|
15
15
|
query: string;
|
|
16
|
-
variables?: Record<string, unknown> | undefined;
|
|
17
|
-
extensions?: Record<string, unknown> | undefined;
|
|
16
|
+
variables?: Record<string, unknown> | null | undefined;
|
|
17
|
+
extensions?: Record<string, unknown> | null | undefined;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* A representation of any set of values over any amount of time.
|
|
@@ -24,7 +24,7 @@ export interface RequestParams {
|
|
|
24
24
|
export interface Sink<T = unknown> {
|
|
25
25
|
/** Next value arriving. */
|
|
26
26
|
next(value: T): void;
|
|
27
|
-
/** An error that has
|
|
27
|
+
/** An error that has occurred. This function "closes" the sink. */
|
|
28
28
|
error(error: unknown): void;
|
|
29
29
|
/** The sink has completed. This function "closes" the sink. */
|
|
30
30
|
complete(): void;
|
package/lib/handler.d.mts
CHANGED
|
@@ -84,16 +84,10 @@ export interface ResponseInit {
|
|
|
84
84
|
* @category Server
|
|
85
85
|
*/
|
|
86
86
|
export type Response = readonly [body: ResponseBody | null, init: ResponseInit];
|
|
87
|
-
/**
|
|
88
|
-
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
89
|
-
*
|
|
90
|
-
* @category Server
|
|
91
|
-
*/
|
|
92
|
-
export declare function isResponse(val: unknown): val is Response;
|
|
93
87
|
/**
|
|
94
88
|
* A concrete GraphQL execution context value type.
|
|
95
89
|
*
|
|
96
|
-
* Mainly used because TypeScript
|
|
90
|
+
* Mainly used because TypeScript collapses unions
|
|
97
91
|
* with `any` or `unknown` to `any` or `unknown`. So,
|
|
98
92
|
* we use a custom type to allow definitions such as
|
|
99
93
|
* the `context` server option.
|
|
@@ -107,6 +101,25 @@ export type OperationContext = Record<PropertyKey, unknown> | symbol | number |
|
|
|
107
101
|
* @category Server
|
|
108
102
|
*/
|
|
109
103
|
export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError | Error;
|
|
104
|
+
/**
|
|
105
|
+
* The request parser for an incoming GraphQL request. It parses and validates the
|
|
106
|
+
* request itself, including the request method and the content-type of the body.
|
|
107
|
+
*
|
|
108
|
+
* In case you are extending the server to handle more request types, this is the
|
|
109
|
+
* perfect place to do so.
|
|
110
|
+
*
|
|
111
|
+
* If an error is thrown, it will be formatted using the provided {@link FormatError}
|
|
112
|
+
* and handled following the spec to be gracefully reported to the client.
|
|
113
|
+
*
|
|
114
|
+
* Throwing an instance of `Error` will _always_ have the client respond with a `400: Bad Request`
|
|
115
|
+
* and the error's message in the response body; however, if an instance of `GraphQLError` is thrown,
|
|
116
|
+
* it will be reported depending on the accepted content-type.
|
|
117
|
+
*
|
|
118
|
+
* If you return nothing, the default parser will be used instead.
|
|
119
|
+
*
|
|
120
|
+
* @category Server
|
|
121
|
+
*/
|
|
122
|
+
export type ParseRequestParams<RequestRaw = unknown, RequestContext = unknown> = (req: Request<RequestRaw, RequestContext>) => Promise<RequestParams | Response | void> | RequestParams | Response | void;
|
|
110
123
|
/** @category Server */
|
|
111
124
|
export type OperationArgs<Context extends OperationContext = undefined> = ExecutionArgs & {
|
|
112
125
|
contextValue?: Context;
|
|
@@ -223,15 +236,21 @@ export interface HandlerOptions<RequestRaw = unknown, RequestContext = unknown,
|
|
|
223
236
|
onOperation?: (req: Request<RequestRaw, RequestContext>, args: OperationArgs<Context>, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
224
237
|
/**
|
|
225
238
|
* Format handled errors to your satisfaction. Either GraphQL errors
|
|
226
|
-
* or safe request processing errors are meant by "
|
|
239
|
+
* or safe request processing errors are meant by "handled errors".
|
|
227
240
|
*
|
|
228
|
-
* If multiple errors have
|
|
241
|
+
* If multiple errors have occurred, all of them will be mapped using
|
|
229
242
|
* this formatter.
|
|
230
243
|
*/
|
|
231
244
|
formatError?: FormatError;
|
|
245
|
+
/**
|
|
246
|
+
* The request parser for an incoming GraphQL request.
|
|
247
|
+
*
|
|
248
|
+
* Read more about it in {@link ParseRequestParams}.
|
|
249
|
+
*/
|
|
250
|
+
parseRequestParams?: ParseRequestParams<RequestRaw, RequestContext>;
|
|
232
251
|
}
|
|
233
252
|
/**
|
|
234
|
-
* The ready-to-use handler. Simply plug it in your
|
|
253
|
+
* The ready-to-use handler. Simply plug it in your favorite HTTP framework
|
|
235
254
|
* and enjoy.
|
|
236
255
|
*
|
|
237
256
|
* Errors thrown from **any** of the provided options or callbacks (or even due to
|
|
@@ -243,7 +262,7 @@ export interface HandlerOptions<RequestRaw = unknown, RequestContext = unknown,
|
|
|
243
262
|
export type Handler<RequestRaw = unknown, RequestContext = unknown> = (req: Request<RequestRaw, RequestContext>) => Promise<Response>;
|
|
244
263
|
/**
|
|
245
264
|
* Makes a GraphQL over HTTP spec compliant server handler. The handler can
|
|
246
|
-
* be used with your
|
|
265
|
+
* be used with your favorite server library.
|
|
247
266
|
*
|
|
248
267
|
* Beware that the handler resolves only after the whole operation completes.
|
|
249
268
|
*
|
|
@@ -297,30 +316,3 @@ export type Handler<RequestRaw = unknown, RequestContext = unknown> = (req: Requ
|
|
|
297
316
|
* @category Server
|
|
298
317
|
*/
|
|
299
318
|
export declare function createHandler<RequestRaw = unknown, RequestContext = unknown, Context extends OperationContext = undefined>(options: HandlerOptions<RequestRaw, RequestContext, Context>): Handler<RequestRaw, RequestContext>;
|
|
300
|
-
/**
|
|
301
|
-
* Request's Media-Type that the server accepts.
|
|
302
|
-
*
|
|
303
|
-
* @category Server
|
|
304
|
-
*/
|
|
305
|
-
export type AcceptableMediaType = 'application/graphql-response+json' | 'application/json';
|
|
306
|
-
/**
|
|
307
|
-
* Inspects the request and detects the appropriate/acceptable Media-Type
|
|
308
|
-
* looking at the `Accept` header while complying with the GraphQL over HTTP spec.
|
|
309
|
-
*
|
|
310
|
-
* @category Server
|
|
311
|
-
*/
|
|
312
|
-
export declare function getAcceptableMediaType(acceptHeader: string | null | undefined): AcceptableMediaType | null;
|
|
313
|
-
/**
|
|
314
|
-
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
315
|
-
*
|
|
316
|
-
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
317
|
-
*
|
|
318
|
-
* If the first argument is (an array of) `GraphQLError`, or an `ExecutionResult` without the `data` field, it will be treated
|
|
319
|
-
* the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP spec.
|
|
320
|
-
*
|
|
321
|
-
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
|
|
322
|
-
* error will be present in the `ExecutionResult` style.
|
|
323
|
-
*
|
|
324
|
-
* @category Server
|
|
325
|
-
*/
|
|
326
|
-
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError> | Readonly<Error>, acceptedMediaType: AcceptableMediaType, formatError: FormatError): Response;
|
package/lib/handler.d.ts
CHANGED
|
@@ -84,16 +84,10 @@ export interface ResponseInit {
|
|
|
84
84
|
* @category Server
|
|
85
85
|
*/
|
|
86
86
|
export type Response = readonly [body: ResponseBody | null, init: ResponseInit];
|
|
87
|
-
/**
|
|
88
|
-
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
89
|
-
*
|
|
90
|
-
* @category Server
|
|
91
|
-
*/
|
|
92
|
-
export declare function isResponse(val: unknown): val is Response;
|
|
93
87
|
/**
|
|
94
88
|
* A concrete GraphQL execution context value type.
|
|
95
89
|
*
|
|
96
|
-
* Mainly used because TypeScript
|
|
90
|
+
* Mainly used because TypeScript collapses unions
|
|
97
91
|
* with `any` or `unknown` to `any` or `unknown`. So,
|
|
98
92
|
* we use a custom type to allow definitions such as
|
|
99
93
|
* the `context` server option.
|
|
@@ -107,6 +101,25 @@ export type OperationContext = Record<PropertyKey, unknown> | symbol | number |
|
|
|
107
101
|
* @category Server
|
|
108
102
|
*/
|
|
109
103
|
export type FormatError = (err: Readonly<GraphQLError | Error>) => GraphQLError | Error;
|
|
104
|
+
/**
|
|
105
|
+
* The request parser for an incoming GraphQL request. It parses and validates the
|
|
106
|
+
* request itself, including the request method and the content-type of the body.
|
|
107
|
+
*
|
|
108
|
+
* In case you are extending the server to handle more request types, this is the
|
|
109
|
+
* perfect place to do so.
|
|
110
|
+
*
|
|
111
|
+
* If an error is thrown, it will be formatted using the provided {@link FormatError}
|
|
112
|
+
* and handled following the spec to be gracefully reported to the client.
|
|
113
|
+
*
|
|
114
|
+
* Throwing an instance of `Error` will _always_ have the client respond with a `400: Bad Request`
|
|
115
|
+
* and the error's message in the response body; however, if an instance of `GraphQLError` is thrown,
|
|
116
|
+
* it will be reported depending on the accepted content-type.
|
|
117
|
+
*
|
|
118
|
+
* If you return nothing, the default parser will be used instead.
|
|
119
|
+
*
|
|
120
|
+
* @category Server
|
|
121
|
+
*/
|
|
122
|
+
export type ParseRequestParams<RequestRaw = unknown, RequestContext = unknown> = (req: Request<RequestRaw, RequestContext>) => Promise<RequestParams | Response | void> | RequestParams | Response | void;
|
|
110
123
|
/** @category Server */
|
|
111
124
|
export type OperationArgs<Context extends OperationContext = undefined> = ExecutionArgs & {
|
|
112
125
|
contextValue?: Context;
|
|
@@ -223,15 +236,21 @@ export interface HandlerOptions<RequestRaw = unknown, RequestContext = unknown,
|
|
|
223
236
|
onOperation?: (req: Request<RequestRaw, RequestContext>, args: OperationArgs<Context>, result: ExecutionResult) => Promise<ExecutionResult | Response | void> | ExecutionResult | Response | void;
|
|
224
237
|
/**
|
|
225
238
|
* Format handled errors to your satisfaction. Either GraphQL errors
|
|
226
|
-
* or safe request processing errors are meant by "
|
|
239
|
+
* or safe request processing errors are meant by "handled errors".
|
|
227
240
|
*
|
|
228
|
-
* If multiple errors have
|
|
241
|
+
* If multiple errors have occurred, all of them will be mapped using
|
|
229
242
|
* this formatter.
|
|
230
243
|
*/
|
|
231
244
|
formatError?: FormatError;
|
|
245
|
+
/**
|
|
246
|
+
* The request parser for an incoming GraphQL request.
|
|
247
|
+
*
|
|
248
|
+
* Read more about it in {@link ParseRequestParams}.
|
|
249
|
+
*/
|
|
250
|
+
parseRequestParams?: ParseRequestParams<RequestRaw, RequestContext>;
|
|
232
251
|
}
|
|
233
252
|
/**
|
|
234
|
-
* The ready-to-use handler. Simply plug it in your
|
|
253
|
+
* The ready-to-use handler. Simply plug it in your favorite HTTP framework
|
|
235
254
|
* and enjoy.
|
|
236
255
|
*
|
|
237
256
|
* Errors thrown from **any** of the provided options or callbacks (or even due to
|
|
@@ -243,7 +262,7 @@ export interface HandlerOptions<RequestRaw = unknown, RequestContext = unknown,
|
|
|
243
262
|
export type Handler<RequestRaw = unknown, RequestContext = unknown> = (req: Request<RequestRaw, RequestContext>) => Promise<Response>;
|
|
244
263
|
/**
|
|
245
264
|
* Makes a GraphQL over HTTP spec compliant server handler. The handler can
|
|
246
|
-
* be used with your
|
|
265
|
+
* be used with your favorite server library.
|
|
247
266
|
*
|
|
248
267
|
* Beware that the handler resolves only after the whole operation completes.
|
|
249
268
|
*
|
|
@@ -297,30 +316,3 @@ export type Handler<RequestRaw = unknown, RequestContext = unknown> = (req: Requ
|
|
|
297
316
|
* @category Server
|
|
298
317
|
*/
|
|
299
318
|
export declare function createHandler<RequestRaw = unknown, RequestContext = unknown, Context extends OperationContext = undefined>(options: HandlerOptions<RequestRaw, RequestContext, Context>): Handler<RequestRaw, RequestContext>;
|
|
300
|
-
/**
|
|
301
|
-
* Request's Media-Type that the server accepts.
|
|
302
|
-
*
|
|
303
|
-
* @category Server
|
|
304
|
-
*/
|
|
305
|
-
export type AcceptableMediaType = 'application/graphql-response+json' | 'application/json';
|
|
306
|
-
/**
|
|
307
|
-
* Inspects the request and detects the appropriate/acceptable Media-Type
|
|
308
|
-
* looking at the `Accept` header while complying with the GraphQL over HTTP spec.
|
|
309
|
-
*
|
|
310
|
-
* @category Server
|
|
311
|
-
*/
|
|
312
|
-
export declare function getAcceptableMediaType(acceptHeader: string | null | undefined): AcceptableMediaType | null;
|
|
313
|
-
/**
|
|
314
|
-
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
315
|
-
*
|
|
316
|
-
* If the first argument is an `ExecutionResult`, the operation will be treated as "successful".
|
|
317
|
-
*
|
|
318
|
-
* If the first argument is (an array of) `GraphQLError`, or an `ExecutionResult` without the `data` field, it will be treated
|
|
319
|
-
* the response will be constructed with the help of `acceptedMediaType` complying with the GraphQL over HTTP spec.
|
|
320
|
-
*
|
|
321
|
-
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
|
|
322
|
-
* error will be present in the `ExecutionResult` style.
|
|
323
|
-
*
|
|
324
|
-
* @category Server
|
|
325
|
-
*/
|
|
326
|
-
export declare function makeResponse(resultOrErrors: Readonly<ExecutionResult> | Readonly<GraphQLError[]> | Readonly<GraphQLError> | Readonly<Error>, acceptedMediaType: AcceptableMediaType, formatError: FormatError): Response;
|
package/lib/handler.js
CHANGED
|
@@ -5,24 +5,19 @@
|
|
|
5
5
|
*
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.
|
|
8
|
+
exports.createHandler = void 0;
|
|
9
9
|
const graphql_1 = require("graphql");
|
|
10
10
|
const utils_1 = require("./utils");
|
|
11
|
-
/**
|
|
12
|
-
* Checks whether the passed value is the `graphql-http` server agnostic response.
|
|
13
|
-
*
|
|
14
|
-
* @category Server
|
|
15
|
-
*/
|
|
11
|
+
/** Checks whether the passed value is the `graphql-http` server agnostic response. */
|
|
16
12
|
function isResponse(val) {
|
|
17
13
|
// TODO: make sure the contents of init match ResponseInit
|
|
18
14
|
return (Array.isArray(val) &&
|
|
19
15
|
(typeof val[0] === 'string' || val[0] === null) &&
|
|
20
16
|
(0, utils_1.isObject)(val[1]));
|
|
21
17
|
}
|
|
22
|
-
exports.isResponse = isResponse;
|
|
23
18
|
/**
|
|
24
19
|
* Makes a GraphQL over HTTP spec compliant server handler. The handler can
|
|
25
|
-
* be used with your
|
|
20
|
+
* be used with your favorite server library.
|
|
26
21
|
*
|
|
27
22
|
* Beware that the handler resolves only after the whole operation completes.
|
|
28
23
|
*
|
|
@@ -76,23 +71,32 @@ exports.isResponse = isResponse;
|
|
|
76
71
|
* @category Server
|
|
77
72
|
*/
|
|
78
73
|
function createHandler(options) {
|
|
79
|
-
const { schema, context, validate = graphql_1.validate, validationRules = [], execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, } = options;
|
|
74
|
+
const { schema, context, validate = graphql_1.validate, validationRules = [], execute = graphql_1.execute, parse = graphql_1.parse, getOperationAST = graphql_1.getOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, parseRequestParams = defaultParseRequestParams, } = options;
|
|
80
75
|
return async function handler(req) {
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
76
|
+
let acceptedMediaType = null;
|
|
77
|
+
const accepts = (getHeader(req, 'accept') || '*/*')
|
|
78
|
+
.replace(/\s/g, '')
|
|
79
|
+
.toLowerCase()
|
|
80
|
+
.split(',');
|
|
81
|
+
for (const accept of accepts) {
|
|
82
|
+
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
83
|
+
// TODO: handle the weight parameter "q"
|
|
84
|
+
const [mediaType, ...params] = accept.split(';');
|
|
85
|
+
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
86
|
+
if (mediaType === 'application/graphql-response+json' &&
|
|
87
|
+
charset === 'charset=utf8') {
|
|
88
|
+
acceptedMediaType = 'application/graphql-response+json';
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
// application/json should be the default until watershed
|
|
92
|
+
if ((mediaType === 'application/json' ||
|
|
93
|
+
mediaType === 'application/*' ||
|
|
94
|
+
mediaType === '*/*') &&
|
|
95
|
+
charset === 'charset=utf8') {
|
|
96
|
+
acceptedMediaType = 'application/json';
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
94
99
|
}
|
|
95
|
-
const acceptedMediaType = getAcceptableMediaType(getHeader(req, 'accept'));
|
|
96
100
|
if (!acceptedMediaType) {
|
|
97
101
|
return [
|
|
98
102
|
null,
|
|
@@ -105,89 +109,14 @@ function createHandler(options) {
|
|
|
105
109
|
},
|
|
106
110
|
];
|
|
107
111
|
}
|
|
108
|
-
// TODO: should graphql-http care about content-encoding? I'd say unzipping should happen before handler is reached
|
|
109
|
-
const [mediaType, charset = 'charset=utf-8', // utf-8 is assumed when not specified. this parameter is either "charset" or "boundary" (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
|
|
110
|
-
] = (getHeader(req, 'content-type') || '')
|
|
111
|
-
.replace(/\s/g, '')
|
|
112
|
-
.toLowerCase()
|
|
113
|
-
.split(';');
|
|
114
112
|
let params;
|
|
115
113
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const searchParams = new URLSearchParams(search);
|
|
123
|
-
partParams.operationName =
|
|
124
|
-
(_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
|
|
125
|
-
partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
|
|
126
|
-
const variables = searchParams.get('variables');
|
|
127
|
-
if (variables)
|
|
128
|
-
partParams.variables = JSON.parse(variables);
|
|
129
|
-
const extensions = searchParams.get('extensions');
|
|
130
|
-
if (extensions)
|
|
131
|
-
partParams.extensions = JSON.parse(extensions);
|
|
132
|
-
}
|
|
133
|
-
catch (_c) {
|
|
134
|
-
throw new Error('Unparsable URL');
|
|
135
|
-
}
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
case method === 'POST' &&
|
|
139
|
-
mediaType === 'application/json' &&
|
|
140
|
-
charset === 'charset=utf-8':
|
|
141
|
-
{
|
|
142
|
-
if (!req.body) {
|
|
143
|
-
throw new Error('Missing body');
|
|
144
|
-
}
|
|
145
|
-
let data;
|
|
146
|
-
try {
|
|
147
|
-
const body = typeof req.body === 'function' ? await req.body() : req.body;
|
|
148
|
-
data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
throw new Error('Unparsable JSON body');
|
|
152
|
-
}
|
|
153
|
-
if (!(0, utils_1.isObject)(data)) {
|
|
154
|
-
throw new Error('JSON body must be an object');
|
|
155
|
-
}
|
|
156
|
-
partParams.operationName = data.operationName;
|
|
157
|
-
partParams.query = data.query;
|
|
158
|
-
partParams.variables = data.variables;
|
|
159
|
-
partParams.extensions = data.extensions;
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
default: // graphql-http doesnt support any other content type
|
|
163
|
-
return [
|
|
164
|
-
null,
|
|
165
|
-
{
|
|
166
|
-
status: 415,
|
|
167
|
-
statusText: 'Unsupported Media Type',
|
|
168
|
-
},
|
|
169
|
-
];
|
|
170
|
-
}
|
|
171
|
-
if (partParams.query == null)
|
|
172
|
-
throw new Error('Missing query');
|
|
173
|
-
if (typeof partParams.query !== 'string')
|
|
174
|
-
throw new Error('Invalid query');
|
|
175
|
-
if (partParams.variables != null &&
|
|
176
|
-
(typeof partParams.variables !== 'object' ||
|
|
177
|
-
Array.isArray(partParams.variables))) {
|
|
178
|
-
throw new Error('Invalid variables');
|
|
179
|
-
}
|
|
180
|
-
if (partParams.operationName != null &&
|
|
181
|
-
typeof partParams.operationName !== 'string') {
|
|
182
|
-
throw new Error('Invalid operationName');
|
|
183
|
-
}
|
|
184
|
-
if (partParams.extensions != null &&
|
|
185
|
-
(typeof partParams.extensions !== 'object' ||
|
|
186
|
-
Array.isArray(partParams.extensions))) {
|
|
187
|
-
throw new Error('Invalid extensions');
|
|
188
|
-
}
|
|
189
|
-
// request parameters are checked and now complete
|
|
190
|
-
params = partParams;
|
|
114
|
+
let paramsOrRes = await parseRequestParams(req);
|
|
115
|
+
if (!paramsOrRes)
|
|
116
|
+
paramsOrRes = await defaultParseRequestParams(req);
|
|
117
|
+
if (isResponse(paramsOrRes))
|
|
118
|
+
return paramsOrRes;
|
|
119
|
+
params = paramsOrRes;
|
|
191
120
|
}
|
|
192
121
|
catch (err) {
|
|
193
122
|
return makeResponse(err, acceptedMediaType, formatError);
|
|
@@ -249,7 +178,7 @@ function createHandler(options) {
|
|
|
249
178
|
throw null;
|
|
250
179
|
operation = ast.operation;
|
|
251
180
|
}
|
|
252
|
-
catch (
|
|
181
|
+
catch (_a) {
|
|
253
182
|
return makeResponse(new graphql_1.GraphQLError('Unable to detect operation AST'), acceptedMediaType, formatError);
|
|
254
183
|
}
|
|
255
184
|
if (operation === 'subscription') {
|
|
@@ -257,7 +186,7 @@ function createHandler(options) {
|
|
|
257
186
|
}
|
|
258
187
|
// mutations cannot happen over GETs
|
|
259
188
|
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
260
|
-
if (operation === 'mutation' && method === 'GET') {
|
|
189
|
+
if (operation === 'mutation' && req.method === 'GET') {
|
|
261
190
|
return [
|
|
262
191
|
JSON.stringify({
|
|
263
192
|
errors: [new graphql_1.GraphQLError('Cannot perform mutations over GET')],
|
|
@@ -294,38 +223,109 @@ function createHandler(options) {
|
|
|
294
223
|
}
|
|
295
224
|
exports.createHandler = createHandler;
|
|
296
225
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
226
|
+
* The default request params parser. Used when no custom one is provided or if it
|
|
227
|
+
* returns nothing.
|
|
299
228
|
*
|
|
300
|
-
* @
|
|
229
|
+
* Read more about it in {@link ParseRequestParams}.
|
|
230
|
+
*
|
|
231
|
+
* TODO: should graphql-http itself care about content-encoding? I'd say unzipping should happen before handler is reached
|
|
301
232
|
*/
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
const
|
|
233
|
+
async function defaultParseRequestParams(req) {
|
|
234
|
+
var _a, _b;
|
|
235
|
+
const method = req.method;
|
|
236
|
+
if (method !== 'GET' && method !== 'POST') {
|
|
237
|
+
return [
|
|
238
|
+
null,
|
|
239
|
+
{
|
|
240
|
+
status: 405,
|
|
241
|
+
statusText: 'Method Not Allowed',
|
|
242
|
+
headers: {
|
|
243
|
+
allow: 'GET, POST',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
const [mediaType, charset = 'charset=utf-8', // utf-8 is assumed when not specified. this parameter is either "charset" or "boundary" (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
|
|
249
|
+
] = (getHeader(req, 'content-type') || '')
|
|
305
250
|
.replace(/\s/g, '')
|
|
306
251
|
.toLowerCase()
|
|
307
|
-
.split('
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
252
|
+
.split(';');
|
|
253
|
+
const partParams = {};
|
|
254
|
+
switch (true) {
|
|
255
|
+
case method === 'GET': {
|
|
256
|
+
// TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
|
|
257
|
+
try {
|
|
258
|
+
const [, search] = req.url.split('?');
|
|
259
|
+
const searchParams = new URLSearchParams(search);
|
|
260
|
+
partParams.operationName =
|
|
261
|
+
(_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
|
|
262
|
+
partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
|
|
263
|
+
const variables = searchParams.get('variables');
|
|
264
|
+
if (variables)
|
|
265
|
+
partParams.variables = JSON.parse(variables);
|
|
266
|
+
const extensions = searchParams.get('extensions');
|
|
267
|
+
if (extensions)
|
|
268
|
+
partParams.extensions = JSON.parse(extensions);
|
|
269
|
+
}
|
|
270
|
+
catch (_c) {
|
|
271
|
+
throw new Error('Unparsable URL');
|
|
272
|
+
}
|
|
323
273
|
break;
|
|
324
274
|
}
|
|
275
|
+
case method === 'POST' &&
|
|
276
|
+
mediaType === 'application/json' &&
|
|
277
|
+
charset === 'charset=utf-8':
|
|
278
|
+
{
|
|
279
|
+
if (!req.body) {
|
|
280
|
+
throw new Error('Missing body');
|
|
281
|
+
}
|
|
282
|
+
let data;
|
|
283
|
+
try {
|
|
284
|
+
const body = typeof req.body === 'function' ? await req.body() : req.body;
|
|
285
|
+
data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
throw new Error('Unparsable JSON body');
|
|
289
|
+
}
|
|
290
|
+
if (!(0, utils_1.isObject)(data)) {
|
|
291
|
+
throw new Error('JSON body must be an object');
|
|
292
|
+
}
|
|
293
|
+
partParams.operationName = data.operationName;
|
|
294
|
+
partParams.query = data.query;
|
|
295
|
+
partParams.variables = data.variables;
|
|
296
|
+
partParams.extensions = data.extensions;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
default: // graphql-http doesnt support any other content type
|
|
300
|
+
return [
|
|
301
|
+
null,
|
|
302
|
+
{
|
|
303
|
+
status: 415,
|
|
304
|
+
statusText: 'Unsupported Media Type',
|
|
305
|
+
},
|
|
306
|
+
];
|
|
307
|
+
}
|
|
308
|
+
if (partParams.query == null)
|
|
309
|
+
throw new Error('Missing query');
|
|
310
|
+
if (typeof partParams.query !== 'string')
|
|
311
|
+
throw new Error('Invalid query');
|
|
312
|
+
if (partParams.variables != null &&
|
|
313
|
+
(typeof partParams.variables !== 'object' ||
|
|
314
|
+
Array.isArray(partParams.variables))) {
|
|
315
|
+
throw new Error('Invalid variables');
|
|
316
|
+
}
|
|
317
|
+
if (partParams.operationName != null &&
|
|
318
|
+
typeof partParams.operationName !== 'string') {
|
|
319
|
+
throw new Error('Invalid operationName');
|
|
325
320
|
}
|
|
326
|
-
|
|
321
|
+
if (partParams.extensions != null &&
|
|
322
|
+
(typeof partParams.extensions !== 'object' ||
|
|
323
|
+
Array.isArray(partParams.extensions))) {
|
|
324
|
+
throw new Error('Invalid extensions');
|
|
325
|
+
}
|
|
326
|
+
// request parameters are checked and now complete
|
|
327
|
+
return partParams;
|
|
327
328
|
}
|
|
328
|
-
exports.getAcceptableMediaType = getAcceptableMediaType;
|
|
329
329
|
/**
|
|
330
330
|
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
331
331
|
*
|
|
@@ -336,8 +336,6 @@ exports.getAcceptableMediaType = getAcceptableMediaType;
|
|
|
336
336
|
*
|
|
337
337
|
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
|
|
338
338
|
* error will be present in the `ExecutionResult` style.
|
|
339
|
-
*
|
|
340
|
-
* @category Server
|
|
341
339
|
*/
|
|
342
340
|
function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
|
|
343
341
|
if (resultOrErrors instanceof Error &&
|
|
@@ -391,7 +389,6 @@ function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
|
|
|
391
389
|
},
|
|
392
390
|
];
|
|
393
391
|
}
|
|
394
|
-
exports.makeResponse = makeResponse;
|
|
395
392
|
function getHeader(req, key) {
|
|
396
393
|
if (typeof req.headers.get === 'function') {
|
|
397
394
|
return req.headers.get(key);
|
package/lib/handler.mjs
CHANGED
|
@@ -5,12 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { validate as graphqlValidate, specifiedRules, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError, } from 'graphql';
|
|
7
7
|
import { areGraphQLErrors, isAsyncIterable, isExecutionResult, isGraphQLError, isObject, jsonErrorReplacer, } from './utils.mjs';
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
* @category Server
|
|
12
|
-
*/
|
|
13
|
-
export function isResponse(val) {
|
|
8
|
+
/** Checks whether the passed value is the `graphql-http` server agnostic response. */
|
|
9
|
+
function isResponse(val) {
|
|
14
10
|
// TODO: make sure the contents of init match ResponseInit
|
|
15
11
|
return (Array.isArray(val) &&
|
|
16
12
|
(typeof val[0] === 'string' || val[0] === null) &&
|
|
@@ -18,7 +14,7 @@ export function isResponse(val) {
|
|
|
18
14
|
}
|
|
19
15
|
/**
|
|
20
16
|
* Makes a GraphQL over HTTP spec compliant server handler. The handler can
|
|
21
|
-
* be used with your
|
|
17
|
+
* be used with your favorite server library.
|
|
22
18
|
*
|
|
23
19
|
* Beware that the handler resolves only after the whole operation completes.
|
|
24
20
|
*
|
|
@@ -72,23 +68,32 @@ export function isResponse(val) {
|
|
|
72
68
|
* @category Server
|
|
73
69
|
*/
|
|
74
70
|
export function createHandler(options) {
|
|
75
|
-
const { schema, context, validate = graphqlValidate, validationRules = [], execute = graphqlExecute, parse = graphqlParse, getOperationAST = graphqlGetOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, } = options;
|
|
71
|
+
const { schema, context, validate = graphqlValidate, validationRules = [], execute = graphqlExecute, parse = graphqlParse, getOperationAST = graphqlGetOperationAST, rootValue, onSubscribe, onOperation, formatError = (err) => err, parseRequestParams = defaultParseRequestParams, } = options;
|
|
76
72
|
return async function handler(req) {
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
let acceptedMediaType = null;
|
|
74
|
+
const accepts = (getHeader(req, 'accept') || '*/*')
|
|
75
|
+
.replace(/\s/g, '')
|
|
76
|
+
.toLowerCase()
|
|
77
|
+
.split(',');
|
|
78
|
+
for (const accept of accepts) {
|
|
79
|
+
// accept-charset became obsolete, shouldnt be used (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset)
|
|
80
|
+
// TODO: handle the weight parameter "q"
|
|
81
|
+
const [mediaType, ...params] = accept.split(';');
|
|
82
|
+
const charset = (params === null || params === void 0 ? void 0 : params.find((param) => param.includes('charset='))) || 'charset=utf8'; // utf-8 is assumed when not specified;
|
|
83
|
+
if (mediaType === 'application/graphql-response+json' &&
|
|
84
|
+
charset === 'charset=utf8') {
|
|
85
|
+
acceptedMediaType = 'application/graphql-response+json';
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
// application/json should be the default until watershed
|
|
89
|
+
if ((mediaType === 'application/json' ||
|
|
90
|
+
mediaType === 'application/*' ||
|
|
91
|
+
mediaType === '*/*') &&
|
|
92
|
+
charset === 'charset=utf8') {
|
|
93
|
+
acceptedMediaType = 'application/json';
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
90
96
|
}
|
|
91
|
-
const acceptedMediaType = getAcceptableMediaType(getHeader(req, 'accept'));
|
|
92
97
|
if (!acceptedMediaType) {
|
|
93
98
|
return [
|
|
94
99
|
null,
|
|
@@ -101,89 +106,14 @@ export function createHandler(options) {
|
|
|
101
106
|
},
|
|
102
107
|
];
|
|
103
108
|
}
|
|
104
|
-
// TODO: should graphql-http care about content-encoding? I'd say unzipping should happen before handler is reached
|
|
105
|
-
const [mediaType, charset = 'charset=utf-8', // utf-8 is assumed when not specified. this parameter is either "charset" or "boundary" (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
|
|
106
|
-
] = (getHeader(req, 'content-type') || '')
|
|
107
|
-
.replace(/\s/g, '')
|
|
108
|
-
.toLowerCase()
|
|
109
|
-
.split(';');
|
|
110
109
|
let params;
|
|
111
110
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const searchParams = new URLSearchParams(search);
|
|
119
|
-
partParams.operationName =
|
|
120
|
-
(_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
|
|
121
|
-
partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
|
|
122
|
-
const variables = searchParams.get('variables');
|
|
123
|
-
if (variables)
|
|
124
|
-
partParams.variables = JSON.parse(variables);
|
|
125
|
-
const extensions = searchParams.get('extensions');
|
|
126
|
-
if (extensions)
|
|
127
|
-
partParams.extensions = JSON.parse(extensions);
|
|
128
|
-
}
|
|
129
|
-
catch (_c) {
|
|
130
|
-
throw new Error('Unparsable URL');
|
|
131
|
-
}
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
case method === 'POST' &&
|
|
135
|
-
mediaType === 'application/json' &&
|
|
136
|
-
charset === 'charset=utf-8':
|
|
137
|
-
{
|
|
138
|
-
if (!req.body) {
|
|
139
|
-
throw new Error('Missing body');
|
|
140
|
-
}
|
|
141
|
-
let data;
|
|
142
|
-
try {
|
|
143
|
-
const body = typeof req.body === 'function' ? await req.body() : req.body;
|
|
144
|
-
data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
145
|
-
}
|
|
146
|
-
catch (err) {
|
|
147
|
-
throw new Error('Unparsable JSON body');
|
|
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;
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
default: // graphql-http doesnt support any other content type
|
|
159
|
-
return [
|
|
160
|
-
null,
|
|
161
|
-
{
|
|
162
|
-
status: 415,
|
|
163
|
-
statusText: 'Unsupported Media Type',
|
|
164
|
-
},
|
|
165
|
-
];
|
|
166
|
-
}
|
|
167
|
-
if (partParams.query == null)
|
|
168
|
-
throw new Error('Missing query');
|
|
169
|
-
if (typeof partParams.query !== 'string')
|
|
170
|
-
throw new Error('Invalid query');
|
|
171
|
-
if (partParams.variables != null &&
|
|
172
|
-
(typeof partParams.variables !== 'object' ||
|
|
173
|
-
Array.isArray(partParams.variables))) {
|
|
174
|
-
throw new Error('Invalid variables');
|
|
175
|
-
}
|
|
176
|
-
if (partParams.operationName != null &&
|
|
177
|
-
typeof partParams.operationName !== 'string') {
|
|
178
|
-
throw new Error('Invalid operationName');
|
|
179
|
-
}
|
|
180
|
-
if (partParams.extensions != null &&
|
|
181
|
-
(typeof partParams.extensions !== 'object' ||
|
|
182
|
-
Array.isArray(partParams.extensions))) {
|
|
183
|
-
throw new Error('Invalid extensions');
|
|
184
|
-
}
|
|
185
|
-
// request parameters are checked and now complete
|
|
186
|
-
params = partParams;
|
|
111
|
+
let paramsOrRes = await parseRequestParams(req);
|
|
112
|
+
if (!paramsOrRes)
|
|
113
|
+
paramsOrRes = await defaultParseRequestParams(req);
|
|
114
|
+
if (isResponse(paramsOrRes))
|
|
115
|
+
return paramsOrRes;
|
|
116
|
+
params = paramsOrRes;
|
|
187
117
|
}
|
|
188
118
|
catch (err) {
|
|
189
119
|
return makeResponse(err, acceptedMediaType, formatError);
|
|
@@ -245,7 +175,7 @@ export function createHandler(options) {
|
|
|
245
175
|
throw null;
|
|
246
176
|
operation = ast.operation;
|
|
247
177
|
}
|
|
248
|
-
catch (
|
|
178
|
+
catch (_a) {
|
|
249
179
|
return makeResponse(new GraphQLError('Unable to detect operation AST'), acceptedMediaType, formatError);
|
|
250
180
|
}
|
|
251
181
|
if (operation === 'subscription') {
|
|
@@ -253,7 +183,7 @@ export function createHandler(options) {
|
|
|
253
183
|
}
|
|
254
184
|
// mutations cannot happen over GETs
|
|
255
185
|
// https://graphql.github.io/graphql-over-http/draft/#sel-CALFJRPAAELBAAxwP
|
|
256
|
-
if (operation === 'mutation' && method === 'GET') {
|
|
186
|
+
if (operation === 'mutation' && req.method === 'GET') {
|
|
257
187
|
return [
|
|
258
188
|
JSON.stringify({
|
|
259
189
|
errors: [new GraphQLError('Cannot perform mutations over GET')],
|
|
@@ -289,36 +219,108 @@ export function createHandler(options) {
|
|
|
289
219
|
};
|
|
290
220
|
}
|
|
291
221
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
222
|
+
* The default request params parser. Used when no custom one is provided or if it
|
|
223
|
+
* returns nothing.
|
|
294
224
|
*
|
|
295
|
-
* @
|
|
225
|
+
* Read more about it in {@link ParseRequestParams}.
|
|
226
|
+
*
|
|
227
|
+
* TODO: should graphql-http itself care about content-encoding? I'd say unzipping should happen before handler is reached
|
|
296
228
|
*/
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
229
|
+
async function defaultParseRequestParams(req) {
|
|
230
|
+
var _a, _b;
|
|
231
|
+
const method = req.method;
|
|
232
|
+
if (method !== 'GET' && method !== 'POST') {
|
|
233
|
+
return [
|
|
234
|
+
null,
|
|
235
|
+
{
|
|
236
|
+
status: 405,
|
|
237
|
+
statusText: 'Method Not Allowed',
|
|
238
|
+
headers: {
|
|
239
|
+
allow: 'GET, POST',
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
const [mediaType, charset = 'charset=utf-8', // utf-8 is assumed when not specified. this parameter is either "charset" or "boundary" (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length)
|
|
245
|
+
] = (getHeader(req, 'content-type') || '')
|
|
300
246
|
.replace(/\s/g, '')
|
|
301
247
|
.toLowerCase()
|
|
302
|
-
.split('
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
248
|
+
.split(';');
|
|
249
|
+
const partParams = {};
|
|
250
|
+
switch (true) {
|
|
251
|
+
case method === 'GET': {
|
|
252
|
+
// TODO: what if content-type is specified and is not application/x-www-form-urlencoded?
|
|
253
|
+
try {
|
|
254
|
+
const [, search] = req.url.split('?');
|
|
255
|
+
const searchParams = new URLSearchParams(search);
|
|
256
|
+
partParams.operationName =
|
|
257
|
+
(_a = searchParams.get('operationName')) !== null && _a !== void 0 ? _a : undefined;
|
|
258
|
+
partParams.query = (_b = searchParams.get('query')) !== null && _b !== void 0 ? _b : undefined;
|
|
259
|
+
const variables = searchParams.get('variables');
|
|
260
|
+
if (variables)
|
|
261
|
+
partParams.variables = JSON.parse(variables);
|
|
262
|
+
const extensions = searchParams.get('extensions');
|
|
263
|
+
if (extensions)
|
|
264
|
+
partParams.extensions = JSON.parse(extensions);
|
|
265
|
+
}
|
|
266
|
+
catch (_c) {
|
|
267
|
+
throw new Error('Unparsable URL');
|
|
268
|
+
}
|
|
318
269
|
break;
|
|
319
270
|
}
|
|
271
|
+
case method === 'POST' &&
|
|
272
|
+
mediaType === 'application/json' &&
|
|
273
|
+
charset === 'charset=utf-8':
|
|
274
|
+
{
|
|
275
|
+
if (!req.body) {
|
|
276
|
+
throw new Error('Missing body');
|
|
277
|
+
}
|
|
278
|
+
let data;
|
|
279
|
+
try {
|
|
280
|
+
const body = typeof req.body === 'function' ? await req.body() : req.body;
|
|
281
|
+
data = typeof body === 'string' ? JSON.parse(body) : body;
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
throw new Error('Unparsable JSON body');
|
|
285
|
+
}
|
|
286
|
+
if (!isObject(data)) {
|
|
287
|
+
throw new Error('JSON body must be an object');
|
|
288
|
+
}
|
|
289
|
+
partParams.operationName = data.operationName;
|
|
290
|
+
partParams.query = data.query;
|
|
291
|
+
partParams.variables = data.variables;
|
|
292
|
+
partParams.extensions = data.extensions;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
default: // graphql-http doesnt support any other content type
|
|
296
|
+
return [
|
|
297
|
+
null,
|
|
298
|
+
{
|
|
299
|
+
status: 415,
|
|
300
|
+
statusText: 'Unsupported Media Type',
|
|
301
|
+
},
|
|
302
|
+
];
|
|
303
|
+
}
|
|
304
|
+
if (partParams.query == null)
|
|
305
|
+
throw new Error('Missing query');
|
|
306
|
+
if (typeof partParams.query !== 'string')
|
|
307
|
+
throw new Error('Invalid query');
|
|
308
|
+
if (partParams.variables != null &&
|
|
309
|
+
(typeof partParams.variables !== 'object' ||
|
|
310
|
+
Array.isArray(partParams.variables))) {
|
|
311
|
+
throw new Error('Invalid variables');
|
|
312
|
+
}
|
|
313
|
+
if (partParams.operationName != null &&
|
|
314
|
+
typeof partParams.operationName !== 'string') {
|
|
315
|
+
throw new Error('Invalid operationName');
|
|
320
316
|
}
|
|
321
|
-
|
|
317
|
+
if (partParams.extensions != null &&
|
|
318
|
+
(typeof partParams.extensions !== 'object' ||
|
|
319
|
+
Array.isArray(partParams.extensions))) {
|
|
320
|
+
throw new Error('Invalid extensions');
|
|
321
|
+
}
|
|
322
|
+
// request parameters are checked and now complete
|
|
323
|
+
return partParams;
|
|
322
324
|
}
|
|
323
325
|
/**
|
|
324
326
|
* Creates an appropriate GraphQL over HTTP response following the provided arguments.
|
|
@@ -330,10 +332,8 @@ export function getAcceptableMediaType(acceptHeader) {
|
|
|
330
332
|
*
|
|
331
333
|
* If the first argument is an `Error`, the operation will be treated as a bad request responding with `400: Bad Request` and the
|
|
332
334
|
* error will be present in the `ExecutionResult` style.
|
|
333
|
-
*
|
|
334
|
-
* @category Server
|
|
335
335
|
*/
|
|
336
|
-
|
|
336
|
+
function makeResponse(resultOrErrors, acceptedMediaType, formatError) {
|
|
337
337
|
if (resultOrErrors instanceof Error &&
|
|
338
338
|
// because GraphQLError extends the Error class
|
|
339
339
|
!isGraphQLError(resultOrErrors)) {
|
package/lib/utils.js
CHANGED
|
@@ -48,7 +48,9 @@ function isAsyncIterable(val) {
|
|
|
48
48
|
}
|
|
49
49
|
exports.isAsyncIterable = isAsyncIterable;
|
|
50
50
|
/** @private */
|
|
51
|
-
function jsonErrorReplacer(_key,
|
|
51
|
+
function jsonErrorReplacer(_key,
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
val) {
|
|
52
54
|
if (val instanceof Error &&
|
|
53
55
|
// GraphQL errors implement their own stringer
|
|
54
56
|
!isGraphQLError(val)) {
|
package/lib/utils.mjs
CHANGED
|
@@ -39,7 +39,9 @@ export function isAsyncIterable(val) {
|
|
|
39
39
|
return typeof Object(val)[Symbol.asyncIterator] === 'function';
|
|
40
40
|
}
|
|
41
41
|
/** @private */
|
|
42
|
-
export function jsonErrorReplacer(_key,
|
|
42
|
+
export function jsonErrorReplacer(_key,
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
val) {
|
|
43
45
|
if (val instanceof Error &&
|
|
44
46
|
// GraphQL errors implement their own stringer
|
|
45
47
|
!isGraphQLError(val)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphql-http",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
4
4
|
"description": "Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphql",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=12"
|
|
26
26
|
},
|
|
27
|
-
"packageManager": "yarn@3.6.
|
|
27
|
+
"packageManager": "yarn@3.6.1",
|
|
28
28
|
"main": "lib/index.js",
|
|
29
29
|
"module": "lib/index.mjs",
|
|
30
30
|
"browser": "umd/graphql-http.js",
|
|
@@ -91,18 +91,18 @@
|
|
|
91
91
|
"access": "public"
|
|
92
92
|
},
|
|
93
93
|
"scripts": {
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"lint
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"type-check": "tsc --noEmit",
|
|
94
|
+
"check:format": "prettier --check .",
|
|
95
|
+
"format": "yarn check:format --write",
|
|
96
|
+
"check:lint": "eslint 'src'",
|
|
97
|
+
"check:spell": "cspell --gitignore **/*.md",
|
|
98
|
+
"check:type": "tsc --noEmit",
|
|
100
99
|
"test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 jest",
|
|
101
|
-
"build:esm": "tsc -b tsconfig.esm.json &&
|
|
100
|
+
"build:esm": "tsc -b tsconfig.esm.json && tsx scripts/esm-post-process.ts",
|
|
102
101
|
"build:cjs": "tsc -b tsconfig.cjs.json",
|
|
103
102
|
"build:umd": "rollup --configPlugin typescript --config rollup.config.ts",
|
|
104
103
|
"build": "yarn build:esm && yarn build:cjs && yarn build:umd",
|
|
105
|
-
"release": "semantic-release"
|
|
104
|
+
"release": "semantic-release",
|
|
105
|
+
"gendocs": "typedoc --options typedoc.js src/"
|
|
106
106
|
},
|
|
107
107
|
"workspaces": [
|
|
108
108
|
"implementations/**/*"
|
|
@@ -111,49 +111,52 @@
|
|
|
111
111
|
"graphql": ">=0.11 <=16"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
|
-
"@babel/core": "^7.22.
|
|
114
|
+
"@babel/core": "^7.22.8",
|
|
115
115
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
116
116
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
|
|
117
117
|
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
|
118
118
|
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
|
119
|
-
"@babel/preset-env": "^7.22.
|
|
120
|
-
"@babel/preset-typescript": "^7.
|
|
119
|
+
"@babel/preset-env": "^7.22.7",
|
|
120
|
+
"@babel/preset-typescript": "^7.22.5",
|
|
121
|
+
"@cspell/cspell-types": "^6.31.1",
|
|
121
122
|
"@rollup/plugin-terser": "^0.4.3",
|
|
122
|
-
"@rollup/plugin-typescript": "^11.1.
|
|
123
|
+
"@rollup/plugin-typescript": "^11.1.2",
|
|
123
124
|
"@semantic-release/changelog": "^6.0.3",
|
|
124
125
|
"@semantic-release/git": "^10.0.1",
|
|
125
|
-
"@types/eslint": "^8.
|
|
126
|
+
"@types/eslint": "^8.44.0",
|
|
126
127
|
"@types/express": "^4.17.17",
|
|
127
128
|
"@types/glob": "^8.1.0",
|
|
128
129
|
"@types/html-validator": "^5.0.3",
|
|
129
130
|
"@types/jest": "^29.5.2",
|
|
130
|
-
"@types/k6": "^0.
|
|
131
|
+
"@types/k6": "^0.45.0",
|
|
131
132
|
"@types/koa": "^2.13.6",
|
|
132
133
|
"@types/koa-mount": "^4.0.2",
|
|
133
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
134
|
-
"@typescript-eslint/parser": "^5.
|
|
135
|
-
"@whatwg-node/fetch": "^0.9.
|
|
136
|
-
"babel-jest": "^29.
|
|
137
|
-
"
|
|
134
|
+
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
|
135
|
+
"@typescript-eslint/parser": "^5.61.0",
|
|
136
|
+
"@whatwg-node/fetch": "^0.9.7",
|
|
137
|
+
"babel-jest": "^29.6.1",
|
|
138
|
+
"cspell": "^6.31.1",
|
|
139
|
+
"eslint": "^8.44.0",
|
|
138
140
|
"eslint-config-prettier": "^8.8.0",
|
|
139
141
|
"express": "^4.18.2",
|
|
140
|
-
"fastify": "^4.
|
|
141
|
-
"glob": "^10.2
|
|
142
|
-
"graphql": "^16.
|
|
142
|
+
"fastify": "^4.19.2",
|
|
143
|
+
"glob": "^10.3.2",
|
|
144
|
+
"graphql": "^16.7.1",
|
|
143
145
|
"html-validator": "^6.0.1",
|
|
144
|
-
"jest": "^29.
|
|
145
|
-
"jest-jasmine2": "^29.
|
|
146
|
+
"jest": "^29.6.1",
|
|
147
|
+
"jest-jasmine2": "^29.6.1",
|
|
146
148
|
"koa": "^2.14.2",
|
|
147
149
|
"koa-mount": "^4.0.0",
|
|
148
150
|
"node-fetch": "^3.3.1",
|
|
149
151
|
"prettier": "^2.8.8",
|
|
150
|
-
"rollup": "^3.
|
|
152
|
+
"rollup": "^3.26.2",
|
|
151
153
|
"rollup-plugin-gzip": "^3.1.0",
|
|
152
|
-
"semantic-release": "^21.0.
|
|
153
|
-
"tslib": "^2.
|
|
154
|
+
"semantic-release": "^21.0.7",
|
|
155
|
+
"tslib": "^2.6.0",
|
|
156
|
+
"tsx": "^3.12.7",
|
|
154
157
|
"typedoc": "^0.24.8",
|
|
155
158
|
"typedoc-plugin-markdown": "^3.15.3",
|
|
156
|
-
"typescript": "^5.1.
|
|
159
|
+
"typescript": "^5.1.6",
|
|
157
160
|
"uWebSockets.js": "uNetworking/uWebSockets.js#v20.30.0"
|
|
158
161
|
}
|
|
159
162
|
}
|