@avantstay/graphql-ts-client 10.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,547 @@
1
+ import axios from 'axios'
2
+ import axiosRetry from 'axios-retry'
3
+ import Case from 'case'
4
+ import * as esbuild from 'esbuild'
5
+ import * as fs from 'fs'
6
+ import { PathLike } from 'fs'
7
+ import {
8
+ getIntrospectionQuery,
9
+ IntrospectionEnumType,
10
+ IntrospectionField,
11
+ IntrospectionInputObjectType,
12
+ IntrospectionInputTypeRef,
13
+ IntrospectionObjectType,
14
+ IntrospectionOutputTypeRef,
15
+ IntrospectionType,
16
+ } from 'graphql'
17
+ import { kebabCase } from 'lodash'
18
+ import orderBy from 'lodash/orderBy'
19
+ import set from 'lodash/set'
20
+ import md5 from 'md5'
21
+ import os from 'os'
22
+ import path from 'path'
23
+ import * as prettier from 'prettier'
24
+ import pkg from '../package.json'
25
+
26
+ const tempDir = fs.realpathSync(os.tmpdir())
27
+
28
+ const graphqlTsClientPath = process.env.GQL_CLIENT_DIST_PATH || 'graphql-ts-client/dist'
29
+
30
+ function gqlScalarToTypescript(gqlType: string) {
31
+ if (/(int|long|double|decimal|float)/i.test(gqlType)) return 'number'
32
+ if (/boolean/i.test(gqlType)) return 'boolean'
33
+ if (/String/i.test(gqlType)) return 'string'
34
+
35
+ return gqlType
36
+ }
37
+
38
+ function gqlTypeToTypescript(
39
+ gqlType: IntrospectionOutputTypeRef,
40
+ { required = false, isInput = false, selection = false } = {}
41
+ ): string {
42
+ if (!gqlType) return ''
43
+
44
+ const maybeWrapped = (it: string) => (required || selection ? it : `Maybe<${it}>`)
45
+
46
+ // noinspection SuspiciousTypeOfGuard
47
+ if (typeof gqlType === 'string') {
48
+ return maybeWrapped(gqlType)
49
+ }
50
+
51
+ if (gqlType.kind.endsWith('OBJECT')) {
52
+ return maybeWrapped((gqlType as any).name + (selection ? 'Selection' : ''))
53
+ }
54
+
55
+ if (gqlType.kind === 'NON_NULL') {
56
+ return `${gqlTypeToTypescript(gqlType.ofType, {
57
+ isInput,
58
+ required: true,
59
+ selection,
60
+ })}`
61
+ }
62
+
63
+ if (gqlType.kind === 'LIST') {
64
+ return maybeWrapped(
65
+ `${gqlTypeToTypescript(gqlType.ofType, {
66
+ isInput,
67
+ required: true,
68
+ selection,
69
+ })}${selection ? '' : '[]'}`
70
+ )
71
+ }
72
+
73
+ if (selection) {
74
+ return ''
75
+ }
76
+
77
+ if (gqlType.kind === 'ENUM' && gqlType.name) {
78
+ return maybeWrapped(gqlType.name)
79
+ }
80
+
81
+ if (gqlType.kind === 'SCALAR') {
82
+ return maybeWrapped(gqlScalarToTypescript(gqlType.name))
83
+ }
84
+
85
+ return ''
86
+ }
87
+
88
+ function gqlFieldToTypescript(
89
+ field: IntrospectionField,
90
+ { isInput, selection, defaultValue }: { defaultValue?: any; isInput: boolean; selection: boolean }
91
+ ) {
92
+ let fieldTypeDefinition = gqlTypeToTypescript(field.type, {
93
+ isInput,
94
+ selection,
95
+ })
96
+
97
+ fieldTypeDefinition = `${fieldTypeDefinition}`
98
+
99
+ if (selection && field.args && field.args.length) {
100
+ let fieldsOnArgs = field.args.map(arg =>
101
+ gqlFieldToTypescript(arg as unknown as IntrospectionField, {
102
+ defaultValue: arg.defaultValue,
103
+ isInput: true,
104
+ selection: false,
105
+ })
106
+ )
107
+
108
+ fieldTypeDefinition = `{ __headers?: {[key: string]: string}; __retry?: boolean; __alias?: string; __args${
109
+ fieldsOnArgs.every(arg => arg.isOptional) ? '?' : ''
110
+ }: { ${fieldsOnArgs.map(arg => arg.code).join(', ')} }}${fieldTypeDefinition ? ` & ${fieldTypeDefinition}` : ''}`
111
+ }
112
+
113
+ const isOptional = defaultValue || selection || fieldTypeDefinition.startsWith('Maybe')
114
+ const rawType = fieldTypeDefinition || (selection && 'boolean')
115
+ const wrappedType = isOptional ? (rawType as string).replace(/Maybe<(.+?)>/, '$1') : rawType
116
+
117
+ return {
118
+ isOptional: isOptional,
119
+ code: `${field.name}${isOptional ? '?:' : ':'} ${wrappedType}`,
120
+ }
121
+ }
122
+
123
+ function getArgsType(endpoint: IntrospectionField) {
124
+ const fieldsOnArgs = endpoint.args.map(arg =>
125
+ gqlFieldToTypescript(arg as unknown as IntrospectionField, {
126
+ defaultValue: arg.defaultValue,
127
+ isInput: true,
128
+ selection: false,
129
+ })
130
+ )
131
+ const argsType = `{ ${fieldsOnArgs.map(arg => arg.code).join(', ')} }`
132
+ const argsFullyOptional = fieldsOnArgs.every(arg => arg.isOptional)
133
+
134
+ return { alias: Case.pascal(`${endpoint.name}Args`), type: argsType, optional: argsFullyOptional }
135
+ }
136
+
137
+ function gqlEndpointToCode(kind: 'mutation' | 'query', endpoint: IntrospectionField, codeOutputType: 'ts' | 'js'): string {
138
+ const selectionType = gqlTypeToTypescript(endpoint.type, {
139
+ isInput: false,
140
+ selection: true,
141
+ })
142
+
143
+ const argsType = endpoint.args && endpoint.args.length ? getArgsType(endpoint) : null
144
+ const inputType = `{
145
+ __headers?: {[key: string]: string};
146
+ __retry?: boolean;
147
+ __alias?: string;
148
+ ${argsType ? `__args${argsType.optional ? '?' : ''}: ${argsType.alias}` : ''}
149
+ }${selectionType ? ` & ${selectionType}` : ''}`
150
+
151
+ const outputType = gqlTypeToTypescript(endpoint.type, { required: true })
152
+ const wrappedOutputType = /^(string|number|boolean)$/.test(outputType) ? outputType : `DeepRequired<${outputType}>`
153
+
154
+ return codeOutputType === 'ts'
155
+ ? `${endpoint.name}: Endpoint<${inputType}, ${wrappedOutputType}, AllEnums>`
156
+ : `${endpoint.name}: apiEndpoint('${kind}', '${endpoint.name}')`
157
+ }
158
+
159
+ function gqlSchemaToCode(
160
+ gqlType: any | IntrospectionObjectType | IntrospectionInputObjectType | IntrospectionEnumType,
161
+ { selection = false, outputType }: { selection: boolean; outputType: 'js' | 'ts' }
162
+ ) {
163
+ const rawKind = gqlType.kind || gqlType.type
164
+
165
+ if (rawKind === 'SCALAR') {
166
+ return outputType === 'ts' ? `export declare type ${gqlType.name} = ${/date/i.test(gqlType.name) ? 'IDate' : 'string'}` : ''
167
+ }
168
+
169
+ if (rawKind === 'ENUM')
170
+ return outputType === 'ts'
171
+ ? `
172
+ export declare enum ${gqlType.name} {
173
+ ${orderBy(gqlType.enumValues, 'name')
174
+ .map((_: any) => `${Case.camel(_.name)} = '${_.name}'`)
175
+ .join(',\n ')}
176
+ }`
177
+ : `export const ${gqlType.name} = {${orderBy(gqlType.enumValues, 'name')
178
+ .map((_: any) => `${Case.camel(_.name)}: '${_.name}'`)
179
+ .join(',\n ')}}`
180
+
181
+ const fields = (gqlType.fields && gqlType.fields) || (gqlType.inputFields && gqlType.inputFields) || []
182
+
183
+ return outputType === 'ts'
184
+ ? `
185
+ export interface ${gqlType.name}${selection ? 'Selection' : ''} {
186
+ ${fields
187
+ .map(
188
+ (_: any) =>
189
+ gqlFieldToTypescript(_, {
190
+ isInput: gqlType.kind === 'INPUT_OBJECT',
191
+ selection,
192
+ }).code
193
+ )
194
+ .join(',\n ')}
195
+ }`
196
+ : ''
197
+ }
198
+
199
+ function getGraphQLInputType(type: IntrospectionInputTypeRef): string {
200
+ switch (type.kind) {
201
+ case 'NON_NULL':
202
+ return `${getGraphQLInputType(type.ofType)}!`
203
+
204
+ case 'SCALAR':
205
+ case 'INPUT_OBJECT':
206
+ case 'ENUM':
207
+ return type.name
208
+
209
+ case 'LIST':
210
+ return `[${getGraphQLInputType(type.ofType)}]`
211
+
212
+ default:
213
+ return ''
214
+ }
215
+ }
216
+
217
+ function getGraphQLOutputType(type: IntrospectionOutputTypeRef): string {
218
+ switch (type.kind) {
219
+ case 'LIST':
220
+ return `${getGraphQLOutputType(type.ofType)}[]`
221
+
222
+ case 'NON_NULL':
223
+ return getGraphQLOutputType(type.ofType)
224
+
225
+ case 'OBJECT':
226
+ return type.name
227
+
228
+ default:
229
+ return ''
230
+ }
231
+ }
232
+
233
+ function getTypesTreeCode(types: IntrospectionObjectType[]) {
234
+ const typesTree = {}
235
+
236
+ types.forEach(type =>
237
+ type.fields
238
+ .filter(_ => _.args && _.args.length)
239
+ .forEach(_ =>
240
+ _.args.forEach(a => {
241
+ let inputType = getGraphQLInputType(a.type)
242
+ if (inputType) {
243
+ set(typesTree, `${type.name}.${_.name}.__args.${a.name}`, inputType)
244
+ }
245
+ })
246
+ )
247
+ )
248
+
249
+ types.forEach(t =>
250
+ t.fields.forEach(f => {
251
+ let outputType = getGraphQLOutputType(f.type)
252
+ if (outputType) {
253
+ set(typesTree, `${t.name}.${f.name}.__shape`, outputType)
254
+ }
255
+ })
256
+ )
257
+
258
+ return `
259
+ const typesTree = {
260
+ ${Object.entries(typesTree)
261
+ .map(([key, value]) => {
262
+ let entryCode = Object.entries(value as any)
263
+ .map(([k, v]: any) => {
264
+ const cleanShapeType = v.__shape && v.__shape.replace(/[\[\]!?]/g, '')
265
+ const fieldsCode =
266
+ v.__shape && typesTree.hasOwnProperty(cleanShapeType) ? `__fields: typesTree.${cleanShapeType},` : ''
267
+
268
+ const argsCode = v.__args
269
+ ? `__args: {
270
+ ${Object.entries(v.__args)
271
+ .map(([k, v]) => `${k}: '${v}'`)
272
+ .join(',\n')}
273
+ }`
274
+ : ''
275
+
276
+ return fieldsCode || argsCode
277
+ ? `get ${k}() {
278
+ return {
279
+ ${fieldsCode}
280
+ ${argsCode}
281
+ }
282
+ }`
283
+ : `${k}: {}`
284
+ })
285
+ .filter(Boolean)
286
+ .join(',\n')
287
+ .trim()
288
+ return (
289
+ entryCode &&
290
+ `
291
+ ${key}: {
292
+ ${entryCode}
293
+ }`
294
+ )
295
+ })
296
+ .filter(Boolean)
297
+ .join(',\n')}
298
+ }
299
+ `
300
+ }
301
+
302
+ type IClientOptions = {
303
+ output?: PathLike
304
+ clientName?: string
305
+ headers?: { [key: string]: string }
306
+ introspectionEndpoint?: string
307
+ endpoint: string
308
+ verbose?: boolean
309
+ formatGraphQL?: boolean
310
+ skipCache?: boolean
311
+ errorsParser?: (errors: any[]) => any
312
+ }
313
+
314
+ type FetchIntrospectionOptions = Omit< IClientOptions, 'output' | 'introspectionEndpoint'>
315
+
316
+ function generateClientCode(types: ReadonlyArray<IntrospectionType>, options: Omit<IClientOptions, 'output'>) {
317
+ const typesHash = md5(`${JSON.stringify(options)}__${JSON.stringify(types)}`)
318
+ const clientCacheFileName = `gql-ts-client__client__${typesHash}__${pkg.version}.json`
319
+ const clientCacheFilePath = path.resolve(tempDir, clientCacheFileName)
320
+
321
+ if (!options.skipCache && fs.existsSync(clientCacheFilePath)) {
322
+ return JSON.parse(fs.readFileSync(clientCacheFilePath, { encoding: 'utf8' }))
323
+ }
324
+
325
+ const queries = (<IntrospectionObjectType>types.find(it => it.name === 'Query'))?.fields || []
326
+ const mutations = (<IntrospectionObjectType>types.find(it => it.name === 'Mutation'))?.fields || []
327
+ const enums = types.filter(it => it.kind === 'ENUM' && !it.name.startsWith('__')) as IntrospectionEnumType[]
328
+ const scalars = types.filter(
329
+ it => it.kind === 'SCALAR' && !/decimal|int|float|string|long|boolean/i.test(it.name)
330
+ ) as IntrospectionEnumType[]
331
+ const objectTypes = types.filter(it => ['OBJECT', 'INPUT_OBJECT'].includes(it.kind) && !it.name.startsWith('__')) as (
332
+ | IntrospectionObjectType
333
+ | IntrospectionInputObjectType
334
+ )[]
335
+
336
+ const forInputExtraction = types.filter(
337
+ it => !it.name.startsWith('__') && ['OBJECT'].includes(it.kind)
338
+ ) as IntrospectionObjectType[]
339
+
340
+ const clientName = options.clientName || 'client'
341
+
342
+ // language=JavaScript
343
+ const jsCode = `
344
+ // noinspection TypeScriptUnresolvedVariable, ES6UnusedImports, JSUnusedLocalSymbols
345
+ import { getApiEndpointCreator } from '${graphqlTsClientPath}/endpoint'
346
+
347
+ ${
348
+ options.formatGraphQL || options.verbose
349
+ ? `
350
+ import { format as formatCode } from "prettier/standalone"
351
+ import parserGraphql from "prettier/parser-graphql"
352
+
353
+ const formatGraphQL = (query) => formatCode(query, {parser: 'graphql', plugins: [parserGraphql]})`
354
+ : `
355
+ const formatGraphQL = (query) => query`
356
+ }
357
+
358
+ // Enums
359
+ ${enums.map(it => gqlSchemaToCode(it, { selection: false, outputType: 'js' })).join('\n')}
360
+
361
+ // Schema Resolution Tree
362
+ ${getTypesTreeCode(forInputExtraction)}
363
+
364
+ let verbose = ${Boolean(options.verbose)}
365
+ let headers = {}
366
+ let url = '${options.endpoint}'
367
+ let retryConfig = {
368
+ max: 0,
369
+ before: undefined,
370
+ waitBeforeRetry: 0
371
+ }
372
+ let responseListeners = []
373
+ let errorsParser = ${options.errorsParser}
374
+ // noinspection JSUnusedLocalSymbols
375
+ let apiEndpoint = getApiEndpointCreator({
376
+ getClient: () => ({ url, headers, retryConfig }),
377
+ responseListeners,
378
+ maxAge: 30000,
379
+ verbose,
380
+ typesTree,
381
+ formatGraphQL,
382
+ errorsParser
383
+ })
384
+
385
+ export const ${clientName} = {
386
+ addResponseListener: (listener) => responseListeners.push(
387
+ listener),
388
+ setHeader: (key, value) => {
389
+ headers[key] = value
390
+ },
391
+ setHeaders: (newHeaders) => {
392
+ headers = newHeaders
393
+ },
394
+ setRetryConfig: (options) => {
395
+ if (!Number.isInteger(options.max) || options.max < 0) {
396
+ throw new Error('retryOptions.max should be a non-negative integer')
397
+ }
398
+
399
+ retryConfig = {
400
+ max: options.max,
401
+ waitBeforeRetry: options.waitBeforeRetry,
402
+ before: options.before
403
+ }
404
+ },
405
+ setUrl: (_url) => url = _url,
406
+ queries: {
407
+ ${queries.map(query => gqlEndpointToCode('query', query, 'js')).join(',\n')}
408
+ },
409
+ mutations: {
410
+ ${mutations.map(mutation => gqlEndpointToCode('mutation', mutation, 'js')).join(',\n')}
411
+ }
412
+ }
413
+
414
+ export default ${clientName}`
415
+
416
+ // language=TypeScript
417
+ const typingsCode = `
418
+ // noinspection TypeScriptUnresolvedVariable, ES6UnusedImports, JSUnusedLocalSymbols, TypeScriptCheckImport
419
+ import { DeepRequired } from 'ts-essentials'
420
+ import { Maybe, IResponseListener, Endpoint } from '${graphqlTsClientPath}'
421
+
422
+ // Scalars
423
+ export type IDate = string | Date
424
+ ${scalars.map(it => gqlSchemaToCode(it, { selection: false, outputType: 'ts' })).join('\n')}
425
+
426
+ // Enums
427
+ ${enums.map(it => gqlSchemaToCode(it, { selection: false, outputType: 'ts' })).join('\n')}
428
+
429
+ type AllEnums = ${enums.length ? enums.map(it => it.name).join(' | ') : 'never'}
430
+
431
+ // Args
432
+ ${[...queries, ...mutations]
433
+ .map(query => {
434
+ const argsType = getArgsType(query)
435
+ return `export interface ${argsType.alias} ${argsType.type}`
436
+ })
437
+ .join('\n')}
438
+
439
+ // Input/Output Types
440
+ ${objectTypes
441
+ .map(
442
+ it => `
443
+ /**
444
+ * @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
445
+ */
446
+ ${gqlSchemaToCode(it, { selection: false, outputType: 'ts' })}`
447
+ )
448
+ .join('\n')}
449
+
450
+ // Selection Types
451
+ ${objectTypes
452
+ .filter(it => it.name !== 'Query')
453
+ .map(it => gqlSchemaToCode(it, { selection: true, outputType: 'ts' }))
454
+ .join('\n')}
455
+
456
+ export declare const ${clientName}: {
457
+ addResponseListener: (listener: IResponseListener) => void
458
+ setHeader: (key: string, value: string) => void
459
+ setHeaders: (newHeaders: { [k: string]: string }) => void,
460
+ setUrl: (url: string) => void,
461
+ setRetryConfig: (options: { max: number, waitBeforeRetry?: number, before?: IResponseListener }) => void
462
+ queries: {
463
+ ${queries.map(q => gqlEndpointToCode('query', q, 'ts')).join(',\n')}
464
+ },
465
+ mutations: {
466
+ ${mutations.map(q => gqlEndpointToCode('mutation', q, 'ts')).join(',\n')}
467
+ }
468
+ }
469
+
470
+ export default ${clientName}`
471
+
472
+ const output = {
473
+ js: esbuild.transformSync(jsCode, { format: 'cjs', loader: 'js' }).code,
474
+ typings: prettier.format(typingsCode, { semi: false, parser: 'typescript' }),
475
+ }
476
+
477
+ fs.writeFileSync(clientCacheFilePath, JSON.stringify(output))
478
+
479
+ return output
480
+ }
481
+
482
+ async function fetchIntrospection({ endpoint, headers }: FetchIntrospectionOptions) {
483
+ const introspectionCacheFileName = `gql-ts-client__introspection__${kebabCase(endpoint)}.json`
484
+ const introspectionCacheFilePath = path.resolve(tempDir, introspectionCacheFileName)
485
+
486
+ let loadedFromCache = false
487
+ let types: any
488
+
489
+ const { data } = await axios
490
+ .post(
491
+ endpoint,
492
+ { query: getIntrospectionQuery() },
493
+ {
494
+ headers: {
495
+ 'Content-Type': 'application/json',
496
+ ...headers,
497
+ },
498
+ }
499
+ )
500
+ .catch(() => {
501
+ const errorMessage = `The GraphQL introspection request failed (${endpoint})`
502
+ if (fs.existsSync(introspectionCacheFilePath)) {
503
+ const cachedSchema = JSON.parse(fs.readFileSync(introspectionCacheFilePath, { encoding: 'utf8' }))
504
+ loadedFromCache = true
505
+ console.warn(`Successfully restored from local cache.`)
506
+ return { data: cachedSchema }
507
+ } else {
508
+ return Promise.reject(errorMessage)
509
+ }
510
+ })
511
+
512
+ types = data.data.__schema.types
513
+
514
+ if (!loadedFromCache) {
515
+ console.log(`Successfully loaded GraphQL introspection from ${endpoint}`)
516
+
517
+ fs.writeFileSync(introspectionCacheFilePath, JSON.stringify(data), {
518
+ encoding: 'utf8',
519
+ })
520
+ }
521
+
522
+ return types
523
+ }
524
+
525
+ export async function generateTypescriptClient({ introspectionEndpoint, output, ...options }: IClientOptions): Promise<{ typings: string; js: string }> {
526
+ axiosRetry(axios, { retries: 5, retryDelay: retryCount => 1000 * 2 ** retryCount })
527
+
528
+ const types = await fetchIntrospection({
529
+ ...options,
530
+ endpoint: introspectionEndpoint || options.endpoint,
531
+ })
532
+
533
+ const { js, typings } = generateClientCode(types, options)
534
+
535
+ if (output && typeof output === 'string') {
536
+ const outputDir = path.dirname(output)
537
+
538
+ if (!fs.existsSync(outputDir)) {
539
+ fs.mkdirSync(outputDir, { recursive: true })
540
+ }
541
+
542
+ fs.writeFileSync(output.replace(/(\.(ts|js))?$/, '.d.ts'), typings, { encoding: 'utf8' })
543
+ fs.writeFileSync(output.replace(/(\.(ts|js))?$/, '.js'), js, { encoding: 'utf8' })
544
+ }
545
+
546
+ return { js, typings }
547
+ }
@@ -0,0 +1,103 @@
1
+ import { graphqlRequest } from './graphqlRequest'
2
+
3
+ describe('GraphQLRequest', () => {
4
+ it('Should request have proper structure', async () => {
5
+ let request: any;
6
+
7
+ const mockedAxios = {
8
+ post: function() {
9
+ request = arguments
10
+ return {
11
+ status: 200,
12
+ }
13
+ },
14
+ } as any
15
+
16
+ const result = await graphqlRequest({
17
+ shouldRetry: false,
18
+ failureMode: 'loud',
19
+ axios: mockedAxios,
20
+ queryName: 'sampleQueryName',
21
+ query: 'sampleQuery',
22
+ variables: {foo:'bar',bar:'foo'},
23
+ client: {
24
+ url: 'https://whatever.com',
25
+ headers: {},
26
+ retryConfig: {
27
+ max: 0,
28
+ before: () => void [1],
29
+ },
30
+ },
31
+ }).catch(err => err)
32
+
33
+ expect(result.status).toBe(200)
34
+ expect(request).toMatchSnapshot()
35
+ expect(request).toBeDefined()
36
+ const [url, data, config] = request
37
+ expect(url).toEqual('https://whatever.com')
38
+ expect(config).toBeDefined()
39
+ expect(data.operationName).toEqual('sampleQueryName')
40
+ expect(data.hasOwnProperty('query')).toEqual(true)
41
+ expect(data.hasOwnProperty('variables')).toEqual(true)
42
+ })
43
+
44
+ it('Should retry as many times as configured properly running a "before" hook', async () => {
45
+ let retryCount = 0
46
+ const maxRetrials = 2
47
+ const mockedAxios = {
48
+ post: async () => ({
49
+ status: retryCount < maxRetrials ? 500 : 200,
50
+ }),
51
+ } as any
52
+
53
+ const result = await graphqlRequest({
54
+
55
+ failureMode: 'loud',
56
+ axios: mockedAxios,
57
+ queryName: 'whatever',
58
+ query: 'whatever',
59
+ variables: {},
60
+ requestHeaders: {},
61
+ client: {
62
+ url: 'https://whatever.com',
63
+ headers: {},
64
+ retryConfig: {
65
+ max: maxRetrials,
66
+ before: () => void [retryCount++],
67
+ },
68
+ },
69
+ })
70
+
71
+ expect(retryCount).toBe(2)
72
+ expect(result.status).toBe(200)
73
+ })
74
+
75
+ it('Should ignore default retrying config explicitly asking to skip retrials ', async () => {
76
+ let retryCount = 0
77
+ const maxRetrials = 2
78
+ const mockedAxios = {
79
+ post: async () => ({
80
+ status: retryCount < maxRetrials ? 500 : 200,
81
+ }),
82
+ } as any
83
+
84
+ const result = await graphqlRequest({
85
+ shouldRetry: false,
86
+ failureMode: 'loud',
87
+ axios: mockedAxios,
88
+ queryName: 'whatever',
89
+ query: 'whatever',
90
+ variables: {},
91
+ client: {
92
+ url: 'https://whatever.com',
93
+ headers: {},
94
+ retryConfig: {
95
+ max: maxRetrials,
96
+ before: () => void [retryCount++],
97
+ },
98
+ },
99
+ }).catch(err => err)
100
+
101
+ expect(result).toBeInstanceOf(Error)
102
+ })
103
+ })