Skip to content

Commit 12c2e8d

Browse files
authored
feat!: add signal support (#303)
1 parent b693d27 commit 12c2e8d

File tree

9 files changed

+575
-25
lines changed

9 files changed

+575
-25
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ node_modules
33
dist
44
.DS_Store
55
*.log
6+
coverage

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Minimal GraphQL client supporting Node and browsers for scripts or simple apps
3131
- [Browser](#browser)
3232
- [Node](#node)
3333
- [Batching](#batching)
34+
- [Cancellation](#cancellation)
3435
- [FAQ](#faq)
3536
- [Why do I have to install `graphql`?](#why-do-i-have-to-install-graphql)
3637
- [Do I need to wrap my GraphQL documents inside the `gql` template exported by `graphql-request`?](#do-i-need-to-wrap-my-graphql-documents-inside-the-gql-template-exported-by-graphql-request)
@@ -85,6 +86,17 @@ const client = new GraphQLClient(endpoint, { headers: {} })
8586
client.request(query, variables).then((data) => console.log(data))
8687
```
8788

89+
You can also use the single argument function variant:
90+
91+
```js
92+
request({
93+
url: endpoint,
94+
document: query,
95+
variables: variables,
96+
requestHeaders: headers,
97+
}).then((data) => console.log(data))
98+
```
99+
88100
## Node Version Support
89101

90102
We only officially support [LTS Node versions](https://github.com/nodejs/Release#release-schedule). We also make an effort to support two additional versions:
@@ -539,6 +551,40 @@ import { batchRequests } from 'graphql-request';
539551
})().catch((error) => console.error(error))
540552
```
541553

554+
### Cancellation
555+
556+
It is possible to cancel a request using an `AbortController` signal.
557+
558+
You can define the `signal` in the `GraphQLClient` constructor:
559+
560+
```ts
561+
const abortController = new AbortController()
562+
563+
const client = new GraphQLClient(endpoint, { signal: abortController.signal })
564+
client.request(query)
565+
566+
abortController.abort()
567+
```
568+
569+
You can also set the signal per request (this will override an existing GraphQLClient signal):
570+
571+
```ts
572+
const abortController = new AbortController()
573+
574+
const client = new GraphQLClient(endpoint)
575+
client.request({ document: query, signal: abortController.signal })
576+
577+
abortController.abort()
578+
```
579+
580+
In Node environment, `AbortController` is supported since version v14.17.0.
581+
For Node.js v12 you can use [abort-controller](https://github.com/mysticatea/abort-controller) polyfill.
582+
583+
````
584+
import 'abort-controller/polyfill'
585+
586+
const abortController = new AbortController()
587+
````
542588

543589
## FAQ
544590

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"test:node": "jest --testEnvironment node",
3939
"test:dom": "jest --testEnvironment jsdom",
4040
"test": "yarn test:node && yarn test:dom",
41+
"test:coverage": "yarn test --coverage",
4142
"release:stable": "dripip stable",
4243
"release:preview": "dripip preview",
4344
"release:pr": "dripip pr"
@@ -51,6 +52,7 @@
5152
"graphql": "14 - 16"
5253
},
5354
"devDependencies": {
55+
"abort-controller": "^3.0.0",
5456
"@prisma-labs/prettier-config": "^0.1.0",
5557
"@types/body-parser": "^1.19.1",
5658
"@types/express": "^4.17.13",

src/index.ts

Lines changed: 123 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,40 @@ import crossFetch, * as CrossFetch from 'cross-fetch'
22
import { OperationDefinitionNode } from 'graphql/language/ast'
33
import { print } from 'graphql/language/printer'
44
import createRequestBody from './createRequestBody'
5-
import { BatchRequestDocument, ClientError, RequestDocument, Variables } from './types'
5+
import {
6+
parseBatchRequestArgs,
7+
parseRawRequestArgs,
8+
parseRequestArgs,
9+
parseBatchRequestsExtendedArgs,
10+
parseRawRequestExtendedArgs,
11+
parseRequestExtendedArgs,
12+
} from './parseArgs'
13+
import {
14+
BatchRequestDocument,
15+
BatchRequestsOptions,
16+
ClientError,
17+
RawRequestOptions,
18+
RequestDocument,
19+
RequestOptions,
20+
BatchRequestsExtendedOptions,
21+
RawRequestExtendedOptions,
22+
RequestExtendedOptions,
23+
Variables,
24+
} from './types'
625
import * as Dom from './types.dom'
726

8-
export { BatchRequestDocument, ClientError, RequestDocument, Variables }
27+
export {
28+
BatchRequestDocument,
29+
BatchRequestsOptions,
30+
BatchRequestsExtendedOptions,
31+
ClientError,
32+
RawRequestOptions,
33+
RawRequestExtendedOptions,
34+
RequestDocument,
35+
RequestOptions,
36+
RequestExtendedOptions,
37+
Variables,
38+
}
939

1040
/**
1141
* Convert the given headers configuration into a plain object.
@@ -152,7 +182,7 @@ const get = async <V = Variables>({
152182
}
153183

154184
/**
155-
* todo
185+
* GraphQL Client.
156186
*/
157187
export class GraphQLClient {
158188
private url: string
@@ -163,21 +193,37 @@ export class GraphQLClient {
163193
this.options = options || {}
164194
}
165195

166-
rawRequest<T = any, V = Variables>(
196+
/**
197+
* Send a GraphQL query to the server.
198+
*/
199+
async rawRequest<T = any, V = Variables>(
167200
query: string,
168201
variables?: V,
169202
requestHeaders?: Dom.RequestInit['headers']
203+
): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }>
204+
async rawRequest<T = any, V = Variables>(
205+
options: RawRequestOptions<V>
206+
): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }>
207+
async rawRequest<T = any, V = Variables>(
208+
queryOrOptions: string | RawRequestOptions<V>,
209+
variables?: V,
210+
requestHeaders?: Dom.RequestInit['headers']
170211
): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> {
212+
const rawRequestOptions = parseRawRequestArgs<V>(queryOrOptions, variables, requestHeaders)
213+
171214
let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options
172215
let { url } = this
216+
if (rawRequestOptions.signal !== undefined) {
217+
fetchOptions.signal = rawRequestOptions.signal
218+
}
173219

174220
return makeRequest<T, V>({
175221
url,
176-
query,
177-
variables,
222+
query: rawRequestOptions.query,
223+
variables: rawRequestOptions.variables,
178224
headers: {
179225
...resolveHeaders(headers),
180-
...resolveHeaders(requestHeaders),
226+
...resolveHeaders(rawRequestOptions.requestHeaders),
181227
},
182228
operationName: undefined,
183229
fetch,
@@ -193,19 +239,30 @@ export class GraphQLClient {
193239
document: RequestDocument,
194240
variables?: V,
195241
requestHeaders?: Dom.RequestInit['headers']
242+
): Promise<T>
243+
async request<T = any, V = Variables>(options: RequestOptions<V>): Promise<T>
244+
async request<T = any, V = Variables>(
245+
documentOrOptions: RequestDocument | RequestOptions<V>,
246+
variables?: V,
247+
requestHeaders?: Dom.RequestInit['headers']
196248
): Promise<T> {
249+
const requestOptions = parseRequestArgs<V>(documentOrOptions, variables, requestHeaders)
250+
197251
let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options
198252
let { url } = this
253+
if (requestOptions.signal !== undefined) {
254+
fetchOptions.signal = requestOptions.signal
255+
}
199256

200-
const { query, operationName } = resolveRequestDocument(document)
257+
const { query, operationName } = resolveRequestDocument(requestOptions.document)
201258

202259
const { data } = await makeRequest<T, V>({
203260
url,
204261
query,
205-
variables,
262+
variables: requestOptions.variables,
206263
headers: {
207264
...resolveHeaders(headers),
208-
...resolveHeaders(requestHeaders),
265+
...resolveHeaders(requestOptions.requestHeaders),
209266
},
210267
operationName,
211268
fetch,
@@ -217,25 +274,37 @@ export class GraphQLClient {
217274
}
218275

219276
/**
220-
* Send a GraphQL document to the server.
277+
* Send GraphQL documents in batch to the server.
221278
*/
222279
async batchRequests<T extends any = any, V = Variables>(
223280
documents: BatchRequestDocument<V>[],
224281
requestHeaders?: Dom.RequestInit['headers']
282+
): Promise<T>
283+
async batchRequests<T = any, V = Variables>(options: BatchRequestsOptions<V>): Promise<T>
284+
async batchRequests<T = any, V = Variables>(
285+
documentsOrOptions: BatchRequestDocument<V>[] | BatchRequestsOptions<V>,
286+
requestHeaders?: Dom.RequestInit['headers']
225287
): Promise<T> {
288+
const batchRequestOptions = parseBatchRequestArgs<V>(documentsOrOptions, requestHeaders)
289+
226290
let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options
227291
let { url } = this
292+
if (batchRequestOptions.signal !== undefined) {
293+
fetchOptions.signal = batchRequestOptions.signal
294+
}
228295

229-
const queries = documents.map(({ document }) => resolveRequestDocument(document).query)
230-
const variables = documents.map(({ variables }) => variables)
296+
const queries = batchRequestOptions.documents.map(
297+
({ document }) => resolveRequestDocument(document).query
298+
)
299+
const variables = batchRequestOptions.documents.map(({ variables }) => variables)
231300

232301
const { data } = await makeRequest<T, (V | undefined)[]>({
233302
url,
234303
query: queries,
235304
variables,
236305
headers: {
237306
...resolveHeaders(headers),
238-
...resolveHeaders(requestHeaders),
307+
...resolveHeaders(batchRequestOptions.requestHeaders),
239308
},
240309
operationName: undefined,
241310
fetch,
@@ -330,20 +399,32 @@ async function makeRequest<T = any, V = Variables>({
330399
}
331400

332401
/**
333-
* todo
402+
* Send a GraphQL Query to the GraphQL server for execution.
334403
*/
335404
export async function rawRequest<T = any, V = Variables>(
336405
url: string,
337406
query: string,
338407
variables?: V,
339408
requestHeaders?: Dom.RequestInit['headers']
409+
): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }>
410+
export async function rawRequest<T = any, V = Variables>(
411+
options: RawRequestExtendedOptions<V>
412+
): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }>
413+
export async function rawRequest<T = any, V = Variables>(
414+
urlOrOptions: string | RawRequestExtendedOptions<V>,
415+
query?: string,
416+
variables?: V,
417+
requestHeaders?: Dom.RequestInit['headers']
340418
): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> {
341-
const client = new GraphQLClient(url)
342-
return client.rawRequest<T, V>(query, variables, requestHeaders)
419+
const requestOptions = parseRawRequestExtendedArgs<V>(urlOrOptions, query, variables, requestHeaders)
420+
const client = new GraphQLClient(requestOptions.url)
421+
return client.rawRequest<T, V>({
422+
...requestOptions,
423+
})
343424
}
344425

345426
/**
346-
* Send a GraphQL Document to the GraphQL server for exectuion.
427+
* Send a GraphQL Document to the GraphQL server for execution.
347428
*
348429
* @example
349430
*
@@ -381,9 +462,19 @@ export async function request<T = any, V = Variables>(
381462
document: RequestDocument,
382463
variables?: V,
383464
requestHeaders?: Dom.RequestInit['headers']
465+
): Promise<T>
466+
export async function request<T = any, V = Variables>(options: RequestExtendedOptions<V>): Promise<T>
467+
export async function request<T = any, V = Variables>(
468+
urlOrOptions: string | RequestExtendedOptions<V>,
469+
document?: RequestDocument,
470+
variables?: V,
471+
requestHeaders?: Dom.RequestInit['headers']
384472
): Promise<T> {
385-
const client = new GraphQLClient(url)
386-
return client.request<T, V>(document, variables, requestHeaders)
473+
const requestOptions = parseRequestExtendedArgs<V>(urlOrOptions, document, variables, requestHeaders)
474+
const client = new GraphQLClient(requestOptions.url)
475+
return client.request<T, V>({
476+
...requestOptions,
477+
})
387478
}
388479

389480
/**
@@ -420,13 +511,22 @@ export async function request<T = any, V = Variables>(
420511
* await batchRequests('https://foo.bar/graphql', [{ query: gql`...` }])
421512
* ```
422513
*/
423-
export async function batchRequests<T extends any = any, V = Variables>(
514+
export async function batchRequests<T = any, V = Variables>(
424515
url: string,
425516
documents: BatchRequestDocument<V>[],
426517
requestHeaders?: Dom.RequestInit['headers']
518+
): Promise<T>
519+
export async function batchRequests<T = any, V = Variables>(
520+
options: BatchRequestsExtendedOptions<V>
521+
): Promise<T>
522+
export async function batchRequests<T = any, V = Variables>(
523+
urlOrOptions: string | BatchRequestsExtendedOptions<V>,
524+
documents?: BatchRequestDocument<V>[],
525+
requestHeaders?: Dom.RequestInit['headers']
427526
): Promise<T> {
428-
const client = new GraphQLClient(url)
429-
return client.batchRequests<T, V>(documents, requestHeaders)
527+
const requestOptions = parseBatchRequestsExtendedArgs<V>(urlOrOptions, documents, requestHeaders)
528+
const client = new GraphQLClient(requestOptions.url)
529+
return client.batchRequests<T, V>({ ...requestOptions })
430530
}
431531

432532
export default request

0 commit comments

Comments
 (0)