graphql-http 1.0.0 → 1.3.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 CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  <h3>graphql-http</h3>
5
5
 
6
- <h6>Simple, plugable, zero-dependency, <a href="https://graphql.github.io/graphql-over-http">GraphQL over HTTP Protocol</a> compliant server and client.</h6>
6
+ <h6>Simple, pluggable, zero-dependency, <a href="https://graphql.github.io/graphql-over-http">GraphQL over HTTP Protocol</a> compliant server and client.</h6>
7
7
 
8
8
  [![Continuous integration](https://github.com/enisdenjo/graphql-http/workflows/Continuous%20integration/badge.svg)](https://github.com/enisdenjo/graphql-http/actions?query=workflow%3A%22Continuous+integration%22) [![graphql-http](https://img.shields.io/npm/v/graphql-http.svg?label=graphql-http&logo=npm)](https://www.npmjs.com/package/graphql-http)
9
9
 
@@ -207,6 +207,43 @@ fastify.listen(4000);
207
207
  console.log('Listening to port 4000');
208
208
  ```
209
209
 
210
+ ##### With [`Deno`](https://deno.land/)
211
+
212
+ ```ts
213
+ import { serve } from 'https://deno.land/std@0.151.0/http/server.ts';
214
+ import { createHandler } from 'https://esm.sh/graphql-http';
215
+ import { schema } from './previous-step';
216
+
217
+ // Create the GraphQL over HTTP handler
218
+ const handler = createHandler<Request>({ schema });
219
+
220
+ // Start serving on `/graphql` using the handler
221
+ await serve(
222
+ async (req: Request) => {
223
+ const [path, _search] = req.url.split('?');
224
+ if (!path.endsWith('/graphql')) {
225
+ return new Response(null, { status: 404, statusText: 'Not Found' });
226
+ }
227
+
228
+ const headers: Record<string, string> = {};
229
+ req.headers.forEach((value, key) => (headers[key] = value));
230
+ const [body, init] = await handler({
231
+ url: req.url,
232
+ method: req.method,
233
+ headers,
234
+ body: await req.text(),
235
+ raw: req,
236
+ });
237
+ return new Response(body, init);
238
+ },
239
+ {
240
+ port: 4000,
241
+ },
242
+ );
243
+
244
+ // Listening to port 4000
245
+ ```
246
+
210
247
  #### Use the client
211
248
 
212
249
  ```js
@@ -515,6 +552,21 @@ const client = createClient({
515
552
 
516
553
  </details>
517
554
 
555
+ <details id="deno-client">
556
+ <summary><a href="#deno-client">🔗</a> Client usage in Deno</summary>
557
+
558
+ ```js
559
+ import { createClient } from 'graphql-http';
560
+
561
+ const client = createClient({
562
+ url: 'http://deno.earth:4000/graphql',
563
+ });
564
+
565
+ // consider other recipes for usage inspiration
566
+ ```
567
+
568
+ </details>
569
+
518
570
  <details id="auth">
519
571
  <summary><a href="#auth">🔗</a> Server handler usage with authentication</summary>
520
572
 
package/lib/client.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * client
4
4
  *
5
5
  */
6
- import { ExecutionResult } from 'graphql';
6
+ import type { ExecutionResult } from 'graphql';
7
7
  import { RequestParams, Sink } from './common';
8
8
  /** This file is the entry point for browsers, re-export common elements. */
9
9
  export * from './common';
@@ -18,8 +18,11 @@ export interface ClientOptions {
18
18
  *
19
19
  * A good use-case for having a function is when using the URL for authentication,
20
20
  * where subsequent requests (due to auth) may have a refreshed identity token.
21
+ *
22
+ * Function receives the request params. Useful for example, to ease up debugging and DevTools
23
+ * navigation you might want to use the operation name in the URL's search params (`/graphql?MyQuery`).
21
24
  */
22
- url: string | (() => Promise<string> | string);
25
+ url: string | ((request: RequestParams) => Promise<string> | string);
23
26
  /**
24
27
  * Indicates whether the user agent should send cookies from the other domain in the case
25
28
  * of cross-origin requests.
@@ -140,7 +143,12 @@ export declare class NetworkError<Response extends ResponseLike = ResponseLike>
140
143
  response: Response | undefined;
141
144
  constructor(msgOrErrOrResponse: string | Error | Response);
142
145
  }
143
- interface ResponseLike {
146
+ /**
147
+ * Concrete interface a response needs to implement for the client.
148
+ *
149
+ * @category Client
150
+ */
151
+ export interface ResponseLike {
144
152
  readonly ok: boolean;
145
153
  readonly status: number;
146
154
  readonly statusText: string;
package/lib/client.js CHANGED
@@ -91,7 +91,7 @@ function createClient(options) {
91
91
  }
92
92
  try {
93
93
  const url = typeof options.url === 'function'
94
- ? await options.url()
94
+ ? await options.url(request)
95
95
  : options.url;
96
96
  if (control.signal.aborted)
97
97
  return;
@@ -105,7 +105,7 @@ function createClient(options) {
105
105
  res = await fetchFn(url, {
106
106
  signal: control.signal,
107
107
  method: 'POST',
108
- headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql+json, application/json' }),
108
+ headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql-response+json, application/json' }),
109
109
  credentials,
110
110
  referrer,
111
111
  referrerPolicy,
@@ -122,7 +122,7 @@ function createClient(options) {
122
122
  const contentType = res.headers.get('content-type');
123
123
  if (!contentType)
124
124
  throw new Error('Missing response content-type');
125
- if (!contentType.includes('application/graphql+json') &&
125
+ if (!contentType.includes('application/graphql-response+json') &&
126
126
  !contentType.includes('application/json')) {
127
127
  throw new Error(`Unsupported response content-type ${contentType}`);
128
128
  }
package/lib/client.mjs CHANGED
@@ -74,7 +74,7 @@ export function createClient(options) {
74
74
  }
75
75
  try {
76
76
  const url = typeof options.url === 'function'
77
- ? await options.url()
77
+ ? await options.url(request)
78
78
  : options.url;
79
79
  if (control.signal.aborted)
80
80
  return;
@@ -88,7 +88,7 @@ export function createClient(options) {
88
88
  res = await fetchFn(url, {
89
89
  signal: control.signal,
90
90
  method: 'POST',
91
- headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql+json, application/json' }),
91
+ headers: Object.assign(Object.assign({}, headers), { 'content-type': 'application/json; charset=utf-8', accept: 'application/graphql-response+json, application/json' }),
92
92
  credentials,
93
93
  referrer,
94
94
  referrerPolicy,
@@ -105,7 +105,7 @@ export function createClient(options) {
105
105
  const contentType = res.headers.get('content-type');
106
106
  if (!contentType)
107
107
  throw new Error('Missing response content-type');
108
- if (!contentType.includes('application/graphql+json') &&
108
+ if (!contentType.includes('application/graphql-response+json') &&
109
109
  !contentType.includes('application/json')) {
110
110
  throw new Error(`Unsupported response content-type ${contentType}`);
111
111
  }
package/lib/common.d.ts CHANGED
@@ -3,42 +3,6 @@
3
3
  * common
4
4
  *
5
5
  */
6
- /**
7
- * Concrete interface that the headers map should implement.
8
- *
9
- * @category Common
10
- */
11
- export interface Headers {
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: Headers;
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,37 +16,6 @@ export interface RequestParams {
52
16
  variables?: Record<string, unknown> | undefined;
53
17
  extensions?: Record<string, unknown> | undefined;
54
18
  }
55
- /**
56
- * Server agnostic response body returned from `graphql-http` needing
57
- * to be coerced to the server implementation in use.
58
- *
59
- * @category Common
60
- */
61
- export declare type ResponseBody = string;
62
- /**
63
- * Server agnostic response options (ex. status and headers) returned from
64
- * `graphql-http` needing to be coerced to the server implementation in use.
65
- *
66
- * @category Common
67
- */
68
- export interface ResponseInit {
69
- readonly status: number;
70
- readonly statusText?: string;
71
- readonly headers?: Headers;
72
- }
73
- /**
74
- * Server agnostic response returned from `graphql-http` containing the
75
- * body and init options needing to be coerced to the server implementation in use.
76
- *
77
- * @category Common
78
- */
79
- export declare type Response = readonly [body: ResponseBody | null, init: ResponseInit];
80
- /**
81
- * Checks whether the passed value is the `graphql-http` server agnostic response.
82
- *
83
- * @category Common
84
- */
85
- export declare function isResponse(val: unknown): val is Response;
86
19
  /**
87
20
  * A representation of any set of values over any amount of time.
88
21
  *
package/lib/common.js CHANGED
@@ -5,15 +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) && typeof val[0] === 'string' && (0, utils_1.isObject)(val[1]);
18
- }
19
- exports.isResponse = isResponse;
package/lib/common.mjs CHANGED
@@ -3,13 +3,4 @@
3
3
  * common
4
4
  *
5
5
  */
6
- import { isObject } from './utils.mjs';
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) && typeof val[0] === 'string' && isObject(val[1]);
15
- }
6
+ export {};
package/lib/handler.d.ts CHANGED
@@ -3,8 +3,89 @@
3
3
  * handler
4
4
  *
5
5
  */
6
- import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, execute as graphqlExecute } from 'graphql';
7
- import { Request, RequestParams, Response } from './common';
6
+ import { ExecutionArgs, ExecutionResult, GraphQLSchema, validate as graphqlValidate, execute as graphqlExecute, parse as graphqlParse, getOperationAST as graphqlGetOperationAST, GraphQLError } from 'graphql';
7
+ import { RequestParams } from './common';
8
+ /**
9
+ * The incoming request headers the implementing server should provide.
10
+ *
11
+ * @category Common
12
+ */
13
+ export interface RequestHeaders {
14
+ accept?: string | undefined;
15
+ allow?: string | undefined;
16
+ 'content-type'?: string | undefined;
17
+ /**
18
+ * Always an array in Node. Duplicates are added to it.
19
+ * Not necessarily true for other environments, make sure
20
+ * to check the type during runtime.
21
+ */
22
+ 'set-cookie'?: string | string[] | undefined;
23
+ [key: string]: string | string[] | undefined;
24
+ }
25
+ /**
26
+ * Server agnostic request interface containing the raw request
27
+ * which is server dependant.
28
+ *
29
+ * @category Common
30
+ */
31
+ export interface Request<RawRequest, Context> {
32
+ readonly method: string;
33
+ readonly url: string;
34
+ readonly headers: RequestHeaders;
35
+ readonly body: string | Record<string, unknown> | null;
36
+ /**
37
+ * The raw request itself from the implementing server.
38
+ *
39
+ * For example: `express.Request` when using Express, or maybe
40
+ * `http.IncomingMessage` when just using Node with `http.createServer`.
41
+ */
42
+ readonly raw: RawRequest;
43
+ /**
44
+ * Context value about the incoming request, you're free to pass any information here.
45
+ */
46
+ readonly context: Context;
47
+ }
48
+ /**
49
+ * The response headers that get returned from graphql-http.
50
+ *
51
+ * @category Common
52
+ */
53
+ export declare type ResponseHeaders = {
54
+ accept?: string;
55
+ allow?: string;
56
+ 'content-type'?: string;
57
+ } & Record<string, string>;
58
+ /**
59
+ * Server agnostic response body returned from `graphql-http` needing
60
+ * to be coerced to the server implementation in use.
61
+ *
62
+ * @category Common
63
+ */
64
+ export declare type ResponseBody = string;
65
+ /**
66
+ * Server agnostic response options (ex. status and headers) returned from
67
+ * `graphql-http` needing to be coerced to the server implementation in use.
68
+ *
69
+ * @category Common
70
+ */
71
+ export interface ResponseInit {
72
+ readonly status: number;
73
+ readonly statusText: string;
74
+ readonly headers?: ResponseHeaders;
75
+ }
76
+ /**
77
+ * Server agnostic response returned from `graphql-http` containing the
78
+ * body and init options needing to be coerced to the server implementation in use.
79
+ *
80
+ * @category Common
81
+ */
82
+ export declare type Response = readonly [body: ResponseBody | null, init: ResponseInit];
83
+ /**
84
+ * Checks whether the passed value is the `graphql-http` server agnostic response.
85
+ *
86
+ * @category Common
87
+ */
88
+ export declare function isResponse(val: unknown): val is Response;
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
  /**
@@ -51,14 +134,31 @@ export interface HandlerOptions<RawRequest = unknown> {
51
134
  * used to execute the query and mutation operations.
52
135
  */
53
136
  execute?: typeof graphqlExecute;
137
+ /**
138
+ * GraphQL parse function allowing you to apply a custom parser.
139
+ */
140
+ parse?: typeof graphqlParse;
141
+ /**
142
+ * GraphQL operation AST getter used for detecting the operation type.
143
+ */
144
+ getOperationAST?: typeof graphqlGetOperationAST;
54
145
  /**
55
146
  * The subscribe callback executed right after processing the request
56
147
  * before proceeding with the GraphQL operation execution.
57
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
+ *
58
153
  * If you return `ExecutionArgs` from the callback, it will be used instead of
59
154
  * trying to build one internally. In this case, you are responsible for providing
60
155
  * a ready set of arguments which will be directly plugged in the operation execution.
61
156
  *
157
+ * You *must* validate the `ExecutionArgs` yourself if returning them.
158
+ *
159
+ * If you return an array of `GraphQLError` from the callback, they will be reported
160
+ * to the client while complying with the spec.
161
+ *
62
162
  * Omitting the fields `contextValue` from the returned `ExecutionArgs` will use the
63
163
  * provided `context` option, if available.
64
164
  *
@@ -70,7 +170,7 @@ export interface HandlerOptions<RawRequest = unknown> {
70
170
  * you should do by returning a `Request` argument which will stop
71
171
  * further execution.
72
172
  */
73
- onSubscribe?: (req: Request<RawRequest>, params: RequestParams) => Promise<ExecutionArgs | Response | void> | ExecutionArgs | Response | void;
173
+ onSubscribe?: (req: Request<RawRequest, Context>, params: RequestParams) => Promise<ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void> | ExecutionResult | ExecutionArgs | readonly GraphQLError[] | Response | void;
74
174
  /**
75
175
  * Executed after the operation call resolves.
76
176
  *
@@ -84,7 +184,7 @@ export interface HandlerOptions<RawRequest = unknown> {
84
184
  * you should do by returning a `Request` argument which will stop
85
185
  * further execution.
86
186
  */
87
- 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;
88
188
  }
89
189
  /**
90
190
  * The ready-to-use handler. Simply plug it in your favourite HTTP framework
@@ -96,7 +196,7 @@ export interface HandlerOptions<RawRequest = unknown> {
96
196
  *
97
197
  * @category Server
98
198
  */
99
- 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>;
100
200
  /**
101
201
  * Makes a GraphQL over HTTP Protocol compliant server handler. The handler can
102
202
  * be used with your favourite server library.
@@ -152,4 +252,28 @@ export declare type Handler<RawRequest = unknown> = (req: Request<RawRequest>) =
152
252
  *
153
253
  * @category Server
154
254
  */
155
- 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-response+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;