@avantstay/graphql-ts-client 10.7.0 → 10.9.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 +63 -51
- package/dist/index.mjs +527 -0
- package/dist/types-23d2d6aa.d.ts +75 -0
- package/dist/types-4ada662b.d.ts +70 -0
- package/package.json +43 -42
- package/src/__snapshots__/generateTypescriptClient.test.ts.snap +3 -2
- package/src/__testClient.d.ts +164 -0
- package/src/__testClient.js +135 -0
- package/src/endpoint.ts +8 -9
- package/src/generateTypescriptClient.test.ts +3 -3
- package/src/generateTypescriptClient.ts +32 -20
- package/src/graphqlRequest.test.ts +3 -4
- package/src/jsonToGraphQLQuery.ts +11 -8
- package/src/types.ts +32 -22
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GraphQLClientError
|
|
3
|
+
} from "./chunk-I4QOADOI.mjs";
|
|
4
|
+
|
|
5
|
+
// src/generateTypescriptClient.ts
|
|
6
|
+
import axios from "axios";
|
|
7
|
+
import axiosRetry from "axios-retry";
|
|
8
|
+
import Case from "case";
|
|
9
|
+
import * as esbuild from "esbuild";
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import {
|
|
12
|
+
buildSchema,
|
|
13
|
+
getIntrospectionQuery,
|
|
14
|
+
graphqlSync,
|
|
15
|
+
Source
|
|
16
|
+
} from "graphql";
|
|
17
|
+
import kebabCase from "lodash/kebabCase";
|
|
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
|
+
|
|
25
|
+
// package.json
|
|
26
|
+
var package_default = {
|
|
27
|
+
name: "@avantstay/graphql-ts-client",
|
|
28
|
+
version: "10.9.0",
|
|
29
|
+
description: "GraphQL Typescript Client Generator",
|
|
30
|
+
homepage: "https://github.com/avantstay/graphql-ts-client",
|
|
31
|
+
bugs: {
|
|
32
|
+
url: "https://github.com/avantstay/graphql-ts-client/issues"
|
|
33
|
+
},
|
|
34
|
+
repository: {
|
|
35
|
+
type: "git",
|
|
36
|
+
url: "git+https://github.com/avantstay/graphql-ts-client.git"
|
|
37
|
+
},
|
|
38
|
+
license: "MIT",
|
|
39
|
+
author: "Wellington Guimaraes",
|
|
40
|
+
contributors: [
|
|
41
|
+
"Felipe Pinheiro <felipe.pinheiro.90@hotmail.com>"
|
|
42
|
+
],
|
|
43
|
+
exports: {
|
|
44
|
+
".": {
|
|
45
|
+
import: "./dist/index.mjs",
|
|
46
|
+
require: "./dist/index.js",
|
|
47
|
+
types: "./dist/index.d.ts"
|
|
48
|
+
},
|
|
49
|
+
"./dist/endpoint": {
|
|
50
|
+
import: "./dist/endpoint.mjs",
|
|
51
|
+
require: "./dist/endpoint.js",
|
|
52
|
+
types: "./dist/endpoint.d.ts"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
main: "dist/index.js",
|
|
56
|
+
module: "dist/index.mjs",
|
|
57
|
+
typings: "dist/index.d.ts",
|
|
58
|
+
files: [
|
|
59
|
+
"dist",
|
|
60
|
+
"src"
|
|
61
|
+
],
|
|
62
|
+
scripts: {
|
|
63
|
+
build: "tsup src/index.ts src/endpoint.ts --dts --format cjs,esm",
|
|
64
|
+
prepublishOnly: "yarn build",
|
|
65
|
+
test: "cross-env GQL_CLIENT_DIST_PATH='.' jest --watch"
|
|
66
|
+
},
|
|
67
|
+
dependencies: {
|
|
68
|
+
"@types/md5": "^2.3.2",
|
|
69
|
+
axios: "^0.24.0",
|
|
70
|
+
"axios-retry": "^3.2.4",
|
|
71
|
+
case: "^1.6.3",
|
|
72
|
+
esbuild: "^0.13.14",
|
|
73
|
+
graphql: "^15.6.0",
|
|
74
|
+
lodash: "^4.17.21",
|
|
75
|
+
md5: "^2.3.0",
|
|
76
|
+
moize: "^6.1.0",
|
|
77
|
+
prettier: "^2.5.1",
|
|
78
|
+
"temp-dir": "^3.0.0",
|
|
79
|
+
"ts-essentials": "^8.1.0"
|
|
80
|
+
},
|
|
81
|
+
devDependencies: {
|
|
82
|
+
"@babel/preset-env": "^7.20.2",
|
|
83
|
+
"@babel/preset-typescript": "^7.18.6",
|
|
84
|
+
"@jest/globals": "^29.3.1",
|
|
85
|
+
"@size-limit/preset-small-lib": "^5.0.4",
|
|
86
|
+
"@swc/core": "^1.3.22",
|
|
87
|
+
"@types/graphql": "^14.5.0",
|
|
88
|
+
"@types/jest": "^29.2.4",
|
|
89
|
+
"@types/lodash": "^4.14.177",
|
|
90
|
+
"@types/node": "^16.11.12",
|
|
91
|
+
"@types/prettier": "^2.4.2",
|
|
92
|
+
"apollo-server": "^3.11.1",
|
|
93
|
+
concurrently: "^7.6.0",
|
|
94
|
+
"cross-env": "^7.0.3",
|
|
95
|
+
jest: "^29.3.1",
|
|
96
|
+
"ts-jest": "^29.0.3",
|
|
97
|
+
"ts-node": "^10.9.1",
|
|
98
|
+
tslib: "^2.3.1",
|
|
99
|
+
tsup: "^6.5.0",
|
|
100
|
+
typescript: "^4.9.4"
|
|
101
|
+
},
|
|
102
|
+
peerDependencies: {},
|
|
103
|
+
engines: {
|
|
104
|
+
node: ">=12"
|
|
105
|
+
},
|
|
106
|
+
publishConfig: {
|
|
107
|
+
"@avantstay:registry": "https://registry.npmjs.org/"
|
|
108
|
+
},
|
|
109
|
+
"size-limit": [
|
|
110
|
+
{
|
|
111
|
+
path: "dist/graphql-ts-client.cjs.production.min.js",
|
|
112
|
+
limit: "10 KB"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
path: "dist/graphql-ts-client.esm.js",
|
|
116
|
+
limit: "10 KB"
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/generateTypescriptClient.ts
|
|
122
|
+
var tempDir = fs.realpathSync(os.tmpdir());
|
|
123
|
+
var graphqlTsClientPath = process.env.GQL_CLIENT_DIST_PATH || "@avantstay/graphql-ts-client/dist";
|
|
124
|
+
function gqlScalarToTypescript(gqlType) {
|
|
125
|
+
if (/(int|long|double|decimal|float)/i.test(gqlType))
|
|
126
|
+
return "number";
|
|
127
|
+
if (/boolean/i.test(gqlType))
|
|
128
|
+
return "boolean";
|
|
129
|
+
if (/String/i.test(gqlType))
|
|
130
|
+
return "string";
|
|
131
|
+
return gqlType;
|
|
132
|
+
}
|
|
133
|
+
function gqlTypeToTypescript(gqlType, { required = false, isInput = false, selection = false } = {}) {
|
|
134
|
+
if (!gqlType)
|
|
135
|
+
return "";
|
|
136
|
+
const maybeWrapped = (it) => required || selection ? it : `Maybe<${it}>`;
|
|
137
|
+
if (typeof gqlType === "string") {
|
|
138
|
+
return maybeWrapped(gqlType);
|
|
139
|
+
}
|
|
140
|
+
if (gqlType.kind.endsWith("OBJECT")) {
|
|
141
|
+
return maybeWrapped(gqlType.name + (selection ? "Selection" : ""));
|
|
142
|
+
}
|
|
143
|
+
if (gqlType.kind === "NON_NULL") {
|
|
144
|
+
return `${gqlTypeToTypescript(gqlType.ofType, {
|
|
145
|
+
isInput,
|
|
146
|
+
required: true,
|
|
147
|
+
selection
|
|
148
|
+
})}`;
|
|
149
|
+
}
|
|
150
|
+
if (gqlType.kind === "LIST") {
|
|
151
|
+
return maybeWrapped(
|
|
152
|
+
`${gqlTypeToTypescript(gqlType.ofType, {
|
|
153
|
+
isInput,
|
|
154
|
+
required: true,
|
|
155
|
+
selection
|
|
156
|
+
})}${selection ? "" : "[]"}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
if (selection) {
|
|
160
|
+
return "";
|
|
161
|
+
}
|
|
162
|
+
if (gqlType.kind === "ENUM" && gqlType.name) {
|
|
163
|
+
return maybeWrapped(gqlType.name);
|
|
164
|
+
}
|
|
165
|
+
if (gqlType.kind === "SCALAR") {
|
|
166
|
+
return maybeWrapped(gqlScalarToTypescript(gqlType.name));
|
|
167
|
+
}
|
|
168
|
+
return "";
|
|
169
|
+
}
|
|
170
|
+
function gqlFieldToTypescript(field, { isInput, selection, defaultValue }) {
|
|
171
|
+
let fieldTypeDefinition = gqlTypeToTypescript(field.type, {
|
|
172
|
+
isInput,
|
|
173
|
+
selection
|
|
174
|
+
});
|
|
175
|
+
fieldTypeDefinition = `${fieldTypeDefinition}`;
|
|
176
|
+
if (selection && field.args && field.args.length) {
|
|
177
|
+
let fieldsOnArgs = field.args.map(
|
|
178
|
+
(arg) => gqlFieldToTypescript(arg, {
|
|
179
|
+
defaultValue: arg.defaultValue,
|
|
180
|
+
isInput: true,
|
|
181
|
+
selection: false
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
fieldTypeDefinition = `{ __headers?: {[key: string]: string}; __retry?: boolean; __alias?: string; __url?: string; __args${fieldsOnArgs.every((arg) => arg.isOptional) ? "?" : ""}: { ${fieldsOnArgs.map((arg) => arg.code).join(", ")} }}${fieldTypeDefinition ? ` & ${fieldTypeDefinition}` : ""}`;
|
|
185
|
+
}
|
|
186
|
+
const isOptional = defaultValue || selection || fieldTypeDefinition.startsWith("Maybe");
|
|
187
|
+
const rawType = fieldTypeDefinition || selection && "boolean";
|
|
188
|
+
const wrappedType = isOptional ? rawType.replace(/Maybe<(.+?)>/, "$1") : rawType;
|
|
189
|
+
return {
|
|
190
|
+
isOptional,
|
|
191
|
+
code: `${field.name}${isOptional ? "?:" : ":"} ${wrappedType}`
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function getArgsType(endpoint) {
|
|
195
|
+
const fieldsOnArgs = endpoint.args.map(
|
|
196
|
+
(arg) => gqlFieldToTypescript(arg, {
|
|
197
|
+
defaultValue: arg.defaultValue,
|
|
198
|
+
isInput: true,
|
|
199
|
+
selection: false
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
const argsType = `{ ${fieldsOnArgs.map((arg) => arg.code).join(", ")} }`;
|
|
203
|
+
const argsFullyOptional = fieldsOnArgs.every((arg) => arg.isOptional);
|
|
204
|
+
return { alias: Case.pascal(`${endpoint.name}Args`), type: argsType, optional: argsFullyOptional };
|
|
205
|
+
}
|
|
206
|
+
function gqlEndpointToCode(kind, endpoint, codeOutputType) {
|
|
207
|
+
const selectionType = gqlTypeToTypescript(endpoint.type, {
|
|
208
|
+
isInput: false,
|
|
209
|
+
selection: true
|
|
210
|
+
});
|
|
211
|
+
const argsType = endpoint.args && endpoint.args.length ? getArgsType(endpoint) : null;
|
|
212
|
+
const inputType = `{
|
|
213
|
+
__headers?: {[key: string]: string};
|
|
214
|
+
__retry?: boolean;
|
|
215
|
+
__alias?: string;
|
|
216
|
+
__url?: string;
|
|
217
|
+
${argsType ? `__args${argsType.optional ? "?" : ""}: ${argsType.alias}` : ""}
|
|
218
|
+
}${selectionType ? ` & ${selectionType}` : ""}`;
|
|
219
|
+
const outputType = gqlTypeToTypescript(endpoint.type, { required: true });
|
|
220
|
+
return codeOutputType === "ts" ? `${endpoint.name}: Endpoint<${inputType}, ${outputType}, AllEnums>` : `${endpoint.name}: apiEndpoint('${kind}', '${endpoint.name}')`;
|
|
221
|
+
}
|
|
222
|
+
function gqlSchemaToCode(gqlType, { selection = false, outputType }) {
|
|
223
|
+
const rawKind = gqlType.kind || gqlType.type;
|
|
224
|
+
if (rawKind === "SCALAR") {
|
|
225
|
+
return outputType === "ts" ? `export declare type ${gqlType.name} = ${/date/i.test(gqlType.name) ? "IDate" : "string"}` : "";
|
|
226
|
+
}
|
|
227
|
+
if (rawKind === "ENUM")
|
|
228
|
+
return outputType === "ts" ? `
|
|
229
|
+
export declare enum ${gqlType.name} {
|
|
230
|
+
${orderBy(gqlType.enumValues, "name").map((_) => `${Case.camel(_.name)} = '${_.name}'`).join(",\n ")}
|
|
231
|
+
}` : `export const ${gqlType.name} = {${orderBy(gqlType.enumValues, "name").map((_) => `${Case.camel(_.name)}: '${_.name}'`).join(",\n ")}}`;
|
|
232
|
+
const fields = gqlType.fields && gqlType.fields || gqlType.inputFields && gqlType.inputFields || [];
|
|
233
|
+
return outputType === "ts" ? `
|
|
234
|
+
export interface ${gqlType.name}${selection ? "Selection" : ""} {
|
|
235
|
+
${fields.map(
|
|
236
|
+
(_) => gqlFieldToTypescript(_, {
|
|
237
|
+
isInput: gqlType.kind === "INPUT_OBJECT",
|
|
238
|
+
selection
|
|
239
|
+
}).code
|
|
240
|
+
).join(",\n ")}
|
|
241
|
+
}` : "";
|
|
242
|
+
}
|
|
243
|
+
function getGraphQLInputType(type) {
|
|
244
|
+
switch (type.kind) {
|
|
245
|
+
case "NON_NULL":
|
|
246
|
+
return `${getGraphQLInputType(type.ofType)}!`;
|
|
247
|
+
case "SCALAR":
|
|
248
|
+
case "INPUT_OBJECT":
|
|
249
|
+
case "ENUM":
|
|
250
|
+
return type.name;
|
|
251
|
+
case "LIST":
|
|
252
|
+
return `[${getGraphQLInputType(type.ofType)}]`;
|
|
253
|
+
default:
|
|
254
|
+
return "";
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function getGraphQLOutputType(type) {
|
|
258
|
+
switch (type.kind) {
|
|
259
|
+
case "LIST":
|
|
260
|
+
return `${getGraphQLOutputType(type.ofType)}[]`;
|
|
261
|
+
case "NON_NULL":
|
|
262
|
+
return getGraphQLOutputType(type.ofType);
|
|
263
|
+
case "OBJECT":
|
|
264
|
+
return type.name;
|
|
265
|
+
default:
|
|
266
|
+
return "";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function getTypesTreeCode(types) {
|
|
270
|
+
const typesTree = {};
|
|
271
|
+
types.forEach(
|
|
272
|
+
(type) => type.fields.filter((_) => _.args && _.args.length).forEach(
|
|
273
|
+
(_) => _.args.forEach((a) => {
|
|
274
|
+
let inputType = getGraphQLInputType(a.type);
|
|
275
|
+
if (inputType) {
|
|
276
|
+
set(typesTree, `${type.name}.${_.name}.__args.${a.name}`, inputType);
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
types.forEach(
|
|
282
|
+
(t) => t.fields.forEach((f) => {
|
|
283
|
+
let outputType = getGraphQLOutputType(f.type);
|
|
284
|
+
if (outputType) {
|
|
285
|
+
set(typesTree, `${t.name}.${f.name}.__shape`, outputType);
|
|
286
|
+
}
|
|
287
|
+
})
|
|
288
|
+
);
|
|
289
|
+
return `
|
|
290
|
+
const typesTree = {
|
|
291
|
+
${Object.entries(typesTree).map(([key, value]) => {
|
|
292
|
+
let entryCode = Object.entries(value).map(([k, v]) => {
|
|
293
|
+
const cleanShapeType = v.__shape && v.__shape.replace(/[\[\]!?]/g, "");
|
|
294
|
+
const fieldsCode = v.__shape && typesTree.hasOwnProperty(cleanShapeType) ? `__fields: typesTree.${cleanShapeType},` : "";
|
|
295
|
+
const argsCode = v.__args ? `__args: {
|
|
296
|
+
${Object.entries(v.__args).map(([k2, v2]) => `${k2}: '${v2}'`).join(",\n")}
|
|
297
|
+
}` : "";
|
|
298
|
+
return fieldsCode || argsCode ? `get ${k}() {
|
|
299
|
+
return {
|
|
300
|
+
${fieldsCode}
|
|
301
|
+
${argsCode}
|
|
302
|
+
}
|
|
303
|
+
}` : `${k}: {}`;
|
|
304
|
+
}).filter(Boolean).join(",\n").trim();
|
|
305
|
+
return entryCode && `
|
|
306
|
+
${key}: {
|
|
307
|
+
${entryCode}
|
|
308
|
+
}`;
|
|
309
|
+
}).filter(Boolean).join(",\n")}
|
|
310
|
+
}
|
|
311
|
+
`;
|
|
312
|
+
}
|
|
313
|
+
function generateClientCode(types, options) {
|
|
314
|
+
const typesHash = md5(`${JSON.stringify(options)}__${JSON.stringify(types)}`);
|
|
315
|
+
const clientCacheFileName = `gql-ts-client__client__${typesHash}__${package_default.version}.json`;
|
|
316
|
+
const clientCacheFilePath = path.resolve(tempDir, clientCacheFileName);
|
|
317
|
+
if (!options.skipCache && fs.existsSync(clientCacheFilePath)) {
|
|
318
|
+
const output2 = JSON.parse(fs.readFileSync(clientCacheFilePath, { encoding: "utf8" }));
|
|
319
|
+
if (output2.js && output2.mjs && output2.typings) {
|
|
320
|
+
return output2;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const queries = types.find((it) => it.name === "Query")?.fields || [];
|
|
324
|
+
const mutations = types.find((it) => it.name === "Mutation")?.fields || [];
|
|
325
|
+
const enums = types.filter((it) => it.kind === "ENUM" && !it.name.startsWith("__"));
|
|
326
|
+
const scalars = types.filter(
|
|
327
|
+
(it) => it.kind === "SCALAR" && !/decimal|int|float|string|long|boolean/i.test(it.name)
|
|
328
|
+
);
|
|
329
|
+
const objectTypes = types.filter((it) => ["OBJECT", "INPUT_OBJECT"].includes(it.kind) && !it.name.startsWith("__"));
|
|
330
|
+
const forInputExtraction = types.filter(
|
|
331
|
+
(it) => !it.name.startsWith("__") && ["OBJECT"].includes(it.kind)
|
|
332
|
+
);
|
|
333
|
+
const clientName = options.clientName || "client";
|
|
334
|
+
const jsCode = `
|
|
335
|
+
// noinspection TypeScriptUnresolvedVariable, ES6UnusedImports, JSUnusedLocalSymbols
|
|
336
|
+
import { getApiEndpointCreator } from '${graphqlTsClientPath}/endpoint'
|
|
337
|
+
|
|
338
|
+
${options.formatGraphQL || options.verbose ? `
|
|
339
|
+
import { format as formatCode } from "prettier/standalone"
|
|
340
|
+
import parserGraphql from "prettier/parser-graphql"
|
|
341
|
+
|
|
342
|
+
const formatGraphQL = (query) => formatCode(query, {parser: 'graphql', plugins: [parserGraphql]})` : `
|
|
343
|
+
const formatGraphQL = (query) => query`}
|
|
344
|
+
|
|
345
|
+
// Enums
|
|
346
|
+
${enums.map((it) => gqlSchemaToCode(it, { selection: false, outputType: "js" })).join("\n")}
|
|
347
|
+
|
|
348
|
+
// Schema Resolution Tree
|
|
349
|
+
${getTypesTreeCode(forInputExtraction)}
|
|
350
|
+
|
|
351
|
+
let verbose = ${Boolean(options.verbose)}
|
|
352
|
+
let headers = {}
|
|
353
|
+
let url = '${options.endpoint}'
|
|
354
|
+
let retryConfig = {
|
|
355
|
+
max: 0,
|
|
356
|
+
before: undefined,
|
|
357
|
+
waitBeforeRetry: 0
|
|
358
|
+
}
|
|
359
|
+
let responseListeners = []
|
|
360
|
+
let errorsParser = ${options.errorsParser}
|
|
361
|
+
// noinspection JSUnusedLocalSymbols
|
|
362
|
+
let apiEndpoint = getApiEndpointCreator({
|
|
363
|
+
getClient: () => ({ url, headers, retryConfig }),
|
|
364
|
+
responseListeners,
|
|
365
|
+
maxAge: 30000,
|
|
366
|
+
verbose,
|
|
367
|
+
typesTree,
|
|
368
|
+
formatGraphQL,
|
|
369
|
+
errorsParser
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
export const ${clientName} = {
|
|
373
|
+
addResponseListener: (listener) => responseListeners.push(
|
|
374
|
+
listener),
|
|
375
|
+
setHeader: (key, value) => {
|
|
376
|
+
headers[key] = value
|
|
377
|
+
},
|
|
378
|
+
setHeaders: (newHeaders) => {
|
|
379
|
+
headers = newHeaders
|
|
380
|
+
},
|
|
381
|
+
setRetryConfig: (options) => {
|
|
382
|
+
if (!Number.isInteger(options.max) || options.max < 0) {
|
|
383
|
+
throw new Error('retryOptions.max should be a non-negative integer')
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
retryConfig = {
|
|
387
|
+
max: options.max,
|
|
388
|
+
waitBeforeRetry: options.waitBeforeRetry,
|
|
389
|
+
before: options.before
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
setUrl: (_url) => url = _url,
|
|
393
|
+
queries: {
|
|
394
|
+
${queries.map((query) => gqlEndpointToCode("query", query, "js")).join(",\n")}
|
|
395
|
+
},
|
|
396
|
+
mutations: {
|
|
397
|
+
${mutations.map((mutation) => gqlEndpointToCode("mutation", mutation, "js")).join(",\n")}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export default ${clientName}`;
|
|
402
|
+
const typingsCode = `
|
|
403
|
+
// noinspection TypeScriptUnresolvedVariable, ES6UnusedImports, JSUnusedLocalSymbols, TypeScriptCheckImport
|
|
404
|
+
import { DeepRequired } from 'ts-essentials'
|
|
405
|
+
import { Maybe, IResponseListener, Endpoint } from '${graphqlTsClientPath}'
|
|
406
|
+
|
|
407
|
+
// Scalars
|
|
408
|
+
export type IDate = string | Date
|
|
409
|
+
${scalars.map((it) => gqlSchemaToCode(it, { selection: false, outputType: "ts" })).join("\n")}
|
|
410
|
+
|
|
411
|
+
// Enums
|
|
412
|
+
${enums.map((it) => gqlSchemaToCode(it, { selection: false, outputType: "ts" })).join("\n")}
|
|
413
|
+
|
|
414
|
+
type AllEnums = ${enums.length ? enums.map((it) => it.name).join(" | ") : "never"}
|
|
415
|
+
|
|
416
|
+
// Args
|
|
417
|
+
${[...queries, ...mutations].map((query) => {
|
|
418
|
+
const argsType = getArgsType(query);
|
|
419
|
+
return `export interface ${argsType.alias} ${argsType.type}`;
|
|
420
|
+
}).join("\n")}
|
|
421
|
+
|
|
422
|
+
// Input/Output Types
|
|
423
|
+
${objectTypes.map(
|
|
424
|
+
(it) => `
|
|
425
|
+
/**
|
|
426
|
+
* @deprecated Avoid directly using this interface. Instead, create a type alias based on the query/mutation return type.
|
|
427
|
+
*/
|
|
428
|
+
${gqlSchemaToCode(it, { selection: false, outputType: "ts" })}`
|
|
429
|
+
).join("\n")}
|
|
430
|
+
|
|
431
|
+
// Selection Types
|
|
432
|
+
${objectTypes.filter((it) => it.name !== "Query").map((it) => gqlSchemaToCode(it, { selection: true, outputType: "ts" })).join("\n")}
|
|
433
|
+
|
|
434
|
+
export declare const ${clientName}: {
|
|
435
|
+
addResponseListener: (listener: IResponseListener) => void
|
|
436
|
+
setHeader: (key: string, value: string) => void
|
|
437
|
+
setHeaders: (newHeaders: { [k: string]: string }) => void,
|
|
438
|
+
setUrl: (url: string) => void,
|
|
439
|
+
setRetryConfig: (options: { max: number, waitBeforeRetry?: number, before?: IResponseListener }) => void
|
|
440
|
+
queries: {
|
|
441
|
+
${queries.map((q) => gqlEndpointToCode("query", q, "ts")).join(",\n")}
|
|
442
|
+
},
|
|
443
|
+
mutations: {
|
|
444
|
+
${mutations.map((q) => gqlEndpointToCode("mutation", q, "ts")).join(",\n")}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export default ${clientName}`;
|
|
449
|
+
const output = {
|
|
450
|
+
js: esbuild.transformSync(jsCode, { format: "cjs", loader: "js" }).code,
|
|
451
|
+
mjs: esbuild.transformSync(jsCode, { format: "esm", loader: "js" }).code,
|
|
452
|
+
typings: prettier.format(typingsCode, { semi: false, parser: "typescript" })
|
|
453
|
+
};
|
|
454
|
+
fs.writeFileSync(clientCacheFilePath, JSON.stringify(output));
|
|
455
|
+
return output;
|
|
456
|
+
}
|
|
457
|
+
async function fetchIntrospection({ endpoint, headers }) {
|
|
458
|
+
const introspectionCacheFileName = `gql-ts-client__introspection__${kebabCase(endpoint)}.json`;
|
|
459
|
+
const introspectionCacheFilePath = path.resolve(tempDir, introspectionCacheFileName);
|
|
460
|
+
let loadedFromCache = false;
|
|
461
|
+
let types;
|
|
462
|
+
const { data } = await axios.post(
|
|
463
|
+
endpoint,
|
|
464
|
+
{ query: getIntrospectionQuery() },
|
|
465
|
+
{
|
|
466
|
+
headers: {
|
|
467
|
+
"Content-Type": "application/json",
|
|
468
|
+
...headers
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
).catch((e) => {
|
|
472
|
+
const errorMessage = `The GraphQL introspection request failed (${endpoint})`;
|
|
473
|
+
if (fs.existsSync(introspectionCacheFilePath)) {
|
|
474
|
+
const cachedSchema = JSON.parse(fs.readFileSync(introspectionCacheFilePath, { encoding: "utf8" }));
|
|
475
|
+
loadedFromCache = true;
|
|
476
|
+
console.warn(`Successfully restored from local cache.`);
|
|
477
|
+
return { data: cachedSchema };
|
|
478
|
+
} else {
|
|
479
|
+
console.error(e);
|
|
480
|
+
return Promise.reject(errorMessage);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
types = data.data.__schema.types;
|
|
484
|
+
if (!loadedFromCache) {
|
|
485
|
+
console.log(`Successfully loaded GraphQL introspection from ${endpoint}`);
|
|
486
|
+
fs.writeFileSync(introspectionCacheFilePath, JSON.stringify(data), {
|
|
487
|
+
encoding: "utf8"
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return types;
|
|
491
|
+
}
|
|
492
|
+
function generateClient(introspectionTypes, { output, ...restOptions }) {
|
|
493
|
+
const { js, mjs, typings } = generateClientCode(introspectionTypes, restOptions);
|
|
494
|
+
if (output && typeof output === "string") {
|
|
495
|
+
const outputDir = path.dirname(output);
|
|
496
|
+
if (!fs.existsSync(outputDir)) {
|
|
497
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
498
|
+
}
|
|
499
|
+
fs.writeFileSync(output.replace(/(\.(ts|js))?$/, ".d.ts"), typings, { encoding: "utf8" });
|
|
500
|
+
fs.writeFileSync(output.replace(/(\.(ts|js))?$/, ".js"), js, { encoding: "utf8" });
|
|
501
|
+
fs.writeFileSync(output.replace(/(\.(ts|js))?$/, ".mjs"), mjs, { encoding: "utf8" });
|
|
502
|
+
}
|
|
503
|
+
return { js, mjs, typings };
|
|
504
|
+
}
|
|
505
|
+
async function generateTypescriptClient({
|
|
506
|
+
introspectionEndpoint,
|
|
507
|
+
...options
|
|
508
|
+
}) {
|
|
509
|
+
console.log(`Generating TypeScript client (name: ${options.clientName ?? "n/a"})`);
|
|
510
|
+
axiosRetry(axios, { retries: 5, retryDelay: (retryCount) => 1e3 * 2 ** retryCount });
|
|
511
|
+
const introspectionTypes = await fetchIntrospection({
|
|
512
|
+
...options,
|
|
513
|
+
endpoint: introspectionEndpoint || options.endpoint
|
|
514
|
+
});
|
|
515
|
+
return generateClient(introspectionTypes, options);
|
|
516
|
+
}
|
|
517
|
+
function generateTypescriptClientFromSDL(SDL, options) {
|
|
518
|
+
console.log(`Generating TypeScript client from SDL (name: ${options.clientName ?? "n/a"})`);
|
|
519
|
+
const graphqlSchemaObj = buildSchema(SDL);
|
|
520
|
+
const introspectionTypes = graphqlSync(graphqlSchemaObj, new Source(getIntrospectionQuery())).data?.__schema.types;
|
|
521
|
+
return generateClient(introspectionTypes, options);
|
|
522
|
+
}
|
|
523
|
+
export {
|
|
524
|
+
GraphQLClientError,
|
|
525
|
+
generateTypescriptClient,
|
|
526
|
+
generateTypescriptClientFromSDL
|
|
527
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
type Maybe<T> = null | undefined | T;
|
|
2
|
+
type Defined<T> = Exclude<T, undefined>;
|
|
3
|
+
type ResponseData = {
|
|
4
|
+
data: any;
|
|
5
|
+
warnings: any;
|
|
6
|
+
headers: any;
|
|
7
|
+
status?: number;
|
|
8
|
+
errors: {
|
|
9
|
+
message: string;
|
|
10
|
+
}[];
|
|
11
|
+
};
|
|
12
|
+
type ResponseListenerInfo = {
|
|
13
|
+
queryName: string;
|
|
14
|
+
query: string;
|
|
15
|
+
variables: any;
|
|
16
|
+
response: ResponseData;
|
|
17
|
+
};
|
|
18
|
+
type IResponseListener = (info: ResponseListenerInfo) => void | Promise<void>;
|
|
19
|
+
type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
|
|
20
|
+
type Primitive = Date | string | number | boolean | null | undefined;
|
|
21
|
+
type Projection<Selection, Base, E = never> = Base extends Array<any> ? ArrayElement<Base> extends Primitive | E ? ArrayElement<Base>[] : Projection<Defined<Selection>, ArrayElement<Base>, E>[] : Base extends Primitive | E ? Selection extends undefined ? Base | undefined : Base : {
|
|
22
|
+
[k in keyof Selection & keyof Base]: Selection[k] extends boolean ? Base[k] : Base[k] extends Array<infer A> ? Projection<Defined<Selection[k]>, A, E>[] : Projection<Defined<Selection[k]>, Base[k], E>;
|
|
23
|
+
};
|
|
24
|
+
type Unpacked<T> = T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise<infer U> ? U : T;
|
|
25
|
+
type Replacement<M extends [any, any], T> = M extends any ? ([T] extends [M[0]] ? M[1] : never) : never;
|
|
26
|
+
type DeepReplace<T, Ignore, M extends [any, any]> = {
|
|
27
|
+
[P in keyof T]: T[P] extends M[0] ? T[P] extends Ignore ? T[P] extends object ? DeepReplace<T[P], Ignore, M> : T[P] : Replacement<M, T[P]> : T[P] extends object ? DeepReplace<T[P], Ignore, M> : T[P];
|
|
28
|
+
};
|
|
29
|
+
type RawEndpoint<I, O, E> = <S extends I>(jsonQuery?: S) => Promise<{
|
|
30
|
+
data: Projection<S, O, E>;
|
|
31
|
+
errors: any[];
|
|
32
|
+
warnings: any[];
|
|
33
|
+
headers: any;
|
|
34
|
+
status: any;
|
|
35
|
+
}>;
|
|
36
|
+
type JsonOutput<O, ToBeIgnored> = DeepReplace<O, ToBeIgnored, [string | Date, string]>;
|
|
37
|
+
type Endpoint<I, O, E> = (<S extends I>(jsonQuery?: S) => Promise<Projection<S, JsonOutput<O, E>, E>>) & {
|
|
38
|
+
memo: <S extends I>(jsonQuery?: S) => Promise<Projection<S, JsonOutput<O, E>, E>>;
|
|
39
|
+
memoRaw: RawEndpoint<I, JsonOutput<O, E>, E>;
|
|
40
|
+
raw: RawEndpoint<I, JsonOutput<O, E>, E>;
|
|
41
|
+
};
|
|
42
|
+
type ClientConfig = {
|
|
43
|
+
url: string;
|
|
44
|
+
headers: {
|
|
45
|
+
[key: string]: string;
|
|
46
|
+
};
|
|
47
|
+
retryConfig: {
|
|
48
|
+
max: number;
|
|
49
|
+
waitBeforeRetry?: number;
|
|
50
|
+
before: IResponseListener;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
type LogInfo = {
|
|
54
|
+
query: string;
|
|
55
|
+
variables: any;
|
|
56
|
+
formatGraphQL: any;
|
|
57
|
+
kind: string;
|
|
58
|
+
queryName: string;
|
|
59
|
+
response?: any;
|
|
60
|
+
error?: Error;
|
|
61
|
+
duration: number;
|
|
62
|
+
};
|
|
63
|
+
type TypescriptClientOutput = {
|
|
64
|
+
js: string;
|
|
65
|
+
mjs: string;
|
|
66
|
+
typings: string;
|
|
67
|
+
};
|
|
68
|
+
declare class GraphQLClientError extends Error {
|
|
69
|
+
responseData: ResponseData;
|
|
70
|
+
constructor(responseData: ResponseData);
|
|
71
|
+
get message(): string;
|
|
72
|
+
get response(): ResponseData;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { ClientConfig as C, Defined as D, Endpoint as E, GraphQLClientError as G, IResponseListener as I, JsonOutput as J, LogInfo as L, Maybe as M, Projection as P, ResponseData as R, TypescriptClientOutput as T, Unpacked as U, ResponseListenerInfo as a, Replacement as b, DeepReplace as c, RawEndpoint as d };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
type Maybe<T> = null | undefined | T;
|
|
2
|
+
type Defined<T> = Exclude<T, undefined>;
|
|
3
|
+
type ResponseData = {
|
|
4
|
+
data: any;
|
|
5
|
+
warnings: any;
|
|
6
|
+
headers: any;
|
|
7
|
+
status?: number;
|
|
8
|
+
errors: {
|
|
9
|
+
message: string;
|
|
10
|
+
}[];
|
|
11
|
+
};
|
|
12
|
+
type ResponseListenerInfo = {
|
|
13
|
+
queryName: string;
|
|
14
|
+
query: string;
|
|
15
|
+
variables: any;
|
|
16
|
+
response: ResponseData;
|
|
17
|
+
};
|
|
18
|
+
type IResponseListener = (info: ResponseListenerInfo) => void | Promise<void>;
|
|
19
|
+
type ArrayElement<ArrayType extends readonly unknown[]> = ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
|
|
20
|
+
type Primitive = Date | string | number | boolean | null | undefined;
|
|
21
|
+
type Projection<Selection, Base, E = never> = Base extends Array<any> ? ArrayElement<Base> extends Primitive | E ? ArrayElement<Base>[] : Projection<Defined<Selection>, ArrayElement<Base>, E>[] : Base extends Primitive | E ? Selection extends undefined ? Base | undefined : Base : {
|
|
22
|
+
[k in keyof Selection & keyof Base]: Selection[k] extends boolean ? Base[k] : Base[k] extends Array<infer A> ? Projection<Defined<Selection[k]>, A, E>[] : Projection<Defined<Selection[k]>, Base[k], E>;
|
|
23
|
+
};
|
|
24
|
+
type Unpacked<T> = T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise<infer U> ? U : T;
|
|
25
|
+
type Replacement<M extends [any, any], T> = M extends any ? ([T] extends [M[0]] ? M[1] : never) : never;
|
|
26
|
+
type DeepReplace<T, Ignore, M extends [any, any]> = {
|
|
27
|
+
[P in keyof T]: T[P] extends M[0] ? T[P] extends Ignore ? T[P] extends object ? DeepReplace<T[P], Ignore, M> : T[P] : Replacement<M, T[P]> : T[P] extends object ? DeepReplace<T[P], Ignore, M> : T[P];
|
|
28
|
+
};
|
|
29
|
+
type RawEndpoint<I, O, E> = <S extends I>(jsonQuery?: S) => Promise<{
|
|
30
|
+
data: Projection<S, O, E>;
|
|
31
|
+
errors: any[];
|
|
32
|
+
warnings: any[];
|
|
33
|
+
headers: any;
|
|
34
|
+
status: any;
|
|
35
|
+
}>;
|
|
36
|
+
type JsonOutput<O, ToBeIgnored> = DeepReplace<O, ToBeIgnored, [string | Date, string]>;
|
|
37
|
+
type Endpoint<I, O, E> = (<S extends I>(jsonQuery?: S) => Promise<Projection<S, JsonOutput<O, E>, E>>) & {
|
|
38
|
+
memo: <S extends I>(jsonQuery?: S) => Promise<Projection<S, JsonOutput<O, E>, E>>;
|
|
39
|
+
memoRaw: RawEndpoint<I, JsonOutput<O, E>, E>;
|
|
40
|
+
raw: RawEndpoint<I, JsonOutput<O, E>, E>;
|
|
41
|
+
};
|
|
42
|
+
type ClientConfig = {
|
|
43
|
+
url: string;
|
|
44
|
+
headers: {
|
|
45
|
+
[key: string]: string;
|
|
46
|
+
};
|
|
47
|
+
retryConfig: {
|
|
48
|
+
max: number;
|
|
49
|
+
waitBeforeRetry?: number;
|
|
50
|
+
before: IResponseListener;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
type LogInfo = {
|
|
54
|
+
query: string;
|
|
55
|
+
variables: any;
|
|
56
|
+
formatGraphQL: any;
|
|
57
|
+
kind: string;
|
|
58
|
+
queryName: string;
|
|
59
|
+
response?: any;
|
|
60
|
+
error?: Error;
|
|
61
|
+
duration: number;
|
|
62
|
+
};
|
|
63
|
+
declare class GraphQLClientError extends Error {
|
|
64
|
+
responseData: ResponseData;
|
|
65
|
+
constructor(responseData: ResponseData);
|
|
66
|
+
get message(): string;
|
|
67
|
+
get response(): ResponseData;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { ClientConfig as C, Defined as D, Endpoint as E, GraphQLClientError as G, IResponseListener as I, JsonOutput as J, LogInfo as L, Maybe as M, Projection as P, ResponseData as R, Unpacked as U, ResponseListenerInfo as a, Replacement as b, DeepReplace as c, RawEndpoint as d };
|