@avantstay/graphql-ts-client 11.0.0-beta.1 → 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.
- package/README.md +4 -4
- package/dist/chunk-I4QOADOI.mjs +27 -0
- package/dist/endpoint.d.ts +1 -1
- package/dist/endpoint.js +8 -3
- package/dist/endpoint.mjs +275 -0
- package/dist/index.d.ts +4 -7
- package/dist/index.js +73 -56
- package/dist/index.mjs +530 -0
- package/dist/{types-4ada662b.d.ts → types-23d2d6aa.d.ts} +6 -1
- package/package.json +44 -43
- package/src/__snapshots__/generateTypescriptClient.test.ts.snap +55 -2
- package/src/__testClient.d.ts +4 -16
- package/src/__testClient.js +1 -10
- package/src/__testClient.mjs +87 -0
- package/src/endpoint.ts +8 -9
- package/src/generateTypescriptClient.test.ts +3 -3
- package/src/generateTypescriptClient.ts +37 -22
- package/src/graphqlRequest.test.ts +3 -4
- package/src/jsonToGraphQLQuery.ts +11 -8
- package/src/types.ts +29 -22
|
@@ -29,7 +29,7 @@ __export(exports, {
|
|
|
29
29
|
client: () => client,
|
|
30
30
|
default: () => stdin_default
|
|
31
31
|
});
|
|
32
|
-
var import_endpoint = __toModule(require("
|
|
32
|
+
var import_endpoint = __toModule(require("./endpoint"));
|
|
33
33
|
const formatGraphQL = (query) => query;
|
|
34
34
|
const typesTree = {};
|
|
35
35
|
let verbose = false;
|
|
@@ -76,10 +76,62 @@ 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"
|
|
82
|
-
import { Maybe, IResponseListener, Endpoint } from "
|
|
134
|
+
import { Maybe, IResponseListener, Endpoint } from "."
|
|
83
135
|
|
|
84
136
|
// Scalars
|
|
85
137
|
export type IDate = string | Date
|
|
@@ -119,6 +171,7 @@ export declare const client: {
|
|
|
119
171
|
__headers?: { [key: string]: string }
|
|
120
172
|
__retry?: boolean
|
|
121
173
|
__alias?: string
|
|
174
|
+
__url?: string
|
|
122
175
|
},
|
|
123
176
|
string,
|
|
124
177
|
AllEnums
|
package/src/__testClient.d.ts
CHANGED
|
@@ -30,15 +30,6 @@ export interface FailingQueryArgs {
|
|
|
30
30
|
|
|
31
31
|
// Input/Output Types
|
|
32
32
|
|
|
33
|
-
/**
|
|
34
|
-
* @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
export interface Image {
|
|
38
|
-
url?: string
|
|
39
|
-
alt?: string
|
|
40
|
-
}
|
|
41
|
-
|
|
42
33
|
/**
|
|
43
34
|
* @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
|
|
44
35
|
*/
|
|
@@ -48,7 +39,6 @@ export interface Book {
|
|
|
48
39
|
author?: string
|
|
49
40
|
type?: BookType
|
|
50
41
|
dateCreated?: ISODate
|
|
51
|
-
images?: Image[]
|
|
52
42
|
}
|
|
53
43
|
|
|
54
44
|
/**
|
|
@@ -83,17 +73,11 @@ export interface Query {
|
|
|
83
73
|
|
|
84
74
|
// Selection Types
|
|
85
75
|
|
|
86
|
-
export interface ImageSelection {
|
|
87
|
-
url?: boolean
|
|
88
|
-
alt?: boolean
|
|
89
|
-
}
|
|
90
|
-
|
|
91
76
|
export interface BookSelection {
|
|
92
77
|
title?: boolean
|
|
93
78
|
author?: boolean
|
|
94
79
|
type?: boolean
|
|
95
80
|
dateCreated?: boolean
|
|
96
|
-
images?: ImageSelection
|
|
97
81
|
}
|
|
98
82
|
|
|
99
83
|
export interface BookSearchParamsAllOptionalSelection {
|
|
@@ -123,6 +107,7 @@ 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
112
|
Book[],
|
|
128
113
|
AllEnums
|
|
@@ -132,6 +117,7 @@ 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
123
|
Book[],
|
|
@@ -142,6 +128,7 @@ 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
134
|
Book[],
|
|
@@ -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,
|
package/src/__testClient.js
CHANGED
|
@@ -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,14 +58,7 @@ const typesTree = {
|
|
|
60
58
|
}
|
|
61
59
|
};
|
|
62
60
|
},
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
__fields: typesTree.Book
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
Book: {
|
|
70
|
-
images: {}
|
|
61
|
+
booksWithoutParams: {}
|
|
71
62
|
}
|
|
72
63
|
};
|
|
73
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: {
|
|
@@ -32,7 +28,10 @@ export const getApiEndpointCreator =
|
|
|
32
28
|
headers: any
|
|
33
29
|
status: any
|
|
34
30
|
}> => {
|
|
31
|
+
const clientConfig = apiConfig.getClient()
|
|
32
|
+
|
|
35
33
|
const alias = (jsonQuery as any)?.__alias ?? queryName
|
|
34
|
+
const url = (jsonQuery as any)?.__url ?? clientConfig.url
|
|
36
35
|
const shouldRetry = (jsonQuery as any)?.__retry ?? true
|
|
37
36
|
const requestHeaders = (jsonQuery as any)?.__headers ?? {}
|
|
38
37
|
const { query, variables } = jsonToGraphQLQuery({ kind, queryName, jsonQuery, typesTree: apiConfig.typesTree })
|
|
@@ -58,11 +57,11 @@ export const getApiEndpointCreator =
|
|
|
58
57
|
shouldRetry,
|
|
59
58
|
failureMode,
|
|
60
59
|
queryName: alias,
|
|
61
|
-
client:
|
|
60
|
+
client: { ...clientConfig, url },
|
|
62
61
|
requestHeaders,
|
|
63
62
|
query,
|
|
64
63
|
variables,
|
|
65
|
-
errorsParser: apiConfig.errorsParser
|
|
64
|
+
errorsParser: apiConfig.errorsParser,
|
|
66
65
|
})
|
|
67
66
|
|
|
68
67
|
const response = { data, warnings, headers, status, errors }
|
|
@@ -75,7 +74,7 @@ export const getApiEndpointCreator =
|
|
|
75
74
|
})
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
executeListeners(apiConfig.responseListeners,{
|
|
77
|
+
executeListeners(apiConfig.responseListeners, {
|
|
79
78
|
...responseListenerData,
|
|
80
79
|
response,
|
|
81
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,9 +15,8 @@ import {
|
|
|
15
15
|
IntrospectionObjectType,
|
|
16
16
|
IntrospectionOutputTypeRef,
|
|
17
17
|
IntrospectionType,
|
|
18
|
-
Source,
|
|
19
18
|
} from 'graphql'
|
|
20
|
-
import
|
|
19
|
+
import kebabCase from 'lodash/kebabCase'
|
|
21
20
|
import orderBy from 'lodash/orderBy'
|
|
22
21
|
import set from 'lodash/set'
|
|
23
22
|
import md5 from 'md5'
|
|
@@ -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
|
|
|
@@ -108,7 +108,7 @@ function gqlFieldToTypescript(
|
|
|
108
108
|
})
|
|
109
109
|
)
|
|
110
110
|
|
|
111
|
-
fieldTypeDefinition = `{ __headers?: {[key: string]: string}; __retry?: boolean; __alias?: string; __args${
|
|
111
|
+
fieldTypeDefinition = `{ __headers?: {[key: string]: string}; __retry?: boolean; __alias?: string; __url?: string; __args${
|
|
112
112
|
fieldsOnArgs.every(arg => arg.isOptional) ? '?' : ''
|
|
113
113
|
}: { ${fieldsOnArgs.map(arg => arg.code).join(', ')} }}${fieldTypeDefinition ? ` & ${fieldTypeDefinition}` : ''}`
|
|
114
114
|
}
|
|
@@ -148,6 +148,7 @@ function gqlEndpointToCode(kind: 'mutation' | 'query', endpoint: IntrospectionFi
|
|
|
148
148
|
__headers?: {[key: string]: string};
|
|
149
149
|
__retry?: boolean;
|
|
150
150
|
__alias?: string;
|
|
151
|
+
__url?: string;
|
|
151
152
|
${argsType ? `__args${argsType.optional ? '?' : ''}: ${argsType.alias}` : ''}
|
|
152
153
|
}${selectionType ? ` & ${selectionType}` : ''}`
|
|
153
154
|
|
|
@@ -313,7 +314,7 @@ type IClientOptions = {
|
|
|
313
314
|
errorsParser?: (errors: any[]) => any
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
type FetchIntrospectionOptions = Omit<
|
|
317
|
+
type FetchIntrospectionOptions = Omit<IClientOptions, 'output' | 'introspectionEndpoint'>
|
|
317
318
|
|
|
318
319
|
function generateClientCode(types: ReadonlyArray<IntrospectionType>, options: Omit<IClientOptions, 'output'>) {
|
|
319
320
|
const typesHash = md5(`${JSON.stringify(options)}__${JSON.stringify(types)}`)
|
|
@@ -321,7 +322,11 @@ function generateClientCode(types: ReadonlyArray<IntrospectionType>, options: Om
|
|
|
321
322
|
const clientCacheFilePath = path.resolve(tempDir, clientCacheFileName)
|
|
322
323
|
|
|
323
324
|
if (!options.skipCache && fs.existsSync(clientCacheFilePath)) {
|
|
324
|
-
|
|
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
|
+
}
|
|
325
330
|
}
|
|
326
331
|
|
|
327
332
|
const queries = (<IntrospectionObjectType>types.find(it => it.name === 'Query'))?.fields || []
|
|
@@ -471,8 +476,9 @@ function generateClientCode(types: ReadonlyArray<IntrospectionType>, options: Om
|
|
|
471
476
|
|
|
472
477
|
export default ${clientName}`
|
|
473
478
|
|
|
474
|
-
const output = {
|
|
479
|
+
const output: TypescriptClientOutput = {
|
|
475
480
|
js: esbuild.transformSync(jsCode, { format: 'cjs', loader: 'js' }).code,
|
|
481
|
+
mjs: esbuild.transformSync(jsCode, { format: 'esm', loader: 'js' }).code,
|
|
476
482
|
typings: prettier.format(typingsCode, { semi: false, parser: 'typescript' }),
|
|
477
483
|
}
|
|
478
484
|
|
|
@@ -497,17 +503,18 @@ async function fetchIntrospection({ endpoint, headers }: FetchIntrospectionOptio
|
|
|
497
503
|
'Content-Type': 'application/json',
|
|
498
504
|
...headers,
|
|
499
505
|
},
|
|
506
|
+
timeout: 5000,
|
|
500
507
|
}
|
|
501
508
|
)
|
|
502
|
-
.catch(
|
|
503
|
-
const errorMessage = `The GraphQL introspection request failed (${endpoint})`
|
|
509
|
+
.catch(e => {
|
|
504
510
|
if (fs.existsSync(introspectionCacheFilePath)) {
|
|
505
511
|
const cachedSchema = JSON.parse(fs.readFileSync(introspectionCacheFilePath, { encoding: 'utf8' }))
|
|
506
512
|
loadedFromCache = true
|
|
507
|
-
console.warn(`Successfully restored from local cache.`)
|
|
513
|
+
console.warn(`Successfully restored (${endpoint}) from local cache.`)
|
|
508
514
|
return { data: cachedSchema }
|
|
509
515
|
} else {
|
|
510
|
-
|
|
516
|
+
console.error(e)
|
|
517
|
+
return Promise.reject(`The GraphQL introspection request failed (${endpoint})`)
|
|
511
518
|
}
|
|
512
519
|
})
|
|
513
520
|
|
|
@@ -524,10 +531,11 @@ async function fetchIntrospection({ endpoint, headers }: FetchIntrospectionOptio
|
|
|
524
531
|
return types
|
|
525
532
|
}
|
|
526
533
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
534
|
+
function generateClient(
|
|
535
|
+
introspectionTypes: ReadonlyArray<IntrospectionType>,
|
|
536
|
+
{ output, ...restOptions }: IClientOptions
|
|
537
|
+
): TypescriptClientOutput {
|
|
538
|
+
const { js, mjs, typings } = generateClientCode(introspectionTypes, restOptions)
|
|
531
539
|
|
|
532
540
|
if (output && typeof output === 'string') {
|
|
533
541
|
const outputDir = path.dirname(output)
|
|
@@ -538,12 +546,16 @@ function generateClient(introspectionTypes: ReadonlyArray<IntrospectionType>, {o
|
|
|
538
546
|
|
|
539
547
|
fs.writeFileSync(output.replace(/(\.(ts|js))?$/, '.d.ts'), typings, { encoding: 'utf8' })
|
|
540
548
|
fs.writeFileSync(output.replace(/(\.(ts|js))?$/, '.js'), js, { encoding: 'utf8' })
|
|
549
|
+
fs.writeFileSync(output.replace(/(\.(ts|js))?$/, '.mjs'), mjs, { encoding: 'utf8' })
|
|
541
550
|
}
|
|
542
551
|
|
|
543
|
-
return { js, typings }
|
|
552
|
+
return { js, mjs, typings }
|
|
544
553
|
}
|
|
545
554
|
|
|
546
|
-
export async function generateTypescriptClient({
|
|
555
|
+
export async function generateTypescriptClient({
|
|
556
|
+
introspectionEndpoint,
|
|
557
|
+
...options
|
|
558
|
+
}: IClientOptions): Promise<TypescriptClientOutput> {
|
|
547
559
|
console.log(`Generating TypeScript client (name: ${options.clientName ?? 'n/a'})`)
|
|
548
560
|
|
|
549
561
|
axiosRetry(axios, { retries: 5, retryDelay: retryCount => 1000 * 2 ** retryCount })
|
|
@@ -554,14 +566,17 @@ export async function generateTypescriptClient({ introspectionEndpoint, ...optio
|
|
|
554
566
|
})
|
|
555
567
|
|
|
556
568
|
return generateClient(introspectionTypes, options)
|
|
557
|
-
|
|
558
569
|
}
|
|
559
570
|
|
|
560
|
-
export function generateTypescriptClientFromSDL(SDL: string, options: IClientOptions):
|
|
561
|
-
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'})`)
|
|
562
573
|
|
|
563
|
-
const graphqlSchemaObj = buildSchema(SDL)
|
|
564
|
-
const
|
|
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[]
|
|
565
580
|
|
|
566
581
|
return generateClient(introspectionTypes, options)
|
|
567
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',
|
|
@@ -23,7 +23,7 @@ export function jsonToGraphQLQuery({
|
|
|
23
23
|
}) {
|
|
24
24
|
const variablesData = {} as ExtractedVariables
|
|
25
25
|
const alias = jsonQuery.__alias
|
|
26
|
-
const newJsonQuery = cloneDeep(omit(jsonQuery, ['__alias', '__headers']))
|
|
26
|
+
const newJsonQuery = cloneDeep(omit(jsonQuery, ['__alias', '__headers', '__url']))
|
|
27
27
|
|
|
28
28
|
extractVariables({
|
|
29
29
|
jsonQuery: { [queryName]: newJsonQuery },
|
|
@@ -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(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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> =
|
|
28
|
-
|
|
29
|
-
? ArrayElement<Base>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
|