From b563ad529c6d6247d2a338fe6f2754ffc061bfbf Mon Sep 17 00:00:00 2001 From: Jason Kuhrt Date: Tue, 4 Apr 2023 22:02:38 -0400 Subject: [PATCH] refactor: lint --- examples/cookie-support-for-node.ts | 2 +- examples/typed-document-node.ts | 3 +- src/createRequestBody.ts | 11 +++--- src/graphql-ws.ts | 1 + src/index.ts | 39 ++++++++------------ src/types.ts | 9 +++-- tests/__helpers.ts | 55 +++++++++++++++++------------ tests/custom-fetch.test.ts | 5 ++- tests/errorPolicy.test.ts | 4 +-- tests/general.test.ts | 33 ++++++++--------- tests/graphql-ws.test.ts | 40 ++++++++++----------- tests/json-serializer.test.ts | 22 ++++++------ 12 files changed, 110 insertions(+), 114 deletions(-) diff --git a/examples/cookie-support-for-node.ts b/examples/cookie-support-for-node.ts index 5bfe2611c..8c230a0be 100644 --- a/examples/cookie-support-for-node.ts +++ b/examples/cookie-support-for-node.ts @@ -1,4 +1,4 @@ -;(global as any).fetch = require(`fetch-cookie/node-fetch`)(require(`node-fetch`)) +;(global as any).fetch = require(`fetch-cookie/node-fetch`)(require(`node-fetch`)) //eslint-disable-line import { gql, GraphQLClient } from '../src/index.js' diff --git a/examples/typed-document-node.ts b/examples/typed-document-node.ts index 20c9c1878..6ba92aaa2 100644 --- a/examples/typed-document-node.ts +++ b/examples/typed-document-node.ts @@ -29,9 +29,8 @@ import { parse } from 'graphql' } `) - const variables = {} - const data = await client.request({ document: query }) + // const variables = {} // const data = await client.request({ document: query, variables: { a: 1 } }) console.log(data.greetings) diff --git a/src/createRequestBody.ts b/src/createRequestBody.ts index c81478609..607fa5fb1 100644 --- a/src/createRequestBody.ts +++ b/src/createRequestBody.ts @@ -8,9 +8,9 @@ import FormDataNode from 'form-data' * Duck type if NodeJS stream * https://github.com/sindresorhus/is-stream/blob/3750505b0727f6df54324784fe369365ef78841e/index.js#L3 */ -const isExtractableFileEnhanced = (value: any): value is ExtractableFile | { pipe: Function } => +const isExtractableFileEnhanced = (value: unknown): value is ExtractableFile | { pipe: () => unknown } => isExtractableFile(value) || - (value !== null && typeof value === `object` && typeof value.pipe === `function`) + (typeof value === `object` && value !== null && `pipe` in value && typeof value.pipe === `function`) /** * Returns Multipart Form if body contains files @@ -23,6 +23,7 @@ const createRequestBody = ( operationName?: string, jsonSerializer = defaultJsonSerializer ): string | FormData => { + // eslint-disable-next-line const { clone, files } = extractFiles({ query, variables, operationName }, ``, isExtractableFileEnhanced) if (files.size === 0) { @@ -36,9 +37,9 @@ const createRequestBody = ( // Batch support const payload = query.reduce<{ query: string; variables: Variables | undefined }[]>( - (accu, currentQuery, index) => { - accu.push({ query: currentQuery, variables: variables ? variables[index] : undefined }) - return accu + (acc, currentQuery, index) => { + acc.push({ query: currentQuery, variables: variables ? variables[index] : undefined }) + return acc }, [] ) diff --git a/src/graphql-ws.ts b/src/graphql-ws.ts index 4c610ad0b..b2b19d3c4 100644 --- a/src/graphql-ws.ts +++ b/src/graphql-ws.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { resolveRequestDocument } from './resolveRequestDocument.js' import type { RequestDocument, Variables } from './types.js' import { ClientError } from './types.js' diff --git a/src/index.ts b/src/index.ts index 2669c21e5..09e51e0cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -316,10 +316,7 @@ export class GraphQLClient { // prettier-ignore batchRequests(options: BatchRequestsOptions): Promise // prettier-ignore - batchRequests( - documentsOrOptions: BatchRequestDocument[] | BatchRequestsOptions, - requestHeaders?: GraphQLClientRequestHeaders - ): Promise { + batchRequests(documentsOrOptions: BatchRequestDocument[] | BatchRequestsOptions, requestHeaders?: GraphQLClientRequestHeaders): Promise { const batchRequestOptions = parseBatchRequestArgs(documentsOrOptions, requestHeaders) const { headers, ...fetchOptions } = this.requestConfig @@ -374,7 +371,7 @@ export class GraphQLClient { if (headers) { // todo what if headers is in nested array form... ? - //@ts-ignore + //@ts-expect-error todo headers[key] = value } else { this.requestConfig.headers = { [key]: value } @@ -422,7 +419,7 @@ const makeRequest = async (params: if (response.ok && successfullyPassedErrorPolicy && successfullyReceivedData) { // @ts-expect-error TODO fixme - const { errors, ...rest } = Array.isArray(result) ? result : result + const { errors: _, ...rest } = Array.isArray(result) ? result : result const data = fetchOptions.errorPolicy === `ignore` ? rest : result const dataEnvelope = isBatchingQuery ? { data } : data @@ -517,22 +514,13 @@ export const rawRequest: RawRequest = async ( * await request('https://foo.bar/graphql', gql`...`) * ``` */ -export async function request( - url: string, - // @ts-ignore - document: RequestDocument | TypedDocumentNode, - ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs -): Promise -export async function request( - options: RequestExtendedOptions -): Promise -export async function request( - urlOrOptions: string | RequestExtendedOptions, - // @ts-ignore - document?: RequestDocument | TypedDocumentNode, - ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs -): Promise { - // @ts-ignore +// prettier-ignore +export async function request(url: string, document: RequestDocument | TypedDocumentNode, ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs): Promise +// prettier-ignore +export async function request(options: RequestExtendedOptions): Promise +// prettier-ignore +// eslint-disable-next-line +export async function request(urlOrOptions: string | RequestExtendedOptions, document?: RequestDocument | TypedDocumentNode, ...variablesAndRequestHeaders: VariablesAndRequestHeadersArgs): Promise { const requestOptions = parseRequestExtendedArgs(urlOrOptions, document, ...variablesAndRequestHeaders) const client = new GraphQLClient(requestOptions.url) return client.request({ @@ -648,18 +636,19 @@ const callOrIdentity = (value: MaybeFunction) => { * Convenience passthrough template tag to get the benefits of tooling for the gql template tag. This does not actually parse the input into a GraphQL DocumentNode like graphql-tag package does. It just returns the string with any variables given interpolated. Can save you a bit of performance and having to install another package. * * @example - * + * ``` * import { gql } from 'graphql-request' * * await request('https://foo.bar/graphql', gql`...`) + * ``` * * @remarks * * Several tools in the Node GraphQL ecosystem are hardcoded to specially treat any template tag named "gql". For example see this prettier issue: https://github.com/prettier/prettier/issues/4360. Using this template tag has no runtime effect beyond variable interpolation. */ -export const gql = (chunks: TemplateStringsArray, ...variables: any[]): string => { +export const gql = (chunks: TemplateStringsArray, ...variables: unknown[]): string => { return chunks.reduce( - (accumulator, chunk, index) => `${accumulator}${chunk}${index in variables ? variables[index] : ``}`, + (acc, chunk, index) => `${acc}${chunk}${index in variables ? String(variables[index]) : ``}`, `` ) } diff --git a/src/types.ts b/src/types.ts index aab5510d5..5bca8401a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,8 +1,11 @@ import type { RemoveIndex } from './helpers.js' import type { TypedDocumentNode } from '@graphql-typed-document-node/core' +import type { fetch } from 'cross-fetch' import type { GraphQLError } from 'graphql/error/GraphQLError.js' import type { DocumentNode } from 'graphql/language/ast.js' +export type Fetch = typeof fetch + /** * 'None' will throw whenever the response contains errors * @@ -13,8 +16,8 @@ import type { DocumentNode } from 'graphql/language/ast.js' export type ErrorPolicy = 'none' | 'ignore' | 'all' export interface JsonSerializer { - stringify(obj: any): string - parse(obj: string): unknown + stringify: (obj: any) => string + parse: (obj: string) => unknown } export interface AdditionalRequestOptions { @@ -89,7 +92,7 @@ export interface GraphQLClientResponse { export type HTTPMethodInput = 'GET' | 'POST' | 'get' | 'post' export interface RequestConfig extends Omit, AdditionalRequestOptions { - fetch?: any + fetch?: Fetch method?: HTTPMethodInput headers?: MaybeFunction requestMiddleware?: RequestMiddleware diff --git a/tests/__helpers.ts b/tests/__helpers.ts index 055f8b508..56e26661d 100644 --- a/tests/__helpers.ts +++ b/tests/__helpers.ts @@ -1,3 +1,4 @@ +import type { ApolloServerExpressConfig } from 'apollo-server-express' import { ApolloServer } from 'apollo-server-express' import body from 'body-parser' import type { Application, Request } from 'express' @@ -45,7 +46,9 @@ type MockResult = { }[] } -export function setupMockServer(delay?: number): Context { +export const setupMockServer = ( + delay?: number +): Context => { const ctx = {} as Context beforeAll(async () => { const port = await getPort() @@ -60,23 +63,24 @@ export function setupMockServer(d ctx.url = `http://localhost:${port}` ctx.res = (spec?: T): MockResult => { const requests: CapturedRequest[] = [] - ctx.server.use(`*`, async function mock(req, res) { - if (delay) { - await sleep(delay) - } - - req.headers.host = `DYNAMIC` - requests.push({ - method: req.method, - headers: req.headers, - body: req.body, - }) - if (spec?.headers) { - Object.entries(spec.headers).forEach(([name, value]) => { - res.setHeader(name, value) + // eslint-disable-next-line + ctx.server.use(`*`, function mock(req, res) { + void new Promise((res) => { + delay ? setTimeout(res, delay) : res(undefined) + }).then(() => { + req.headers.host = `DYNAMIC` + requests.push({ + method: req.method, + headers: req.headers, + body: req.body, // eslint-disable-line }) - } - res.send(spec?.body ?? { data: {} }) + if (spec?.headers) { + Object.entries(spec.headers).forEach(([name, value]) => { + res.setHeader(name, value) + }) + } + res.send(spec?.body ?? { data: {} }) + }) }) return { spec, requests: requests } as MockResult @@ -85,7 +89,9 @@ export function setupMockServer(d afterEach(() => { // https://stackoverflow.com/questions/10378690/remove-route-mappings-in-nodejs-express/28369539#28369539 + // eslint-disable-next-line ctx.server._router.stack.forEach((item: any, i: number) => { + // eslint-disable-next-line if (item.name === `mock`) ctx.server._router.stack.splice(i, 1) }) }) @@ -101,15 +107,18 @@ export function setupMockServer(d return ctx } -type ApolloServerContextOptions = { typeDefs: string; resolvers: any } +type ApolloServerContextOptions = { + typeDefs: string + resolvers: ApolloServerExpressConfig['resolvers'] +} -export async function startApolloServer({ typeDefs, resolvers }: ApolloServerContextOptions) { +export const startApolloServer = async ({ typeDefs, resolvers }: ApolloServerContextOptions) => { const app = express() const apolloServer = new ApolloServer({ typeDefs, resolvers }) await apolloServer.start() - apolloServer.applyMiddleware({ app: app as any }) + apolloServer.applyMiddleware({ app }) let server: Server @@ -120,8 +129,8 @@ export async function startApolloServer({ typeDefs, resolvers }: ApolloServerCon return server! } -export function createApolloServerContext({ typeDefs, resolvers }: ApolloServerContextOptions) { - const ctx: { url: string; server: Server } = {} as any +export const createApolloServerContext = ({ typeDefs, resolvers }: ApolloServerContextOptions) => { + const ctx: { url: string; server: Server } = {} as any // eslint-disable-line beforeEach(async () => { ctx.server = await startApolloServer({ typeDefs, resolvers }) @@ -140,7 +149,7 @@ export function createApolloServerContext({ typeDefs, resolvers }: ApolloServerC return ctx } -export function sleep(timeout: number): Promise { +export const sleep = (timeout: number): Promise => { return new Promise((resolve) => { setTimeout(resolve, timeout) }) diff --git a/tests/custom-fetch.test.ts b/tests/custom-fetch.test.ts index f5b11aaa7..2a4167531 100644 --- a/tests/custom-fetch.test.ts +++ b/tests/custom-fetch.test.ts @@ -7,13 +7,12 @@ const ctx = setupMockServer() test(`with custom fetch`, async () => { let touched = false - // wrap fetch in a custom method - const customFetch = function (input: RequestInfo, init?: RequestInit) { + const customFetch = (input: RequestInfo | URL, init?: RequestInit) => { touched = true return fetch(input, init) } const client = new GraphQLClient(ctx.url, { fetch: customFetch }) - const mock = ctx.res() + ctx.res() await client.request(`{ me { id } }`) expect(touched).toEqual(true) }) diff --git a/tests/errorPolicy.test.ts b/tests/errorPolicy.test.ts index 8f9bb48a4..1702cbaee 100644 --- a/tests/errorPolicy.test.ts +++ b/tests/errorPolicy.test.ts @@ -21,7 +21,7 @@ test(`should throw error when error policy not set`, async () => { }, }) - expect(async () => await new GraphQLClient(ctx.url).rawRequest(`x`)).rejects.toThrow(`GraphQL Error`) + await expect(() => new GraphQLClient(ctx.url).rawRequest(`x`)).rejects.toThrow(`GraphQL Error`) }) test(`should throw error when error policy set to "none"`, async () => { @@ -32,7 +32,7 @@ test(`should throw error when error policy set to "none"`, async () => { }, }) - expect(async () => await new GraphQLClient(ctx.url).rawRequest(`x`)).rejects.toThrow(`GraphQL Error`) + await expect(() => new GraphQLClient(ctx.url).rawRequest(`x`)).rejects.toThrow(`GraphQL Error`) }) test(`should not throw error when error policy set to "ignore" and return only data`, async () => { diff --git a/tests/general.test.ts b/tests/general.test.ts index bd119cabd..3d05c8b58 100644 --- a/tests/general.test.ts +++ b/tests/general.test.ts @@ -34,7 +34,7 @@ test(`minimal raw query`, async () => { }, }, }).spec.body! - const { headers, ...result } = await rawRequest(ctx.url, `{ me { id } }`) + const { headers: _, ...result } = await rawRequest(ctx.url, `{ me { id } }`) expect(result).toEqual({ data, extensions, status: 200 }) }) @@ -63,7 +63,7 @@ test(`minimal raw query with response headers`, async () => { }) test(`minimal raw query with response headers and new graphql content type`, async () => { - const { headers: reqHeaders, body } = ctx.res({ + const { headers: _, body } = ctx.res({ headers: { 'Content-Type': `application/graphql+json`, }, @@ -79,13 +79,13 @@ test(`minimal raw query with response headers and new graphql content type`, asy }, }).spec - const { headers, ...result } = await rawRequest(ctx.url, `{ me { id } }`) + const { headers: __, ...result } = await rawRequest(ctx.url, `{ me { id } }`) expect(result).toEqual({ ...body, status: 200 }) }) test(`minimal raw query with response headers and application/graphql-response+json response type`, async () => { - const { headers: reqHeaders, body } = ctx.res({ + const { headers: _, body } = ctx.res({ headers: { 'Content-Type': `application/graphql-response+json`, }, @@ -101,7 +101,7 @@ test(`minimal raw query with response headers and application/graphql-response+j }, }).spec - const { headers, ...result } = await rawRequest(ctx.url, `{ me { id } }`) + const { headers: __, ...result } = await rawRequest(ctx.url, `{ me { id } }`) expect(result).toEqual({ ...body, status: 200 }) }) @@ -157,7 +157,7 @@ test(`basic error with raw request`, async () => { }, }, }) - const res = await rawRequest(ctx.url, `x`).catch((x) => x) + const res: unknown = await rawRequest(ctx.url, `x`).catch((x) => x) expect(res).toMatchInlineSnapshot( `[Error: GraphQL Error (Code: 200): {"response":{"errors":{"message":"Syntax Error GraphQL request (1:1) Unexpected Name \\"x\\"\\n\\n1: x\\n ^\\n","locations":[{"line":1,"column":1}]},"status":200,"headers":{}},"request":{"query":"x"}}]` ) @@ -229,8 +229,7 @@ describe(`middleware`, () => { }, }, }) - - requestMiddleware = vitest.fn(async (req) => ({ ...req })) + requestMiddleware = vitest.fn((req) => ({ ...req })) client = new GraphQLClient(ctx.url, { requestMiddleware, }) @@ -343,21 +342,17 @@ test(`case-insensitive content-type header for custom fetch`, async () => { testResponseHeaders.set(`ConTENT-type`, `apPliCatiON/JSON`) const options: RequestConfig = { - fetch: function (url: string) { - return Promise.resolve({ + // @ts-expect-error testing + fetch: (url) => + Promise.resolve({ headers: testResponseHeaders, data: testData, - json: function () { - return testData - }, - text: function () { - return JSON.stringify(testData) - }, + json: () => testData, + text: () => JSON.stringify(testData), ok: true, status: 200, url, - }) - }, + }), } const client = new GraphQLClient(ctx.url, options) @@ -398,7 +393,7 @@ describe(`operationName parsing`, () => { }) }) -test(`should not throw error when errors property is an empty array (occured when using UltraGraphQL)`, async () => { +test(`should not throw error when errors property is an empty array (occurred when using UltraGraphQL)`, async () => { ctx.res({ body: { data: { test: `test` }, diff --git a/tests/graphql-ws.test.ts b/tests/graphql-ws.test.ts index 1aa5035f3..a8b101744 100644 --- a/tests/graphql-ws.test.ts +++ b/tests/graphql-ws.test.ts @@ -1,17 +1,17 @@ +import { GraphQLWebSocketClient } from '../src/graphql-ws.js' import { makeExecutableSchema } from '@graphql-tools/schema' +import getPort from 'get-port' import { gql } from 'graphql-tag' -import { useServer } from 'graphql-ws/lib/use/ws' import { GRAPHQL_TRANSPORT_WS_PROTOCOL } from 'graphql-ws' -import { GraphQLWebSocketClient } from '../src/graphql-ws.js' -import getPort from 'get-port' +import { useServer } from 'graphql-ws/lib/use/ws' +import { afterAll, beforeAll, expect, test } from 'vitest' import WebSocketImpl, { WebSocketServer } from 'ws' -import { beforeAll, afterAll, expect, test } from 'vitest' -async function createClient(url: string) { +const createClient = async (url: string) => { return new Promise((resolve) => { - const socket = new WebSocketImpl(url, GRAPHQL_TRANSPORT_WS_PROTOCOL) - const client: GraphQLWebSocketClient = new GraphQLWebSocketClient(socket as unknown as WebSocket, { - onAcknowledged: async (_p) => resolve(client), + const socket = new WebSocketImpl(url, GRAPHQL_TRANSPORT_WS_PROTOCOL) as unknown as WebSocket + const client: GraphQLWebSocketClient = new GraphQLWebSocketClient(socket, { + onAcknowledged: (_p) => Promise.resolve(resolve(client)), }) }) } @@ -27,13 +27,13 @@ const typeDefs = gql` const resolvers = { Query: { - hello: () => 'world', + hello: () => `world`, }, Subscription: { greetings: { subscribe: async function* () { - for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) { - yield { greetings: hi } + for (const hi of [`Hi`, `Bonjour`, `Hola`, `Ciao`, `Zdravo`]) { + yield await Promise.resolve({ greetings: hi }) } }, }, @@ -42,11 +42,11 @@ const resolvers = { const schema = makeExecutableSchema({ typeDefs, resolvers }) -var ctx: { server: WebSocketServer; url: string } +let ctx: { server: WebSocketServer; url: string } beforeAll(async () => { const port = await getPort() - const server = new WebSocketServer({ path: '/graphql', host: 'localhost', port }) + const server = new WebSocketServer({ path: `/graphql`, host: `localhost`, port }) useServer({ schema }, server) ctx = { server, url: `ws://localhost:${port}/graphql` } }) @@ -55,7 +55,7 @@ afterAll(() => { ctx.server.close() }) -test('graphql-ws request', async () => { +test(`graphql-ws request`, async () => { const client = await createClient(ctx.url) const data = client.request( gql` @@ -64,14 +64,14 @@ test('graphql-ws request', async () => { } ` ) - expect(await data).toEqual({ hello: 'world' }) + expect(await data).toEqual({ hello: `world` }) client.close() }) -test('graphql-ws subscription', async () => { +test(`graphql-ws subscription`, async () => { const client = await createClient(ctx.url) const result = new Promise((resolve) => { - var allGreatings = '' + let allGreetings = `` client.subscribe<{ greetings: string }>( gql` subscription greetings { @@ -80,13 +80,13 @@ test('graphql-ws subscription', async () => { `, { next: ({ greetings }) => - (allGreatings = allGreatings != '' ? `${allGreatings},${greetings}` : greetings), + (allGreetings = allGreetings != `` ? `${allGreetings},${greetings}` : greetings), complete: () => { - resolve(allGreatings) + resolve(allGreetings) }, } ) }) - expect(await result).toEqual('Hi,Bonjour,Hola,Ciao,Zdravo') + expect(await result).toEqual(`Hi,Bonjour,Hola,Ciao,Zdravo`) client.close() }) diff --git a/tests/json-serializer.test.ts b/tests/json-serializer.test.ts index fe7c2d37d..375bc0692 100644 --- a/tests/json-serializer.test.ts +++ b/tests/json-serializer.test.ts @@ -1,5 +1,7 @@ import { GraphQLClient } from '../src/index.js' +import type { Fetch, Variables } from '../src/types.js' import { setupMockServer } from './__helpers.js' +import { Headers, Response } from 'cross-fetch' import { createReadStream } from 'fs' import { join } from 'path' import { beforeEach, describe, expect, test, vitest } from 'vitest' @@ -13,21 +15,19 @@ const createMockSerializer = () => ({ const testData = { data: { test: { name: `test` } } } -const createMockFetch = () => (url: string) => - Promise.resolve({ - headers: new Map([[`Content-Type`, `application/json; charset=utf-8`]]), - data: testData, - text: () => { - return JSON.stringify(testData) - }, - ok: true, +const createMockFetch = (): Fetch => () => { + const response = new Response(JSON.stringify(testData), { + headers: new Headers({ + 'Content-Type': `application/json; charset=utf-8`, + }), status: 200, - url, }) + return Promise.resolve(response) +} describe(`jsonSerializer option`, () => { test(`is used for parsing response body`, async () => { - const client: GraphQLClient = new GraphQLClient(ctx.url, { + const client = new GraphQLClient(ctx.url, { jsonSerializer: createMockSerializer(), fetch: createMockFetch(), }) @@ -49,7 +49,7 @@ describe(`jsonSerializer option`, () => { } const testBatchQuery = - (expectedNumStringifyCalls: number, variables: any = simpleVariable) => + (expectedNumStringifyCalls: number, variables: Variables = simpleVariable) => async () => { await client.batchRequests([{ document, variables }]) expect(client.requestConfig.jsonSerializer?.stringify).toBeCalledTimes(expectedNumStringifyCalls)