@avantstay/graphql-ts-client 11.0.0 → 12.0.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.
@@ -76,6 +76,58 @@ const client = {
76
76
  mutations: {}
77
77
  };
78
78
  var stdin_default = client;
79
+ ",
80
+ "mjs": "import { getApiEndpointCreator } from "./endpoint";
81
+ const formatGraphQL = (query) => query;
82
+ const typesTree = {};
83
+ let verbose = false;
84
+ let headers = {};
85
+ let url = "https://sample.endpoint.com/graphl";
86
+ let retryConfig = {
87
+ max: 0,
88
+ before: void 0,
89
+ waitBeforeRetry: 0
90
+ };
91
+ let responseListeners = [];
92
+ let errorsParser = void 0;
93
+ let apiEndpoint = getApiEndpointCreator({
94
+ getClient: () => ({ url, headers, retryConfig }),
95
+ responseListeners,
96
+ maxAge: 3e4,
97
+ verbose,
98
+ typesTree,
99
+ formatGraphQL,
100
+ errorsParser
101
+ });
102
+ const client = {
103
+ addResponseListener: (listener) => responseListeners.push(listener),
104
+ setHeader: (key, value) => {
105
+ headers[key] = value;
106
+ },
107
+ setHeaders: (newHeaders) => {
108
+ headers = newHeaders;
109
+ },
110
+ setRetryConfig: (options) => {
111
+ if (!Number.isInteger(options.max) || options.max < 0) {
112
+ throw new Error("retryOptions.max should be a non-negative integer");
113
+ }
114
+ retryConfig = {
115
+ max: options.max,
116
+ waitBeforeRetry: options.waitBeforeRetry,
117
+ before: options.before
118
+ };
119
+ },
120
+ setUrl: (_url) => url = _url,
121
+ queries: {
122
+ hello: apiEndpoint("query", "hello")
123
+ },
124
+ mutations: {}
125
+ };
126
+ var stdin_default = client;
127
+ export {
128
+ client,
129
+ stdin_default as default
130
+ };
79
131
  ",
80
132
  "typings": "// noinspection TypeScriptUnresolvedVariable, ES6UnusedImports, JSUnusedLocalSymbols, TypeScriptCheckImport
81
133
  import { DeepRequired } from "ts-essentials"
@@ -39,16 +39,6 @@ export interface Book {
39
39
  author?: string
40
40
  type?: BookType
41
41
  dateCreated?: ISODate
42
- sequence?: BookSequence
43
- }
44
-
45
- /**
46
- * @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
47
- */
48
-
49
- export interface BookSequence {
50
- previousBook?: Book
51
- nextBook?: Book
52
42
  }
53
43
 
