From dec3cd4c3a98a7a11ac2b6acecd7ed95a321fad3 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Mon, 15 Nov 2021 13:11:17 +0100 Subject: [PATCH 01/11] feat: add signal support by request --- .gitignore | 1 + package.json | 1 + src/index.ts | 33 ++++++++--- tests/signal.test.ts | 133 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 tests/signal.test.ts diff --git a/.gitignore b/.gitignore index 8a76002c5..5dcb9b25c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules dist .DS_Store *.log +coverage \ No newline at end of file diff --git a/package.json b/package.json index 4d08db925..c84c263df 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "test:node": "jest --testEnvironment node", "test:dom": "jest --testEnvironment jsdom", "test": "yarn test:node && yarn test:dom", + "test:coverage": "yarn test --coverage", "release:stable": "dripip stable", "release:preview": "dripip preview", "release:pr": "dripip pr" diff --git a/src/index.ts b/src/index.ts index ce7e35224..a5da1a243 100644 --- a/src/index.ts +++ b/src/index.ts @@ -166,10 +166,14 @@ export class GraphQLClient { rawRequest( query: string, variables?: V, - requestHeaders?: Dom.RequestInit['headers'] + requestHeaders?: Dom.RequestInit['headers'], + signal?: Dom.RequestInit['signal'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this + if (signal !== undefined) { + fetchOptions.signal = signal + } return makeRequest({ url, @@ -192,10 +196,14 @@ export class GraphQLClient { async request( document: RequestDocument, variables?: V, - requestHeaders?: Dom.RequestInit['headers'] + requestHeaders?: Dom.RequestInit['headers'], + signal?: Dom.RequestInit['signal'] ): Promise { let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this + if (signal !== undefined) { + fetchOptions.signal = signal + } const { query, operationName } = resolveRequestDocument(document) @@ -221,10 +229,14 @@ export class GraphQLClient { */ async batchRequests( documents: BatchRequestDocument[], - requestHeaders?: Dom.RequestInit['headers'] + requestHeaders?: Dom.RequestInit['headers'], + signal?: Dom.RequestInit['signal'] ): Promise { let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this + if (signal !== undefined) { + fetchOptions.signal = signal + } const queries = documents.map(({ document }) => resolveRequestDocument(document).query) const variables = documents.map(({ variables }) => variables) @@ -336,9 +348,10 @@ export async function rawRequest( url: string, query: string, variables?: V, - requestHeaders?: Dom.RequestInit['headers'] + requestHeaders?: Dom.RequestInit['headers'], + signal?: Dom.RequestInit['signal'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { - const client = new GraphQLClient(url) + const client = new GraphQLClient(url, { signal }) return client.rawRequest(query, variables, requestHeaders) } @@ -380,9 +393,10 @@ export async function request( url: string, document: RequestDocument, variables?: V, - requestHeaders?: Dom.RequestInit['headers'] + requestHeaders?: Dom.RequestInit['headers'], + signal?: Dom.RequestInit['signal'] ): Promise { - const client = new GraphQLClient(url) + const client = new GraphQLClient(url, { signal }) return client.request(document, variables, requestHeaders) } @@ -423,9 +437,10 @@ export async function request( export async function batchRequests( url: string, documents: BatchRequestDocument[], - requestHeaders?: Dom.RequestInit['headers'] + requestHeaders?: Dom.RequestInit['headers'], + signal?: Dom.RequestInit['signal'] ): Promise { - const client = new GraphQLClient(url) + const client = new GraphQLClient(url, { signal }) return client.batchRequests(documents, requestHeaders) } diff --git a/tests/signal.test.ts b/tests/signal.test.ts new file mode 100644 index 000000000..3fa0d68c3 --- /dev/null +++ b/tests/signal.test.ts @@ -0,0 +1,133 @@ +import { batchRequests, GraphQLClient, rawRequest, request } from '../src' +import { setupTestServer } from './__helpers' + +const ctx = setupTestServer() + +it('should abort a request when the signal is defined using GraphQLClient constructor', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + const client = new GraphQLClient(ctx.url, { signal: abortController.signal }) + + try { + await client.request(ctx.url, `{ me { id } }`) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort a raw request when the signal is defined using GraphQLClient constructor', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + const client = new GraphQLClient(ctx.url, { signal: abortController.signal }) + + try { + await client.rawRequest(ctx.url, `{ me { id } }`) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort batch requests when the signal is defined using GraphQLClient constructor', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + const client = new GraphQLClient(ctx.url, { signal: abortController.signal }) + + try { + await client.batchRequests([{ document: `{ me { id } }` }, { document: `{ me { id } }` }]) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort a request when the signal overrides GraphQLClient settings', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + const client = new GraphQLClient(ctx.url) + + try { + await client.request(ctx.url, `{ me { id } }`, undefined, abortController.signal) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort a raw request when the signal overrides GraphQLClient settings', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + const client = new GraphQLClient(ctx.url) + + try { + await client.rawRequest(ctx.url, `{ me { id } }`, undefined, abortController.signal) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort batch requests when the signal overrides GraphQLClient settings', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + const client = new GraphQLClient(ctx.url) + + try { + await client.batchRequests( + [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], + undefined, + abortController.signal + ) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort a request', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + try { + await request(ctx.url, `{ me { id } }`, undefined, undefined, abortController.signal) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort a raw request', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + try { + await rawRequest(ctx.url, `{ me { id } }`, undefined, undefined, abortController.signal) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort batch requests', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + try { + await batchRequests( + ctx.url, + [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], + undefined, + abortController.signal + ) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) From 508dedcafcda0352aa38c00174d51b5eb3c6e59a Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Mon, 15 Nov 2021 14:02:51 +0100 Subject: [PATCH 02/11] docs(readme): add cancellation section --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 278e3aefa..d37be7b2f 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Minimal GraphQL client supporting Node and browsers for scripts or simple apps - [Browser](#browser) - [Node](#node) - [Batching](#batching) + - [Cancellation](#cancellation) - [FAQ](#faq) - [Why do I have to install `graphql`?](#why-do-i-have-to-install-graphql) - [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) @@ -539,6 +540,30 @@ import { batchRequests } from 'graphql-request'; })().catch((error) => console.error(error)) ``` +### Cancellation + +It is possible to cancel a request using an `AbortController` signal. + +You can define the `signal` in the `GraphQLClient` constructor: + +```ts + const abortController = new AbortController() + + const client = new GraphQLClient(endpoint, { signal: abortController.signal }) + client.request(query) + + abortController.abort() +``` + +You can also set the signal per request (this will override an existing GraphQLClient signal): + +```ts + const abortController = new AbortController() + + request(endpoint, query, undefined, undefined, abortController.signal) + + abortController.abort() +``` ## FAQ From 53cd549336e54c17a4947dca3b59703dc7a23a3d Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Fri, 19 Nov 2021 23:25:06 +0100 Subject: [PATCH 03/11] test(signal): add tests after the request has been sent --- tests/__helpers.ts | 14 ++++++-- tests/signal.test.ts | 78 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/tests/__helpers.ts b/tests/__helpers.ts index a0875f54b..11736d66b 100644 --- a/tests/__helpers.ts +++ b/tests/__helpers.ts @@ -43,7 +43,7 @@ type MockResult = { }[] } -export function setupTestServer(): Context { +export function setupTestServer(delay?: number): Context { const ctx = {} as Context beforeAll(async () => { const port = await getPort() @@ -58,7 +58,11 @@ export function setupTestServer() ctx.url = `http://localhost:${port}` ctx.res = (spec?: T): MockResult => { const requests: CapturedRequest[] = [] - ctx.server.use('*', function mock(req, res) { + ctx.server.use('*', async function mock(req, res) { + if (delay) { + await sleep(delay) + } + req.headers.host = 'DYNAMIC' requests.push({ method: req.method, @@ -130,3 +134,9 @@ export function createApolloServerContext({ typeDefs, resolvers }: ApolloServerC return ctx } + +export function sleep(timeout: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, timeout) + }) +} diff --git a/tests/signal.test.ts b/tests/signal.test.ts index 3fa0d68c3..45e846f85 100644 --- a/tests/signal.test.ts +++ b/tests/signal.test.ts @@ -1,7 +1,7 @@ import { batchRequests, GraphQLClient, rawRequest, request } from '../src' -import { setupTestServer } from './__helpers' +import { setupTestServer, sleep } from './__helpers' -const ctx = setupTestServer() +const ctx = setupTestServer(20) it('should abort a request when the signal is defined using GraphQLClient constructor', async () => { const abortController = new AbortController() @@ -17,6 +17,30 @@ it('should abort a request when the signal is defined using GraphQLClient constr } }) +it('should abort a request when the signal is defined using GraphQLClient constructor and after the request has been sent', async () => { + const abortController = new AbortController() + ctx.res({ + body: { + data: { + me: { + id: 'some-id', + }, + }, + }, + }).spec.body! + + expect.assertions(1) + + const client = new GraphQLClient(ctx.url, { signal: abortController.signal }) + client.request(ctx.url, `{ me { id } }`).catch((error) => { + expect((error as Error).message).toEqual('The user aborted a request.') + }) + + await sleep(10) + abortController.abort() + await sleep(20) +}) + it('should abort a raw request when the signal is defined using GraphQLClient constructor', async () => { const abortController = new AbortController() abortController.abort() @@ -103,6 +127,29 @@ it('should abort a request', async () => { } }) +it('should abort a request after the request has been sent', async () => { + const abortController = new AbortController() + ctx.res({ + body: { + data: { + me: { + id: 'some-id', + }, + }, + }, + }).spec.body! + + expect.assertions(1) + + request(ctx.url, `{ me { id } }`, undefined, undefined, abortController.signal).catch((error) => { + expect((error as Error).message).toEqual('The user aborted a request.') + }) + + await sleep(10) + abortController.abort() + await sleep(20) +}) + it('should abort a raw request', async () => { const abortController = new AbortController() abortController.abort() @@ -131,3 +178,30 @@ it('should abort batch requests', async () => { expect((error as Error).message).toEqual('The user aborted a request.') } }) + +it('should abort batch requests after a request has been sent', async () => { + const abortController = new AbortController() + ctx.res({ + body: { + data: { + me: { + id: 'some-id', + }, + }, + }, + }).spec.body! + expect.assertions(1) + + batchRequests( + ctx.url, + [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], + undefined, + abortController.signal + ).catch((error) => { + expect((error as Error).message).toEqual('The user aborted a request.') + }) + + await sleep(10) + abortController.abort() + await sleep(20) +}) From 499c03c3962a67b4aa1d6e2f7fbb069fbf916190 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Fri, 3 Dec 2021 12:47:13 +0100 Subject: [PATCH 04/11] refactor: add overloads with request options single arg object --- README.md | 3 +- src/index.ts | 153 +++++++++++++++++++++++++++++++++---------- src/types.ts | 21 ++++++ tests/signal.test.ts | 119 +++++---------------------------- 4 files changed, 156 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index d37be7b2f..fbc6630af 100644 --- a/README.md +++ b/README.md @@ -560,7 +560,8 @@ You can also set the signal per request (this will override an existing GraphQLC ```ts const abortController = new AbortController() - request(endpoint, query, undefined, undefined, abortController.signal) + const client = new GraphQLClient(endpoint) + client.request({ document: query, signal: abortController.signal }) abortController.abort() ``` diff --git a/src/index.ts b/src/index.ts index 5d2c436a5..b45ee9310 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,10 +2,69 @@ import crossFetch, * as CrossFetch from 'cross-fetch' import { OperationDefinitionNode } from 'graphql/language/ast' import { print } from 'graphql/language/printer' import createRequestBody from './createRequestBody' -import { BatchRequestDocument, ClientError, RequestDocument, Variables } from './types' +import { + BatchRequestDocument, + BatchRequestOptions, + ClientError, + RawRequestOptions, + RequestDocument, + RequestOptions, + Variables, +} from './types' import * as Dom from './types.dom' -export { BatchRequestDocument, ClientError, RequestDocument, Variables } +export { + BatchRequestDocument, + BatchRequestOptions, + ClientError, + RequestDocument, + Variables, + RawRequestOptions, + RequestOptions, +} + +function parseRequestArgs( + arg1: RequestDocument | RequestOptions, + arg2?: V, + arg3?: Dom.RequestInit['headers'] +) { + return (arg1 as RequestOptions).document + ? (arg1 as RequestOptions) + : { + document: arg1 as RequestDocument, + variables: arg2, + requestHeaders: arg3, + signal: undefined, + } +} + +function parseRawRequestArgs( + arg1: RequestDocument | RawRequestOptions, + arg2?: V, + arg3?: Dom.RequestInit['headers'] +) { + return (arg1 as RawRequestOptions).query + ? (arg1 as RawRequestOptions) + : { + query: arg1 as string, + variables: arg2, + requestHeaders: arg3, + signal: undefined, + } +} + +function parseBatchRequestArgs( + arg1: BatchRequestDocument[] | BatchRequestOptions, + arg2?: Dom.RequestInit['headers'] +) { + return (arg1 as BatchRequestOptions).documents + ? (arg1 as BatchRequestOptions) + : { + documents: arg1 as BatchRequestDocument[], + requestHeaders: arg2, + signal: undefined, + } +} /** * Convert the given headers configuration into a plain object. @@ -152,7 +211,7 @@ const get = async ({ } /** - * todo + * GraphQL Client. */ export class GraphQLClient { private url: string @@ -163,25 +222,37 @@ export class GraphQLClient { this.options = options || {} } - rawRequest( + /** + * Send a GraphQL query to the server. + */ + async rawRequest( query: string, variables?: V, - requestHeaders?: Dom.RequestInit['headers'], - signal?: Dom.RequestInit['signal'] + requestHeaders?: Dom.RequestInit['headers'] + ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> + async rawRequest( + options: RawRequestOptions + ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> + async rawRequest( + arg1: RequestDocument | RawRequestOptions, + arg2?: V, + arg3?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { + const rawRequestOptions = parseRawRequestArgs(arg1, arg2, arg3) + let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this - if (signal !== undefined) { - fetchOptions.signal = signal + if (rawRequestOptions.signal !== undefined) { + fetchOptions.signal = rawRequestOptions.signal } return makeRequest({ url, - query, - variables, + query: rawRequestOptions.query, + variables: rawRequestOptions.variables, headers: { ...resolveHeaders(headers), - ...resolveHeaders(requestHeaders), + ...resolveHeaders(rawRequestOptions.requestHeaders), }, operationName: undefined, fetch, @@ -196,24 +267,31 @@ export class GraphQLClient { async request( document: RequestDocument, variables?: V, - requestHeaders?: Dom.RequestInit['headers'], - signal?: Dom.RequestInit['signal'] + requestHeaders?: Dom.RequestInit['headers'] + ): Promise + async request(options: RequestOptions): Promise + async request( + arg1: RequestDocument | RequestOptions, + arg2?: V, + arg3?: Dom.RequestInit['headers'] ): Promise { + const requestOptions = parseRequestArgs(arg1, arg2, arg3) + let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this - if (signal !== undefined) { - fetchOptions.signal = signal + if (requestOptions.signal !== undefined) { + fetchOptions.signal = requestOptions.signal } - const { query, operationName } = resolveRequestDocument(document) + const { query, operationName } = resolveRequestDocument(requestOptions.document) const { data } = await makeRequest({ url, query, - variables, + variables: requestOptions.variables, headers: { ...resolveHeaders(headers), - ...resolveHeaders(requestHeaders), + ...resolveHeaders(requestOptions.requestHeaders), }, operationName, fetch, @@ -225,21 +303,29 @@ export class GraphQLClient { } /** - * Send a GraphQL document to the server. + * Send GraphQL documents in batch to the server. */ async batchRequests( documents: BatchRequestDocument[], - requestHeaders?: Dom.RequestInit['headers'], - signal?: Dom.RequestInit['signal'] + requestHeaders?: Dom.RequestInit['headers'] + ): Promise + async batchRequests(options: BatchRequestOptions): Promise + async batchRequests( + arg1: BatchRequestDocument[] | BatchRequestOptions, + arg2?: Dom.RequestInit['headers'] ): Promise { + const batchRequestOptions = parseBatchRequestArgs(arg1, arg2) + let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this - if (signal !== undefined) { - fetchOptions.signal = signal + if (batchRequestOptions.signal !== undefined) { + fetchOptions.signal = batchRequestOptions.signal } - const queries = documents.map(({ document }) => resolveRequestDocument(document).query) - const variables = documents.map(({ variables }) => variables) + const queries = batchRequestOptions.documents.map( + ({ document }) => resolveRequestDocument(document).query + ) + const variables = batchRequestOptions.documents.map(({ variables }) => variables) const { data } = await makeRequest({ url, @@ -247,7 +333,7 @@ export class GraphQLClient { variables, headers: { ...resolveHeaders(headers), - ...resolveHeaders(requestHeaders), + ...resolveHeaders(batchRequestOptions.requestHeaders), }, operationName: undefined, fetch, @@ -348,10 +434,9 @@ export async function rawRequest( url: string, query: string, variables?: V, - requestHeaders?: Dom.RequestInit['headers'], - signal?: Dom.RequestInit['signal'] + requestHeaders?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { - const client = new GraphQLClient(url, { signal }) + const client = new GraphQLClient(url) return client.rawRequest(query, variables, requestHeaders) } @@ -393,10 +478,9 @@ export async function request( url: string, document: RequestDocument, variables?: V, - requestHeaders?: Dom.RequestInit['headers'], - signal?: Dom.RequestInit['signal'] + requestHeaders?: Dom.RequestInit['headers'] ): Promise { - const client = new GraphQLClient(url, { signal }) + const client = new GraphQLClient(url) return client.request(document, variables, requestHeaders) } @@ -437,10 +521,9 @@ export async function request( export async function batchRequests( url: string, documents: BatchRequestDocument[], - requestHeaders?: Dom.RequestInit['headers'], - signal?: Dom.RequestInit['signal'] + requestHeaders?: Dom.RequestInit['headers'] ): Promise { - const client = new GraphQLClient(url, { signal }) + const client = new GraphQLClient(url) return client.batchRequests(documents, requestHeaders) } diff --git a/src/types.ts b/src/types.ts index bd6a5c448..1360072a7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import { DocumentNode } from 'graphql/language/ast' +import * as Dom from './types.dom' export type Variables = { [key: string]: any } @@ -60,3 +61,23 @@ export type BatchRequestDocument = { document: RequestDocument variables?: V } + +export type RawRequestOptions = { + query: string + variables?: V + requestHeaders?: Dom.RequestInit['headers'] + signal?: Dom.RequestInit['signal'] +} + +export type RequestOptions = { + document: RequestDocument + variables?: V + requestHeaders?: Dom.RequestInit['headers'] + signal?: Dom.RequestInit['signal'] +} + +export type BatchRequestOptions = { + documents: BatchRequestDocument[] + requestHeaders?: Dom.RequestInit['headers'] + signal?: Dom.RequestInit['signal'] +} diff --git a/tests/signal.test.ts b/tests/signal.test.ts index 45e846f85..09afdcc27 100644 --- a/tests/signal.test.ts +++ b/tests/signal.test.ts @@ -3,7 +3,7 @@ import { setupTestServer, sleep } from './__helpers' const ctx = setupTestServer(20) -it('should abort a request when the signal is defined using GraphQLClient constructor', async () => { +it('should abort a request when the signal is defined in the GraphQLClient', async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -11,13 +11,13 @@ it('should abort a request when the signal is defined using GraphQLClient constr const client = new GraphQLClient(ctx.url, { signal: abortController.signal }) try { - await client.request(ctx.url, `{ me { id } }`) + await client.request('{ me { id } }') } catch (error) { expect((error as Error).message).toEqual('The user aborted a request.') } }) -it('should abort a request when the signal is defined using GraphQLClient constructor and after the request has been sent', async () => { +it('should abort a request when the signal is defined in GraphQLClient and after the request has been sent', async () => { const abortController = new AbortController() ctx.res({ body: { @@ -32,7 +32,7 @@ it('should abort a request when the signal is defined using GraphQLClient constr expect.assertions(1) const client = new GraphQLClient(ctx.url, { signal: abortController.signal }) - client.request(ctx.url, `{ me { id } }`).catch((error) => { + client.request('{ me { id } }').catch((error) => { expect((error as Error).message).toEqual('The user aborted a request.') }) @@ -41,7 +41,7 @@ it('should abort a request when the signal is defined using GraphQLClient constr await sleep(20) }) -it('should abort a raw request when the signal is defined using GraphQLClient constructor', async () => { +it('should abort a raw request when the signal is defined in the GraphQLClient', async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -55,7 +55,7 @@ it('should abort a raw request when the signal is defined using GraphQLClient co } }) -it('should abort batch requests when the signal is defined using GraphQLClient constructor', async () => { +it('should abort batch requests when the signal is defined in the GraphQLClient', async () => { const abortController = new AbortController() abortController.abort() expect.assertions(1) @@ -77,7 +77,10 @@ it('should abort a request when the signal overrides GraphQLClient settings', as const client = new GraphQLClient(ctx.url) try { - await client.request(ctx.url, `{ me { id } }`, undefined, abortController.signal) + await client.request({ + document: '{ me { id } }', + signal: abortController.signal, + }) } catch (error) { expect((error as Error).message).toEqual('The user aborted a request.') } @@ -91,7 +94,7 @@ it('should abort a raw request when the signal overrides GraphQLClient settings' const client = new GraphQLClient(ctx.url) try { - await client.rawRequest(ctx.url, `{ me { id } }`, undefined, abortController.signal) + await client.rawRequest({ query: '{ me { id } }', signal: abortController.signal }) } catch (error) { expect((error as Error).message).toEqual('The user aborted a request.') } @@ -105,103 +108,11 @@ it('should abort batch requests when the signal overrides GraphQLClient settings const client = new GraphQLClient(ctx.url) try { - await client.batchRequests( - [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], - undefined, - abortController.signal - ) + await client.batchRequests({ + documents: [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], + signal: abortController.signal, + }) } catch (error) { expect((error as Error).message).toEqual('The user aborted a request.') } }) - -it('should abort a request', async () => { - const abortController = new AbortController() - abortController.abort() - expect.assertions(1) - - try { - await request(ctx.url, `{ me { id } }`, undefined, undefined, abortController.signal) - } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') - } -}) - -it('should abort a request after the request has been sent', async () => { - const abortController = new AbortController() - ctx.res({ - body: { - data: { - me: { - id: 'some-id', - }, - }, - }, - }).spec.body! - - expect.assertions(1) - - request(ctx.url, `{ me { id } }`, undefined, undefined, abortController.signal).catch((error) => { - expect((error as Error).message).toEqual('The user aborted a request.') - }) - - await sleep(10) - abortController.abort() - await sleep(20) -}) - -it('should abort a raw request', async () => { - const abortController = new AbortController() - abortController.abort() - expect.assertions(1) - - try { - await rawRequest(ctx.url, `{ me { id } }`, undefined, undefined, abortController.signal) - } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') - } -}) - -it('should abort batch requests', async () => { - const abortController = new AbortController() - abortController.abort() - expect.assertions(1) - - try { - await batchRequests( - ctx.url, - [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], - undefined, - abortController.signal - ) - } catch (error) { - expect((error as Error).message).toEqual('The user aborted a request.') - } -}) - -it('should abort batch requests after a request has been sent', async () => { - const abortController = new AbortController() - ctx.res({ - body: { - data: { - me: { - id: 'some-id', - }, - }, - }, - }).spec.body! - expect.assertions(1) - - batchRequests( - ctx.url, - [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], - undefined, - abortController.signal - ).catch((error) => { - expect((error as Error).message).toEqual('The user aborted a request.') - }) - - await sleep(10) - abortController.abort() - await sleep(20) -}) From a1236b69f945ae2d851a2413c8471de1c893f9d2 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Sat, 4 Dec 2021 13:04:41 +0100 Subject: [PATCH 05/11] feat: add single arg overloads for request, rawRequest, batchRequests --- README.md | 11 ++++ src/index.ts | 116 +++++++++++++++++++------------------- src/parseArgs.ts | 104 ++++++++++++++++++++++++++++++++++ src/types.ts | 8 ++- tests/signal.test.ts | 129 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 309 insertions(+), 59 deletions(-) create mode 100644 src/parseArgs.ts diff --git a/README.md b/README.md index fbc6630af..4f7c814a5 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,17 @@ const client = new GraphQLClient(endpoint, { headers: {} }) client.request(query, variables).then((data) => console.log(data)) ``` +You can also use the single argument function variant: + +```js +request({ + url: endpoint, + document: query, + variables: variables, + requestHeaders: headers, +}).then((data) => console.log(data)) +``` + ## Node Version Support We only officially support [LTS Node versions](https://github.com/nodejs/Release#release-schedule). We also make an effort to support two additional versions: diff --git a/src/index.ts b/src/index.ts index b45ee9310..a7c44b0e5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,68 +2,39 @@ import crossFetch, * as CrossFetch from 'cross-fetch' import { OperationDefinitionNode } from 'graphql/language/ast' import { print } from 'graphql/language/printer' import createRequestBody from './createRequestBody' +import { + parseBatchRequestArgs, + parseRawRequestArgs, + parseRequestArgs, + parseBatchRequestsExtendedArgs, + parseRawRequestExtendedArgs, + parseRequestExtendedArgs, +} from './parseArgs' import { BatchRequestDocument, - BatchRequestOptions, + BatchRequestsOptions, ClientError, RawRequestOptions, RequestDocument, RequestOptions, + BatchRequestsExtendedOptions, + RawRequestExtendedOptions, + RequestExtendedOptions, Variables, } from './types' import * as Dom from './types.dom' export { BatchRequestDocument, - BatchRequestOptions, + BatchRequestsOptions, + BatchRequestsExtendedOptions, ClientError, - RequestDocument, - Variables, RawRequestOptions, + RawRequestExtendedOptions, + RequestDocument, RequestOptions, -} - -function parseRequestArgs( - arg1: RequestDocument | RequestOptions, - arg2?: V, - arg3?: Dom.RequestInit['headers'] -) { - return (arg1 as RequestOptions).document - ? (arg1 as RequestOptions) - : { - document: arg1 as RequestDocument, - variables: arg2, - requestHeaders: arg3, - signal: undefined, - } -} - -function parseRawRequestArgs( - arg1: RequestDocument | RawRequestOptions, - arg2?: V, - arg3?: Dom.RequestInit['headers'] -) { - return (arg1 as RawRequestOptions).query - ? (arg1 as RawRequestOptions) - : { - query: arg1 as string, - variables: arg2, - requestHeaders: arg3, - signal: undefined, - } -} - -function parseBatchRequestArgs( - arg1: BatchRequestDocument[] | BatchRequestOptions, - arg2?: Dom.RequestInit['headers'] -) { - return (arg1 as BatchRequestOptions).documents - ? (arg1 as BatchRequestOptions) - : { - documents: arg1 as BatchRequestDocument[], - requestHeaders: arg2, - signal: undefined, - } + RequestExtendedOptions, + Variables, } /** @@ -309,9 +280,9 @@ export class GraphQLClient { documents: BatchRequestDocument[], requestHeaders?: Dom.RequestInit['headers'] ): Promise - async batchRequests(options: BatchRequestOptions): Promise + async batchRequests(options: BatchRequestsOptions): Promise async batchRequests( - arg1: BatchRequestDocument[] | BatchRequestOptions, + arg1: BatchRequestDocument[] | BatchRequestsOptions, arg2?: Dom.RequestInit['headers'] ): Promise { const batchRequestOptions = parseBatchRequestArgs(arg1, arg2) @@ -428,20 +399,32 @@ async function makeRequest({ } /** - * todo + * Send a GraphQL Query to the GraphQL server for execution. */ export async function rawRequest( url: string, query: string, variables?: V, requestHeaders?: Dom.RequestInit['headers'] +): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> +export async function rawRequest( + options: RawRequestExtendedOptions +): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> +export async function rawRequest( + arg1: string | RawRequestExtendedOptions, + arg2?: string, + arg3?: V, + arg4?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { - const client = new GraphQLClient(url) - return client.rawRequest(query, variables, requestHeaders) + const requestOptions = parseRawRequestExtendedArgs(arg1, arg2, arg3, arg4) + const client = new GraphQLClient(requestOptions.url) + return client.rawRequest({ + ...requestOptions, + }) } /** - * Send a GraphQL Document to the GraphQL server for exectuion. + * Send a GraphQL Document to the GraphQL server for execution. * * @example * @@ -479,9 +462,19 @@ export async function request( document: RequestDocument, variables?: V, requestHeaders?: Dom.RequestInit['headers'] +): Promise +export async function request(options: RequestExtendedOptions): Promise +export async function request( + arg1: string | RequestExtendedOptions, + arg2?: RequestDocument, + arg3?: V, + arg4?: Dom.RequestInit['headers'] ): Promise { - const client = new GraphQLClient(url) - return client.request(document, variables, requestHeaders) + const requestOptions = parseRequestExtendedArgs(arg1, arg2, arg3, arg4) + const client = new GraphQLClient(requestOptions.url) + return client.request({ + ...requestOptions, + }) } /** @@ -518,13 +511,20 @@ export async function request( * await batchRequests('https://foo.bar/graphql', [{ query: gql`...` }]) * ``` */ -export async function batchRequests( +export async function batchRequests( url: string, documents: BatchRequestDocument[], requestHeaders?: Dom.RequestInit['headers'] +): Promise +export async function batchRequests(options: BatchRequestsExtendedOptions): Promise +export async function batchRequests( + arg1: string | BatchRequestsExtendedOptions, + arg2?: BatchRequestDocument[], + arg3?: Dom.RequestInit['headers'] ): Promise { - const client = new GraphQLClient(url) - return client.batchRequests(documents, requestHeaders) + const requestOptions = parseBatchRequestsExtendedArgs(arg1, arg2, arg3) + const client = new GraphQLClient(requestOptions.url) + return client.batchRequests({ ...requestOptions }) } export default request diff --git a/src/parseArgs.ts b/src/parseArgs.ts new file mode 100644 index 000000000..407c3b4ca --- /dev/null +++ b/src/parseArgs.ts @@ -0,0 +1,104 @@ +import { + BatchRequestDocument, + BatchRequestsOptions, + RawRequestOptions, + RequestDocument, + RequestOptions, + BatchRequestsExtendedOptions, + RawRequestExtendedOptions, + RequestExtendedOptions, + Variables, +} from './types' +import * as Dom from './types.dom' + +export function parseRequestArgs( + arg1: RequestDocument | RequestOptions, + arg2?: V, + arg3?: Dom.RequestInit['headers'] +) { + return (arg1 as RequestOptions).document + ? (arg1 as RequestOptions) + : { + document: arg1 as RequestDocument, + variables: arg2, + requestHeaders: arg3, + signal: undefined, + } +} + +export function parseRawRequestArgs( + arg1: RequestDocument | RawRequestOptions, + arg2?: V, + arg3?: Dom.RequestInit['headers'] +) { + return (arg1 as RawRequestOptions).query + ? (arg1 as RawRequestOptions) + : { + query: arg1 as string, + variables: arg2, + requestHeaders: arg3, + signal: undefined, + } +} + +export function parseBatchRequestArgs( + arg1: BatchRequestDocument[] | BatchRequestsOptions, + arg2?: Dom.RequestInit['headers'] +) { + return (arg1 as BatchRequestsOptions).documents + ? (arg1 as BatchRequestsOptions) + : { + documents: arg1 as BatchRequestDocument[], + requestHeaders: arg2, + signal: undefined, + } +} + +export function parseRequestExtendedArgs( + arg1: string | RequestExtendedOptions, + arg2?: RequestDocument, + arg3?: V, + arg4?: Dom.RequestInit['headers'] +) { + return (arg1 as RequestExtendedOptions).document + ? (arg1 as RequestExtendedOptions) + : { + url: arg1 as string, + document: arg2 as RequestDocument, + variables: arg3, + requestHeaders: arg4, + signal: undefined, + } +} + +export function parseRawRequestExtendedArgs( + arg1: string | RawRequestExtendedOptions, + arg2?: string, + arg3?: V, + arg4?: Dom.RequestInit['headers'] +) { + return (arg1 as RawRequestExtendedOptions).query + ? (arg1 as RawRequestExtendedOptions) + : { + url: arg1 as string, + query: arg2 as string, + variables: arg3, + requestHeaders: arg4, + signal: undefined, + } +} + +export function parseBatchRequestsExtendedArgs( + arg1: string | BatchRequestsExtendedOptions, + arg2?: BatchRequestDocument[], + arg3?: Dom.RequestInit['headers'] +) { + return (arg1 as BatchRequestsExtendedOptions).documents + ? (arg1 as BatchRequestsExtendedOptions) + : { + url: arg1 as string, + documents: arg2 as BatchRequestDocument[], + requestHeaders: arg3, + signal: undefined, + } +} diff --git a/src/types.ts b/src/types.ts index 1360072a7..0a77e67e4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -76,8 +76,14 @@ export type RequestOptions = { signal?: Dom.RequestInit['signal'] } -export type BatchRequestOptions = { +export type BatchRequestsOptions = { documents: BatchRequestDocument[] requestHeaders?: Dom.RequestInit['headers'] signal?: Dom.RequestInit['signal'] } + +export type RequestExtendedOptions = { url: string } & RequestOptions + +export type RawRequestExtendedOptions = { url: string } & RawRequestOptions + +export type BatchRequestsExtendedOptions = { url: string } & BatchRequestsOptions diff --git a/tests/signal.test.ts b/tests/signal.test.ts index 09afdcc27..ec4643e88 100644 --- a/tests/signal.test.ts +++ b/tests/signal.test.ts @@ -116,3 +116,132 @@ it('should abort batch requests when the signal overrides GraphQLClient settings expect((error as Error).message).toEqual('The user aborted a request.') } }) + +it('should abort a request', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + try { + await request({ + url: ctx.url, + document: '{ me { id } }', + signal: abortController.signal, + }) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort a request after the request has been sent', async () => { + const abortController = new AbortController() + ctx.res({ + body: { + data: { + me: { + id: 'some-id', + }, + }, + }, + }).spec.body! + + expect.assertions(1) + + request({ + url: ctx.url, + document: '{ me { id } }', + signal: abortController.signal, + }).catch((error) => { + expect((error as Error).message).toEqual('The user aborted a request.') + }) + + await sleep(10) + abortController.abort() + await sleep(20) +}) + +it('should abort a raw request', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + try { + await rawRequest({ + url: ctx.url, + query: '{ me { id } }', + signal: abortController.signal, + }) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort a raw request after the request has been sent', async () => { + const abortController = new AbortController() + ctx.res({ + body: { + data: { + me: { + id: 'some-id', + }, + }, + }, + }).spec.body! + + expect.assertions(1) + + rawRequest({ + url: ctx.url, + query: '{ me { id } }', + signal: abortController.signal, + }).catch((error) => { + expect((error as Error).message).toEqual('The user aborted a request.') + }) + + await sleep(10) + abortController.abort() + await sleep(20) +}) + +it('should abort batch requests', async () => { + const abortController = new AbortController() + abortController.abort() + expect.assertions(1) + + try { + await batchRequests({ + url: ctx.url, + documents: [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], + signal: abortController.signal, + }) + } catch (error) { + expect((error as Error).message).toEqual('The user aborted a request.') + } +}) + +it('should abort batch requests after a request has been sent', async () => { + const abortController = new AbortController() + ctx.res({ + body: { + data: { + me: { + id: 'some-id', + }, + }, + }, + }).spec.body! + + expect.assertions(1) + + batchRequests({ + url: ctx.url, + documents: [{ document: `{ me { id } }` }, { document: `{ me { id } }` }], + signal: abortController.signal, + }).catch((error) => { + expect((error as Error).message).toEqual('The user aborted a request.') + }) + + await sleep(10) + abortController.abort() + await sleep(20) +}) From 2f8a43ab006ed969b052331d7dff8650f1d2d809 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Sat, 4 Dec 2021 21:37:07 +0100 Subject: [PATCH 06/11] refactor: add return type to parse functions and add missing generics --- src/index.ts | 38 ++++++++++++++++++++------------------ src/parseArgs.ts | 36 ++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/index.ts b/src/index.ts index a7c44b0e5..1f9cb96b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -202,14 +202,14 @@ export class GraphQLClient { requestHeaders?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> async rawRequest( - options: RawRequestOptions + options: RawRequestOptions ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> async rawRequest( - arg1: RequestDocument | RawRequestOptions, + arg1: RequestDocument | RawRequestOptions, arg2?: V, arg3?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { - const rawRequestOptions = parseRawRequestArgs(arg1, arg2, arg3) + const rawRequestOptions = parseRawRequestArgs(arg1, arg2, arg3) let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this @@ -240,13 +240,13 @@ export class GraphQLClient { variables?: V, requestHeaders?: Dom.RequestInit['headers'] ): Promise - async request(options: RequestOptions): Promise + async request(options: RequestOptions): Promise async request( - arg1: RequestDocument | RequestOptions, + arg1: RequestDocument | RequestOptions, arg2?: V, arg3?: Dom.RequestInit['headers'] ): Promise { - const requestOptions = parseRequestArgs(arg1, arg2, arg3) + const requestOptions = parseRequestArgs(arg1, arg2, arg3) let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this @@ -280,12 +280,12 @@ export class GraphQLClient { documents: BatchRequestDocument[], requestHeaders?: Dom.RequestInit['headers'] ): Promise - async batchRequests(options: BatchRequestsOptions): Promise + async batchRequests(options: BatchRequestsOptions): Promise async batchRequests( - arg1: BatchRequestDocument[] | BatchRequestsOptions, + arg1: BatchRequestDocument[] | BatchRequestsOptions, arg2?: Dom.RequestInit['headers'] ): Promise { - const batchRequestOptions = parseBatchRequestArgs(arg1, arg2) + const batchRequestOptions = parseBatchRequestArgs(arg1, arg2) let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this @@ -408,15 +408,15 @@ export async function rawRequest( requestHeaders?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> export async function rawRequest( - options: RawRequestExtendedOptions + options: RawRequestExtendedOptions ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> export async function rawRequest( - arg1: string | RawRequestExtendedOptions, + arg1: string | RawRequestExtendedOptions, arg2?: string, arg3?: V, arg4?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { - const requestOptions = parseRawRequestExtendedArgs(arg1, arg2, arg3, arg4) + const requestOptions = parseRawRequestExtendedArgs(arg1, arg2, arg3, arg4) const client = new GraphQLClient(requestOptions.url) return client.rawRequest({ ...requestOptions, @@ -463,14 +463,14 @@ export async function request( variables?: V, requestHeaders?: Dom.RequestInit['headers'] ): Promise -export async function request(options: RequestExtendedOptions): Promise +export async function request(options: RequestExtendedOptions): Promise export async function request( - arg1: string | RequestExtendedOptions, + arg1: string | RequestExtendedOptions, arg2?: RequestDocument, arg3?: V, arg4?: Dom.RequestInit['headers'] ): Promise { - const requestOptions = parseRequestExtendedArgs(arg1, arg2, arg3, arg4) + const requestOptions = parseRequestExtendedArgs(arg1, arg2, arg3, arg4) const client = new GraphQLClient(requestOptions.url) return client.request({ ...requestOptions, @@ -516,13 +516,15 @@ export async function batchRequests( documents: BatchRequestDocument[], requestHeaders?: Dom.RequestInit['headers'] ): Promise -export async function batchRequests(options: BatchRequestsExtendedOptions): Promise export async function batchRequests( - arg1: string | BatchRequestsExtendedOptions, + options: BatchRequestsExtendedOptions +): Promise +export async function batchRequests( + arg1: string | BatchRequestsExtendedOptions, arg2?: BatchRequestDocument[], arg3?: Dom.RequestInit['headers'] ): Promise { - const requestOptions = parseBatchRequestsExtendedArgs(arg1, arg2, arg3) + const requestOptions = parseBatchRequestsExtendedArgs(arg1, arg2, arg3) const client = new GraphQLClient(requestOptions.url) return client.batchRequests({ ...requestOptions }) } diff --git a/src/parseArgs.ts b/src/parseArgs.ts index 407c3b4ca..ccc8965ab 100644 --- a/src/parseArgs.ts +++ b/src/parseArgs.ts @@ -12,11 +12,11 @@ import { import * as Dom from './types.dom' export function parseRequestArgs( - arg1: RequestDocument | RequestOptions, + arg1: RequestDocument | RequestOptions, arg2?: V, arg3?: Dom.RequestInit['headers'] -) { - return (arg1 as RequestOptions).document +): RequestOptions { + return (arg1 as RequestOptions).document ? (arg1 as RequestOptions) : { document: arg1 as RequestDocument, @@ -27,11 +27,11 @@ export function parseRequestArgs( } export function parseRawRequestArgs( - arg1: RequestDocument | RawRequestOptions, + arg1: RequestDocument | RawRequestOptions, arg2?: V, arg3?: Dom.RequestInit['headers'] -) { - return (arg1 as RawRequestOptions).query +): RawRequestOptions { + return (arg1 as RawRequestOptions).query ? (arg1 as RawRequestOptions) : { query: arg1 as string, @@ -42,10 +42,10 @@ export function parseRawRequestArgs( } export function parseBatchRequestArgs( - arg1: BatchRequestDocument[] | BatchRequestsOptions, + arg1: BatchRequestDocument[] | BatchRequestsOptions, arg2?: Dom.RequestInit['headers'] -) { - return (arg1 as BatchRequestsOptions).documents +): BatchRequestsOptions { + return (arg1 as BatchRequestsOptions).documents ? (arg1 as BatchRequestsOptions) : { documents: arg1 as BatchRequestDocument[], @@ -55,12 +55,12 @@ export function parseBatchRequestArgs( } export function parseRequestExtendedArgs( - arg1: string | RequestExtendedOptions, + arg1: string | RequestExtendedOptions, arg2?: RequestDocument, arg3?: V, arg4?: Dom.RequestInit['headers'] -) { - return (arg1 as RequestExtendedOptions).document +): RequestExtendedOptions { + return (arg1 as RequestExtendedOptions).document ? (arg1 as RequestExtendedOptions) : { url: arg1 as string, @@ -72,12 +72,12 @@ export function parseRequestExtendedArgs( } export function parseRawRequestExtendedArgs( - arg1: string | RawRequestExtendedOptions, + arg1: string | RawRequestExtendedOptions, arg2?: string, arg3?: V, arg4?: Dom.RequestInit['headers'] -) { - return (arg1 as RawRequestExtendedOptions).query +): RawRequestExtendedOptions { + return (arg1 as RawRequestExtendedOptions).query ? (arg1 as RawRequestExtendedOptions) : { url: arg1 as string, @@ -89,11 +89,11 @@ export function parseRawRequestExtendedArgs( } export function parseBatchRequestsExtendedArgs( - arg1: string | BatchRequestsExtendedOptions, + arg1: string | BatchRequestsExtendedOptions, arg2?: BatchRequestDocument[], arg3?: Dom.RequestInit['headers'] -) { - return (arg1 as BatchRequestsExtendedOptions).documents +): BatchRequestsExtendedOptions { + return (arg1 as BatchRequestsExtendedOptions).documents ? (arg1 as BatchRequestsExtendedOptions) : { url: arg1 as string, From c513e619769644e121ea7b9a0feebb2185c38401 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Tue, 7 Dec 2021 16:26:57 +0100 Subject: [PATCH 07/11] refactor: args naming in new overloads --- src/index.ts | 50 ++++++++++++------------ src/parseArgs.ts | 100 +++++++++++++++++++++++------------------------ 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1f9cb96b2..46398fd3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -205,11 +205,11 @@ export class GraphQLClient { options: RawRequestOptions ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> async rawRequest( - arg1: RequestDocument | RawRequestOptions, - arg2?: V, - arg3?: Dom.RequestInit['headers'] + queryOrOptions: string | RawRequestOptions, + variables?: V, + requestHeaders?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { - const rawRequestOptions = parseRawRequestArgs(arg1, arg2, arg3) + const rawRequestOptions = parseRawRequestArgs(queryOrOptions, variables, requestHeaders) let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this @@ -242,11 +242,11 @@ export class GraphQLClient { ): Promise async request(options: RequestOptions): Promise async request( - arg1: RequestDocument | RequestOptions, - arg2?: V, - arg3?: Dom.RequestInit['headers'] + documentOrOptions: RequestDocument | RequestOptions, + variables?: V, + requestHeaders?: Dom.RequestInit['headers'] ): Promise { - const requestOptions = parseRequestArgs(arg1, arg2, arg3) + const requestOptions = parseRequestArgs(documentOrOptions, variables, requestHeaders) let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this @@ -282,10 +282,10 @@ export class GraphQLClient { ): Promise async batchRequests(options: BatchRequestsOptions): Promise async batchRequests( - arg1: BatchRequestDocument[] | BatchRequestsOptions, - arg2?: Dom.RequestInit['headers'] + documentsOrOptions: BatchRequestDocument[] | BatchRequestsOptions, + requestHeaders?: Dom.RequestInit['headers'] ): Promise { - const batchRequestOptions = parseBatchRequestArgs(arg1, arg2) + const batchRequestOptions = parseBatchRequestArgs(documentsOrOptions, requestHeaders) let { headers, fetch = crossFetch, method = 'POST', ...fetchOptions } = this.options let { url } = this @@ -411,12 +411,12 @@ export async function rawRequest( options: RawRequestExtendedOptions ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> export async function rawRequest( - arg1: string | RawRequestExtendedOptions, - arg2?: string, - arg3?: V, - arg4?: Dom.RequestInit['headers'] + urlOrOptions: string | RawRequestExtendedOptions, + query?: string, + variables?: V, + requestHeaders?: Dom.RequestInit['headers'] ): Promise<{ data: T; extensions?: any; headers: Dom.Headers; status: number }> { - const requestOptions = parseRawRequestExtendedArgs(arg1, arg2, arg3, arg4) + const requestOptions = parseRawRequestExtendedArgs(urlOrOptions, query, variables, requestHeaders) const client = new GraphQLClient(requestOptions.url) return client.rawRequest({ ...requestOptions, @@ -465,12 +465,12 @@ export async function request( ): Promise export async function request(options: RequestExtendedOptions): Promise export async function request( - arg1: string | RequestExtendedOptions, - arg2?: RequestDocument, - arg3?: V, - arg4?: Dom.RequestInit['headers'] + urlOrOptions: string | RequestExtendedOptions, + document?: RequestDocument, + variables?: V, + requestHeaders?: Dom.RequestInit['headers'] ): Promise { - const requestOptions = parseRequestExtendedArgs(arg1, arg2, arg3, arg4) + const requestOptions = parseRequestExtendedArgs(urlOrOptions, document, variables, requestHeaders) const client = new GraphQLClient(requestOptions.url) return client.request({ ...requestOptions, @@ -520,11 +520,11 @@ export async function batchRequests( options: BatchRequestsExtendedOptions ): Promise export async function batchRequests( - arg1: string | BatchRequestsExtendedOptions, - arg2?: BatchRequestDocument[], - arg3?: Dom.RequestInit['headers'] + urlOrOptions: string | BatchRequestsExtendedOptions, + documents?: BatchRequestDocument[], + requestHeaders?: Dom.RequestInit['headers'] ): Promise { - const requestOptions = parseBatchRequestsExtendedArgs(arg1, arg2, arg3) + const requestOptions = parseBatchRequestsExtendedArgs(urlOrOptions, documents, requestHeaders) const client = new GraphQLClient(requestOptions.url) return client.batchRequests({ ...requestOptions }) } diff --git a/src/parseArgs.ts b/src/parseArgs.ts index ccc8965ab..3e037c6d8 100644 --- a/src/parseArgs.ts +++ b/src/parseArgs.ts @@ -12,93 +12,93 @@ import { import * as Dom from './types.dom' export function parseRequestArgs( - arg1: RequestDocument | RequestOptions, - arg2?: V, - arg3?: Dom.RequestInit['headers'] + documentOrOptions: RequestDocument | RequestOptions, + variables?: V, + requestHeaders?: Dom.RequestInit['headers'] ): RequestOptions { - return (arg1 as RequestOptions).document - ? (arg1 as RequestOptions) + return (documentOrOptions as RequestOptions).document + ? (documentOrOptions as RequestOptions) : { - document: arg1 as RequestDocument, - variables: arg2, - requestHeaders: arg3, + document: documentOrOptions as RequestDocument, + variables: variables, + requestHeaders: requestHeaders, signal: undefined, } } export function parseRawRequestArgs( - arg1: RequestDocument | RawRequestOptions, - arg2?: V, - arg3?: Dom.RequestInit['headers'] + queryOrOptions: string | RawRequestOptions, + variables?: V, + requestHeaders?: Dom.RequestInit['headers'] ): RawRequestOptions { - return (arg1 as RawRequestOptions).query - ? (arg1 as RawRequestOptions) + return (queryOrOptions as RawRequestOptions).query + ? (queryOrOptions as RawRequestOptions) : { - query: arg1 as string, - variables: arg2, - requestHeaders: arg3, + query: queryOrOptions as string, + variables: variables, + requestHeaders: requestHeaders, signal: undefined, } } export function parseBatchRequestArgs( - arg1: BatchRequestDocument[] | BatchRequestsOptions, - arg2?: Dom.RequestInit['headers'] + documentsOrOptions: BatchRequestDocument[] | BatchRequestsOptions, + requestHeaders?: Dom.RequestInit['headers'] ): BatchRequestsOptions { - return (arg1 as BatchRequestsOptions).documents - ? (arg1 as BatchRequestsOptions) + return (documentsOrOptions as BatchRequestsOptions).documents + ? (documentsOrOptions as BatchRequestsOptions) : { - documents: arg1 as BatchRequestDocument[], - requestHeaders: arg2, + documents: documentsOrOptions as BatchRequestDocument[], + requestHeaders: requestHeaders, signal: undefined, } } export function parseRequestExtendedArgs( - arg1: string | RequestExtendedOptions, - arg2?: RequestDocument, - arg3?: V, - arg4?: Dom.RequestInit['headers'] + urlOrOptions: string | RequestExtendedOptions, + document?: RequestDocument, + variables?: V, + requestHeaders?: Dom.RequestInit['headers'] ): RequestExtendedOptions { - return (arg1 as RequestExtendedOptions).document - ? (arg1 as RequestExtendedOptions) + return (urlOrOptions as RequestExtendedOptions).document + ? (urlOrOptions as RequestExtendedOptions) : { - url: arg1 as string, - document: arg2 as RequestDocument, - variables: arg3, - requestHeaders: arg4, + url: urlOrOptions as string, + document: document as RequestDocument, + variables: variables, + requestHeaders: requestHeaders, signal: undefined, } } export function parseRawRequestExtendedArgs( - arg1: string | RawRequestExtendedOptions, - arg2?: string, - arg3?: V, - arg4?: Dom.RequestInit['headers'] + urlOrOptions: string | RawRequestExtendedOptions, + query?: string, + variables?: V, + requestHeaders?: Dom.RequestInit['headers'] ): RawRequestExtendedOptions { - return (arg1 as RawRequestExtendedOptions).query - ? (arg1 as RawRequestExtendedOptions) + return (urlOrOptions as RawRequestExtendedOptions).query + ? (urlOrOptions as RawRequestExtendedOptions) : { - url: arg1 as string, - query: arg2 as string, - variables: arg3, - requestHeaders: arg4, + url: urlOrOptions as string, + query: query as string, + variables: variables, + requestHeaders: requestHeaders, signal: undefined, } } export function parseBatchRequestsExtendedArgs( - arg1: string | BatchRequestsExtendedOptions, - arg2?: BatchRequestDocument[], - arg3?: Dom.RequestInit['headers'] + urlOrOptions: string | BatchRequestsExtendedOptions, + documents?: BatchRequestDocument[], + requestHeaders?: Dom.RequestInit['headers'] ): BatchRequestsExtendedOptions { - return (arg1 as BatchRequestsExtendedOptions).documents - ? (arg1 as BatchRequestsExtendedOptions) + return (urlOrOptions as BatchRequestsExtendedOptions).documents + ? (urlOrOptions as BatchRequestsExtendedOptions) : { - url: arg1 as string, - documents: arg2 as BatchRequestDocument[], - requestHeaders: arg3, + url: urlOrOptions as string, + documents: documents as BatchRequestDocument[], + requestHeaders: requestHeaders, signal: undefined, } } From 7b37a4591739a584465667c16238d7102f4a2332 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Wed, 5 Jan 2022 11:38:07 +0100 Subject: [PATCH 08/11] cicd: remove node v12 test v12 is not officially supported so the impact should be minimal. --- .github/workflows/pr.yml | 2 +- .github/workflows/trunk.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 514113b7c..935e40a56 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [12, 14, 16] + node: [14, 16] environment: [dom, node] name: Node ${{ matrix.node }} @env ${{matrix.environment}} steps: diff --git a/.github/workflows/trunk.yml b/.github/workflows/trunk.yml index a63495657..45486ffc9 100644 --- a/.github/workflows/trunk.yml +++ b/.github/workflows/trunk.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [12, 14, 16] + node: [14, 16] environment: [dom, node] name: Node ${{ matrix.node }} steps: From db2ef8c4745ca178af6c9d5f828d1d005b3843f7 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Wed, 5 Jan 2022 18:12:01 +0100 Subject: [PATCH 09/11] Revert "cicd: remove node v12 test" This reverts commit 7b37a4591739a584465667c16238d7102f4a2332. --- .github/workflows/pr.yml | 2 +- .github/workflows/trunk.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 935e40a56..514113b7c 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16] + node: [12, 14, 16] environment: [dom, node] name: Node ${{ matrix.node }} @env ${{matrix.environment}} steps: diff --git a/.github/workflows/trunk.yml b/.github/workflows/trunk.yml index 45486ffc9..a63495657 100644 --- a/.github/workflows/trunk.yml +++ b/.github/workflows/trunk.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [14, 16] + node: [12, 14, 16] environment: [dom, node] name: Node ${{ matrix.node }} steps: From 5c2d73dc16cb9a74cef8d4d2ca3dfd771f2bcad0 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Wed, 5 Jan 2022 19:40:40 +0100 Subject: [PATCH 10/11] test(signal): add AbortController polyfill to support Node.js version < 14.17.0 --- package.json | 1 + tests/signal.test.ts | 1 + yarn.lock | 12 ++++++++++++ 3 files changed, 14 insertions(+) diff --git a/package.json b/package.json index c84c263df..569b61d40 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "graphql": "14.x || 15.x" }, "devDependencies": { + "abort-controller": "^3.0.0", "@prisma-labs/prettier-config": "^0.1.0", "@types/body-parser": "^1.19.1", "@types/express": "^4.17.13", diff --git a/tests/signal.test.ts b/tests/signal.test.ts index ec4643e88..dc295c318 100644 --- a/tests/signal.test.ts +++ b/tests/signal.test.ts @@ -1,5 +1,6 @@ import { batchRequests, GraphQLClient, rawRequest, request } from '../src' import { setupTestServer, sleep } from './__helpers' +import 'abort-controller/polyfill' const ctx = setupTestServer(20) diff --git a/yarn.lock b/yarn.lock index c7705b2c8..0ec4d49f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1147,6 +1147,13 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@^1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -2121,6 +2128,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" From 19d45f89011cc833f9c2be7ddacd9e7ab226c053 Mon Sep 17 00:00:00 2001 From: arnaudbzn Date: Fri, 7 Jan 2022 21:50:31 +0100 Subject: [PATCH 11/11] doc(readme): add mention about supported node versions and polyfill --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 4f7c814a5..30afa4f1d 100644 --- a/README.md +++ b/README.md @@ -577,6 +577,15 @@ You can also set the signal per request (this will override an existing GraphQLC abortController.abort() ``` +In Node environment, `AbortController` is supported since version v14.17.0. +For Node.js v12 you can use [abort-controller](https://github.com/mysticatea/abort-controller) polyfill. + +```` + import 'abort-controller/polyfill' + + const abortController = new AbortController() +```` + ## FAQ #### Why do I have to install `graphql`?