54
44
  /**
@@ -88,12 +78,6 @@ export interface BookSelection {
88
78
  author?: boolean
89
79
  type?: boolean
90
80
  dateCreated?: boolean
91
- sequence?: BookSequenceSelection
92
- }
93
-
94
- export interface BookSequenceSelection {
95
- previousBook?: BookSelection
96
- nextBook?: BookSelection
97
81
  }
98
82
 
99
83
  export interface BookSearchParamsAllOptionalSelection {
@@ -123,8 +107,9 @@ export declare const myApiClient: {
123
107
  __headers?: { [key: string]: string }
124
108
  __retry?: boolean
125
109
  __alias?: string
110
+ __url?: string
126
111
  } & BookSelection,
127
- DeepRequired<Book[]>,
112
+ Book[],
128
113
  AllEnums
129
114
  >
130
115
  booksWithOptionalParams: Endpoint<
@@ -132,9 +117,10 @@ export declare const myApiClient: {
132
117
  __headers?: { [key: string]: string }
133
118
  __retry?: boolean
134
119
  __alias?: string
120
+ __url?: string
135
121
  __args?: BooksWithOptionalParamsArgs
136
122
  } & BookSelection,
137
- DeepRequired<Book[]>,
123
+ Book[],
138
124
  AllEnums
139
125
  >
140
126
  booksWithRequiredParams: Endpoint<
@@ -142,9 +128,10 @@ export declare const myApiClient: {
142
128
  __headers?: { [key: string]: string }
143
129
  __retry?: boolean
144
130
  __alias?: string
131
+ __url?: string
145
132
  __args: BooksWithRequiredParamsArgs
146
133
  } & BookSelection,
147
- DeepRequired<Book[]>,
134
+ Book[],
148
135
  AllEnums
149
136
  >
150
137
  failingQuery: Endpoint<
@@ -152,6 +139,7 @@ export declare const myApiClient: {
152
139
  __headers?: { [key: string]: string }
153
140
  __retry?: boolean
154
141
  __alias?: string
142
+ __url?: string
155
143
  __args: FailingQueryArgs
156
144
  },
157
145
  string,
@@ -39,7 +39,6 @@ const typesTree = {
39
39
  Query: {
40
40
  get booksWithOptionalParams() {
41
41
  return {
42
- __fields: typesTree.Book,
43
42
  __args: {
44
43
  params: "BookSearchParamsAllOptional!"
45
44
  }
@@ -47,7 +46,6 @@ const typesTree = {
47
46
  },
48
47
  get booksWithRequiredParams() {
49
48
  return {
50
- __fields: typesTree.Book,
51
49
  __args: {
52
50
  params: "BookSearchParamsSomeRequired!"
53
51
  }
@@ -60,30 +58,7 @@ const typesTree = {
60
58
  }
61
59
  };
62
60
  },
63
- get booksWithoutParams() {
64
- return {
65
- __fields: typesTree.Book
66
- };
67
- }
68
- },
69
- Book: {
70
- get sequence() {
71
- return {
72
- __fields: typesTree.BookSequence
73
- };
74
- }
75
- },
76
- BookSequence: {
77
- get previousBook() {
78
- return {
79
- __fields: typesTree.Book
80
- };
81
- },
82
- get nextBook() {
83
- return {
84
- __fields: typesTree.Book
85
- };
86
- }
61
+ booksWithoutParams: {}
87
62
  }
88
63
  };
89
64
  let verbose = false;
@@ -0,0 +1,87 @@
1
+ import { getApiEndpointCreator } from "./endpoint";
2
+ import { format as formatCode } from "prettier/standalone";
3
+ import parserGraphql from "prettier/parser-graphql";
4
+ const formatGraphQL = (query) => formatCode(query, { parser: "graphql", plugins: [parserGraphql] });
5
+ const BookType = {
6
+ dolor: "DOLOR",
7
+ ipsum: "IPSUM",
8
+ sit: "SIT"
9
+ };
10
+ const typesTree = {
11
+ Query: {
12
+ get booksWithOptionalParams() {
13
+ return {
14
+ __args: {
15
+ params: "BookSearchParamsAllOptional!"
16
+ }
17
+ };
18
+ },
19
+ get booksWithRequiredParams() {
20
+ return {
21
+ __args: {
22
+ params: "BookSearchParamsSomeRequired!"
23
+ }
24
+ };
25
+ },
26
+ get failingQuery() {
27
+ return {
28
+ __args: {
29
+ id: "String!"
30
+ }
31
+ };
32
+ },
33
+ booksWithoutParams: {}
34
+ }
35
+ };
36
+ let verbose = false;
37
+ let headers = {};
38
+ let url = "http://localhost:4123/graphql";
39
+ let retryConfig = {
40
+ max: 0,
41
+ before: void 0,
42
+ waitBeforeRetry: 0
43
+ };
44
+ let responseListeners = [];
45
+ let errorsParser = void 0;
46
+ let apiEndpoint = getApiEndpointCreator({
47
+ getClient: () => ({ url, headers, retryConfig }),
48
+ responseListeners,
49
+ maxAge: 3e4,
50
+ verbose,
51
+ typesTree,
52
+ formatGraphQL,
53
+ errorsParser
54
+ });
55
+ const myApiClient = {
56
+ addResponseListener: (listener) => responseListeners.push(listener),
57
+ setHeader: (key, value) => {
58
+ headers[key] = value;
59
+ },
60
+ setHeaders: (newHeaders) => {
61
+ headers = newHeaders;
62
+ },
63
+ setRetryConfig: (options) => {
64
+ if (!Number.isInteger(options.max) || options.max < 0) {
65
+ throw new Error("retryOptions.max should be a non-negative integer");
66
+ }
67
+ retryConfig = {
68
+ max: options.max,
69
+ waitBeforeRetry: options.waitBeforeRetry,
70
+ before: options.before
71
+ };
72
+ },
73
+ setUrl: (_url) => url = _url,
74
+ queries: {
75
+ booksWithoutParams: apiEndpoint("query", "booksWithoutParams"),
76
+ booksWithOptionalParams: apiEndpoint("query", "booksWithOptionalParams"),
77
+ booksWithRequiredParams: apiEndpoint("query", "booksWithRequiredParams"),
78
+ failingQuery: apiEndpoint("query", "failingQuery")
79
+ },
80
+ mutations: {}
81
+ };
82
+ var stdin_default = myApiClient;
83
+ export {
84
+ BookType,
85
+ stdin_default as default,
86
+ myApiClient
87
+ };
package/src/endpoint.ts CHANGED
@@ -2,14 +2,10 @@ import memoize from 'moize'
2
2
  import { graphqlRequest } from './graphqlRequest'
3
3
  import { jsonToGraphQLQuery } from './jsonToGraphQLQuery'
4
4
  import { logRequest } from './logging'
5
- import {ClientConfig, Endpoint, GraphQLClientError, IResponseListener, Projection, ResponseListenerInfo} from './types'
5
+ import { ClientConfig, Endpoint, GraphQLClientError, IResponseListener, Projection, ResponseListenerInfo } from './types'
6
6
 
7
7
  const executeListeners = (listeners: IResponseListener[], data: ResponseListenerInfo) =>
8
- setTimeout(() =>
9
- listeners.forEach(runResponseListener =>
10
- runResponseListener(data)
11
- )
12
- )
8
+ setTimeout(() => listeners.forEach(runResponseListener => runResponseListener(data)))
13
9
 
14
10
  export const getApiEndpointCreator =
15
11
  (apiConfig: {
@@ -61,11 +57,11 @@ export const getApiEndpointCreator =
61
57
  shouldRetry,
62
58
  failureMode,
63
59
  queryName: alias,
64
- client: {...clientConfig, url },
60
+ client: { ...clientConfig, url },
65
61
  requestHeaders,
66
62
  query,
67
63
  variables,
68
- errorsParser: apiConfig.errorsParser
64
+ errorsParser: apiConfig.errorsParser,
69
65
  })
70
66
 
71
67
  const response = { data, warnings, headers, status, errors }
@@ -78,7 +74,7 @@ export const getApiEndpointCreator =
78
74
  })
79
75
  }
80
76
 
81
- executeListeners(apiConfig.responseListeners,{
77
+ executeListeners(apiConfig.responseListeners, {
82
78
  ...responseListenerData,
83
79
  response,
84
80
  })
@@ -125,12 +125,12 @@ describe('Generated Client', () => {
125
125
  expect(responseData?.response.errors.length).toBeGreaterThan(0)
126
126
  })
127
127
 
128
- it('should generate proper code from SDL', ()=>{
128
+ it('should generate proper code from SDL', () => {
129
129
  const sdlString = `
130
130
  type Query {
131
131
  hello: String
132
132
  }
133
- `;
134
- expect(generateTypescriptClientFromSDL(sdlString, {endpoint: 'https://sample.endpoint.com/graphl'})).toMatchSnapshot()
133
+ `
134
+ expect(generateTypescriptClientFromSDL(sdlString, { endpoint: 'https://sample.endpoint.com/graphl' })).toMatchSnapshot()
135
135
  })
136
136
  })
@@ -6,8 +6,8 @@ import * as fs from 'fs'
6
6
  import { PathLike } from 'fs'
7
7
  import {
8
8
  buildSchema,
9
- graphqlSync,
10
9
  getIntrospectionQuery,
10
+ graphqlSync,
11
11
  IntrospectionEnumType,
12
12
  IntrospectionField,
13
13
  IntrospectionInputObjectType,
@@ -15,7 +15,6 @@ import {
15
15
  IntrospectionObjectType,
16
16
  IntrospectionOutputTypeRef,
17
17
  IntrospectionType,
18
- Source,
19
18
  } from 'graphql'
20
19
  import kebabCase from 'lodash/kebabCase'
21
20
  import orderBy from 'lodash/orderBy'
@@ -25,6 +24,7 @@ import os from 'os'
25
24
  import path from 'path'
26
25
  import * as prettier from 'prettier'
27
26
  import pkg from '../package.json'
27
+ import { TypescriptClientOutput } from './types'
28
28
 
29
29
  const tempDir = fs.realpathSync(os.tmpdir())
30
30
 
@@ -314,7 +314,7 @@ type IClientOptions = {
314
314
  errorsParser?: (errors: any[]) => any
315
315
  }
316
316
 
317
- type FetchIntrospectionOptions = Omit< IClientOptions, 'output' | 'introspectionEndpoint'>
317
+ type FetchIntrospectionOptions = Omit<IClientOptions, 'output' | 'introspectionEndpoint'>
318
318
 
319
319
  function generateClientCode(types: ReadonlyArray<IntrospectionType>, options: Omit<IClientOptions, 'output'>) {
320
320
  const typesHash = md5(`${JSON.stringify(options)}__${JSON.stringify(types)}`)
@@ -322,7 +322,11 @@ function generateClientCode(types: ReadonlyArray<IntrospectionType>, options: Om
322
322
  const clientCacheFilePath = path.resolve(tempDir, clientCacheFileName)
323
323
 
324
324
  if (!options.skipCache && fs.existsSync(clientCacheFilePath)) {
325
- return JSON.parse(fs.readFileSync(clientCacheFilePath, { encoding: 'utf8' }))
325
+ const output: Partial<TypescriptClientOutput> = JSON.parse(fs.readFileSync(clientCacheFilePath, { encoding: 'utf8' }))
326
+
327
+ if (output.js && output.mjs && output.typings) {
328
+ return output as TypescriptClientOutput
329
+ }
326
330
  }
327
331
 
328
332
  const queries = (<IntrospectionObjectType>types.find(it => it.name === 'Query'))?.fields || []
@@ -472,8 +476,9 @@ function generateClientCode(types: ReadonlyArray<IntrospectionType>, options: Om
472
476
 
473
477
  export default ${clientName}`
474
478
 
475
- const output = {
479
+ const output: TypescriptClientOutput = {
476
480
  js: esbuild.transformSync(jsCode, { format: 'cjs', loader: 'js' }).code,
481
+ mjs: esbuild.transformSync(jsCode, { format: 'esm', loader: 'js' }).code,
477
482
  typings: prettier.format(typingsCode, { semi: false, parser: 'typescript' }),
478
483
  }
479
484
 
@@ -498,18 +503,18 @@ async function fetchIntrospection({ endpoint, headers }: FetchIntrospectionOptio
498
503
  'Content-Type': 'application/json',
499
504
  ...headers,
500
505
  },
506
+ timeout: 5000,
501
507
  }
502
508
  )
503
- .catch((e) => {
504
- const errorMessage = `The GraphQL introspection request failed (${endpoint})`
509
+ .catch(e => {
505
510
  if (fs.existsSync(introspectionCacheFilePath)) {
506
511
  const cachedSchema = JSON.parse(fs.readFileSync(introspectionCacheFilePath, { encoding: 'utf8' }))
507
512
  loadedFromCache = true
508
- console.warn(`Successfully restored from local cache.`)
513
+ console.warn(`Successfully restored (${endpoint}) from local cache.`)
509
514
  return { data: cachedSchema }
510
515
  } else {
511
516
  console.error(e)
512
- return Promise.reject(errorMessage)
517
+ return Promise.reject(`The GraphQL introspection request failed (${endpoint})`)
513
518
  }
514
519
  })
515
520
 
@@ -526,10 +531,11 @@ async function fetchIntrospection({ endpoint, headers }: FetchIntrospectionOptio
526
531
  return types
527
532
  }
528
533
 
529
- type Client = { typings: string; js: string }
530
-
531
- function generateClient(introspectionTypes: ReadonlyArray<IntrospectionType>, {output,...restOptions}: IClientOptions): Client {
532
- const { js, typings } = generateClientCode(introspectionTypes, restOptions)
534
+ function generateClient(
535
+ introspectionTypes: ReadonlyArray<IntrospectionType>,
536
+ { output, ...restOptions }: IClientOptions
537
+ ): TypescriptClientOutput {
538
+ const { js, mjs, typings } = generateClientCode(introspectionTypes, restOptions)
533
539
 
534
540
  if (output && typeof output === 'string') {
535
541
  const outputDir = path.dirname(output)
@@ -540,12 +546,16 @@ function generateClient(introspectionTypes: ReadonlyArray<IntrospectionType>, {o
540
546
 
541
547
  fs.writeFileSync(output.replace(/(\.(ts|js))?$/, '.d.ts'), typings, { encoding: 'utf8' })
542
548
  fs.writeFileSync(output.replace(/(\.(ts|js))?$/, '.js'), js, { encoding: 'utf8' })
549
+ fs.writeFileSync(output.replace(/(\.(ts|js))?$/, '.mjs'), mjs, { encoding: 'utf8' })
543
550
  }
544
551
 
545
- return { js, typings }
552
+ return { js, mjs, typings }
546
553
  }
547
554
 
548
- export async function generateTypescriptClient({ introspectionEndpoint, ...options }: IClientOptions): Promise<Client> {
555
+ export async function generateTypescriptClient({
556
+ introspectionEndpoint,
557
+ ...options
558
+ }: IClientOptions): Promise<TypescriptClientOutput> {
549
559
  console.log(`Generating TypeScript client (name: ${options.clientName ?? 'n/a'})`)
550
560
 
551
561
  axiosRetry(axios, { retries: 5, retryDelay: retryCount => 1000 * 2 ** retryCount })
@@ -556,14 +566,17 @@ export async function generateTypescriptClient({ introspectionEndpoint, ...optio
556
566
  })
557
567
 
558
568
  return generateClient(introspectionTypes, options)
559
-
560
569
  }
561
570
 
562
- export function generateTypescriptClientFromSDL(SDL: string, options: IClientOptions): Client {
563
- console.log(`Generating TypeScript client from SDL (name: ${options.clientName ?? 'n/a'})`);
571
+ export function generateTypescriptClientFromSDL(SDL: string, options: IClientOptions): TypescriptClientOutput {
572
+ console.log(`Generating TypeScript client from SDL (name: ${options.clientName ?? 'n/a'})`)
564
573
 
565
- const graphqlSchemaObj = buildSchema(SDL);
566
- const introspectionTypes = graphqlSync(graphqlSchemaObj, new Source(getIntrospectionQuery())).data?.__schema.types as IntrospectionType[]
574
+ const graphqlSchemaObj = buildSchema(SDL)
575
+ const introspectionResult = graphqlSync({
576
+ schema: graphqlSchemaObj,
577
+ source: getIntrospectionQuery(),
578
+ })
579
+ const introspectionTypes = (introspectionResult.data as any)?.__schema.types as IntrospectionType[]
567
580
 
568
581
  return generateClient(introspectionTypes, options)
569
582
  }
@@ -2,10 +2,10 @@ import { graphqlRequest } from './graphqlRequest'
2
2
 
3
3
  describe('GraphQLRequest', () => {
4
4
  it('Should request have proper structure', async () => {
5
- let request: any;
5
+ let request: any
6
6
 
7
7
  const mockedAxios = {
8
- post: function() {
8
+ post: function () {
9
9
  request = arguments
10
10
  return {
11
11
  status: 200,
@@ -19,7 +19,7 @@ describe('GraphQLRequest', () => {
19
19
  axios: mockedAxios,
20
20
  queryName: 'sampleQueryName',
21
21
  query: 'sampleQuery',
22
- variables: {foo:'bar',bar:'foo'},
22
+ variables: { foo: 'bar', bar: 'foo' },
23
23
  client: {
24
24
  url: 'https://whatever.com',
25
25
  headers: {},
@@ -51,7 +51,6 @@ describe('GraphQLRequest', () => {
51
51
  } as any
52
52
 
53
53
  const result = await graphqlRequest({
54
-
55
54
  failureMode: 'loud',
56
55
  axios: mockedAxios,
57
56
  queryName: 'whatever',
@@ -31,14 +31,17 @@ export function jsonToGraphQLQuery({
31
31
  parentType: kind === 'query' ? typesTree.Query : typesTree.Mutation,
32
32
  })
33
33
 
34
- const variableItems = Object.values(variablesData).reduce((variablesObj, variables) => {
35
- variables.forEach((variable, index) => {
36
- const name = variable.update(variables.length > 1 ? index : undefined)
37
- variablesObj[name] = { type: variable.type, value: variable.value }
38
- })
34
+ const variableItems = Object.values(variablesData).reduce(
35
+ (variablesObj, variables) => {
36
+ variables.forEach((variable, index) => {
37
+ const name = variable.update(variables.length > 1 ? index : undefined)
38
+ variablesObj[name] = { type: variable.type, value: variable.value }
39
+ })
39
40
 
40
- return variablesObj
41
- }, {} as Record<string, Variable>)
41
+ return variablesObj
42
+ },
43
+ {} as Record<string, Variable>
44
+ )
42
45
 
43
46
  const variablesQuery = Object.keys(variableItems).length
44
47
  ? `(${entries(variableItems)
package/src/types.ts CHANGED
@@ -24,30 +24,31 @@ type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends read
24
24
  type Primitive = Date | string | number | boolean | null | undefined
25
25
 
26
26
  // Projection is the resulting type of Selection (type generated out of a query) applied to Base (generated graphql type)
27
- export type Projection<Selection, Base, E = never> = Base extends Array<any>
28
- ? ArrayElement<Base> extends Primitive | E
29
- ? ArrayElement<Base>[]
30
- : Projection<Defined<Selection>, ArrayElement<Base>, E>[]
31
- : Base extends Primitive | E
32
- ? // Is primitive and extends undefined
33
- Selection extends undefined
34
- ? Base | undefined
35
- : Base
36
- : {
37
- [k in keyof Selection & keyof Base]: Selection[k] extends boolean
38
- ? Base[k]
39
- : Base[k] extends Array<infer A>
40
- ? Projection<Defined<Selection[k]>, A, E>[]
41
- : Projection<Defined<Selection[k]>, Base[k], E>
42
- }
27
+ export type Projection<Selection, Base, E = never> =
28
+ Base extends Array<any>
29
+ ? ArrayElement<Base> extends Primitive | E
30
+ ? ArrayElement<Base>[]
31
+ : Projection<Defined<Selection>, ArrayElement<Base>, E>[]
32
+ : Base extends Primitive | E
33
+ ? // Is primitive and extends undefined
34
+ Selection extends undefined
35
+ ? Base | undefined
36
+ : Base
37
+ : {
38
+ [k in keyof Selection & keyof Base]: Selection[k] extends boolean
39
+ ? Base[k]
40
+ : Base[k] extends Array<infer A>
41
+ ? Projection<Defined<Selection[k]>, A, E>[]
42
+ : Projection<Defined<Selection[k]>, Base[k], E>
43
+ }
43
44
 
44
45
  export type Unpacked<T> = T extends (infer U)[]
45
46
  ? U
46
47
  : T extends (...args: any[]) => infer U
47
- ? U
48
- : T extends Promise<infer U>
49
- ? U
50
- : T
48
+ ? U
49
+ : T extends Promise<infer U>
50
+ ? U
51
+ : T
51
52
 
52
53
  export type Replacement<M extends [any, any], T> = M extends any ? ([T] extends [M[0]] ? M[1] : never) : never
53
54
 
@@ -59,8 +60,8 @@ export type DeepReplace<T, Ignore, M extends [any, any]> = {
59
60
  : T[P]
60
61
  : Replacement<M, T[P]>
61
62
  : T[P] extends object
62
- ? DeepReplace<T[P], Ignore, M>
63
- : T[P]
63
+ ? DeepReplace<T[P], Ignore, M>
64
+ : T[P]
64
65
  }
65
66
 
66
67
  export type RawEndpoint<I, O, E> = <S extends I>(
@@ -98,6 +99,12 @@ export type LogInfo = {
98
99
  duration: number
99
100
  }
100
101
 
102
+ export type TypescriptClientOutput = {
103
+ js: string
104
+ mjs: string
105
+ typings: string
106
+ }
107
+
101
108
  export class GraphQLClientError extends Error {
102
109
  responseData: ResponseData
103
110