@manot40/genql-cli 1.0.0 → 1.0.1

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.
@@ -1,798 +0,0 @@
1
- import { Listr } from "listr2";
2
- import fsx from "fs-extra";
3
- import url from "node:url";
4
- import path, { resolve } from "node:path";
5
- import { assertValidSchema, buildClientSchema, getIntrospectionQuery, getNamedType, isEnumType, isInputObjectType, isInterfaceType, isListType, isNamedType, isNonNullType, isObjectType, isScalarType, isUnionType, lexicographicSortSchema, printSchema } from "graphql";
6
- import lodashCamelCase from "lodash-es/camelCase.js";
7
- import { mkdir, writeFile } from "node:fs/promises";
8
- import { mkdirp } from "mkdirp";
9
- import { rimraf } from "rimraf";
10
- import path$1 from "path";
11
- import uniq from "lodash-es/uniq.js";
12
- import { GraphQLFileLoader } from "@graphql-tools/graphql-file-loader";
13
- import { stringifyQuery } from "ufo";
14
- import { loadSchema } from "@graphql-tools/load";
15
-
16
- //#region src/utils.ts
17
- function camelCase(str) {
18
- if (!str) return str;
19
- if (str.startsWith("_")) {
20
- const leadingChars = str.match(/^_+/);
21
- return leadingChars[0] + lodashCamelCase(str.slice(leadingChars[0].length));
22
- }
23
- if (str.endsWith("_")) {
24
- const leadingChars = str.match(/_+$/);
25
- return lodashCamelCase(str.slice(0, str.length - leadingChars[0].length)) + leadingChars[0];
26
- }
27
- return lodashCamelCase(str);
28
- }
29
- function capitalize(str) {
30
- if (!str) return str;
31
- return str.charAt(0).toUpperCase() + str.slice(1);
32
- }
33
-
34
- //#endregion
35
- //#region src/helpers/files.ts
36
- const ensurePath = async (path$2, clear = false) => {
37
- if (clear) await rimraf(resolve(...path$2));
38
- await mkdirp(resolve(...path$2));
39
- };
40
- const writeFileToPath = async (path$2, content) => {
41
- await mkdir(resolve(...path$2, ".."), { recursive: true });
42
- await writeFile(resolve(...path$2), content, "utf-8");
43
- };
44
-
45
- //#endregion
46
- //#region src/config.ts
47
- const RUNTIME_LIB_NAME = "./runtime";
48
-
49
- //#endregion
50
- //#region src/render/requestTypes/requestTypeName.ts
51
- const requestTypeName = (type) => {
52
- if (!type) return "";
53
- return `${type.name}GenqlSelection`;
54
- };
55
-
56
- //#endregion
57
- //#region src/render/client/renderClient.ts
58
- const renderClientCode = (ctx) => {
59
- const url$1 = ctx.config?.endpoint ? `"${ctx.config.endpoint}"` : "undefined";
60
- const fetchImport = ctx.config?.fetchImport;
61
- return `
62
- function(options${url$1 ? "?" : ""}: ClientOptions): Client {
63
- return createClientOriginal({
64
- url: ${url$1},
65
- ${fetchImport ? `fetch,` : ""}
66
- ...options,
67
- queryRoot: typeMap.Query!,
68
- mutationRoot: typeMap.Mutation!,
69
- subscriptionRoot: typeMap.Subscription!,
70
- }) as any
71
- }`;
72
- };
73
- const renderClientEsm = (schema, ctx) => {
74
- const queryType = schema.getQueryType();
75
- const mutationType = schema.getMutationType();
76
- const subscriptionType = schema.getSubscriptionType();
77
- const fetchImport = ctx.config?.fetchImport || "";
78
- ctx.addCodeBlock(`
79
- ${fetchImport}
80
- ${renderClientTypesImports({
81
- mutationType,
82
- queryType,
83
- subscriptionType
84
- })}
85
- import {
86
- linkTypeMap,
87
- createClient as createClientOriginal,
88
- generateGraphqlOperation,
89
- type FieldsSelection, type GraphqlOperation, type ClientOptions, GenqlError
90
- } from '${RUNTIME_LIB_NAME}'
91
- export type { FieldsSelection } from '${RUNTIME_LIB_NAME}'
92
- export { GenqlError }
93
-
94
- import types from './types'
95
- export * from './schema'
96
- const typeMap = linkTypeMap(types as any)
97
-
98
- ${renderClientType({
99
- mutationType,
100
- queryType,
101
- subscriptionType
102
- })}
103
-
104
- export const createClient = ${renderClientCode(ctx)}
105
-
106
- export const everything = {
107
- __scalar: true
108
- }
109
- `);
110
- if (queryType) ctx.addCodeBlock(`
111
- export type QueryResult<fields extends ${requestTypeName(queryType)}> = FieldsSelection<${queryType.name}, fields>
112
- export const generateQueryOp: (fields: ${requestTypeName(queryType)} & { __name?: string }) => GraphqlOperation = function(fields) {
113
- return generateGraphqlOperation('query', typeMap.Query!, fields as any)
114
- }
115
- `);
116
- if (mutationType) ctx.addCodeBlock(`
117
- export type MutationResult<fields extends ${requestTypeName(mutationType)}> = FieldsSelection<${mutationType.name}, fields>
118
- export const generateMutationOp: (fields: ${requestTypeName(mutationType)} & { __name?: string }) => GraphqlOperation = function(fields) {
119
- return generateGraphqlOperation('mutation', typeMap.Mutation!, fields as any)
120
- }
121
- `);
122
- if (subscriptionType) ctx.addCodeBlock(`
123
- export type SubscriptionResult<fields extends ${requestTypeName(subscriptionType)}> = FieldsSelection<${subscriptionType.name}, fields>
124
- export const generateSubscriptionOp: (fields: ${requestTypeName(subscriptionType)} & { __name?: string }) => GraphqlOperation = function(fields) {
125
- return generateGraphqlOperation('subscription', typeMap.Subscription!, fields as any)
126
- }
127
- `);
128
- };
129
- function renderClientTypesImports({ queryType, mutationType, subscriptionType }) {
130
- const imports = [];
131
- if (queryType) imports.push(requestTypeName(queryType), queryType.name);
132
- if (mutationType) imports.push(requestTypeName(mutationType), mutationType.name);
133
- if (subscriptionType) imports.push(requestTypeName(subscriptionType), subscriptionType.name);
134
- if (imports.length > 0) return `import type {${imports.join(",")}} from './schema'`;
135
- return "";
136
- }
137
- function renderClientType({ queryType, mutationType, subscriptionType }) {
138
- let interfaceContent = "";
139
- if (queryType) interfaceContent += `
140
- query<R extends ${requestTypeName(queryType)}>(
141
- request: R & { __name?: string },
142
- ): Promise<FieldsSelection<${queryType.name}, R>>
143
- `;
144
- if (mutationType) interfaceContent += `
145
- mutation<R extends ${requestTypeName(mutationType)}>(
146
- request: R & { __name?: string },
147
- ): Promise<FieldsSelection<${mutationType.name}, R>>
148
- `;
149
- return `
150
- export interface Client {
151
- ${interfaceContent}
152
- }
153
- `;
154
- }
155
-
156
- //#endregion
157
- //#region src/render/common/excludedTypes.ts
158
- const excludedTypes = [
159
- "__Schema",
160
- "__Type",
161
- "__TypeKind",
162
- "__Field",
163
- "__InputValue",
164
- "__EnumValue",
165
- "__Directive",
166
- "__DirectiveLocation"
167
- ];
168
-
169
- //#endregion
170
- //#region src/helpers/prettify.ts
171
- function prettify(code, parser) {
172
- const hasPrettier = import.meta.resolve("prettier");
173
- if (parser === "skip" || !hasPrettier) return code;
174
- return new Promise(async (resolve$1, reject) => {
175
- try {
176
- const prettier = await import("prettier/standalone.js").then(({ default: mod }) => mod);
177
- const parserTS = await import("prettier/plugins/typescript.js").then(({ default: mod }) => mod);
178
- const parserEstree = await import("prettier/plugins/estree.js").then(({ default: mod }) => mod);
179
- const parserGraphQL = await import("prettier/plugins/graphql.js").then(({ default: mod }) => mod);
180
- resolve$1(await prettier.format(code, {
181
- parser,
182
- plugins: [
183
- parserGraphQL,
184
- parserTS,
185
- parserEstree
186
- ],
187
- semi: false,
188
- singleQuote: true,
189
- trailingComma: "all",
190
- printWidth: 80
191
- }));
192
- } catch (e) {
193
- reject(e);
194
- }
195
- });
196
- }
197
-
198
- //#endregion
199
- //#region src/render/common/relativeImportPath.ts
200
- const relativeImportPath = (from, to) => {
201
- const fromResolved = path$1.relative(from, to);
202
- return fromResolved[0] === "." ? fromResolved : `./${fromResolved}`;
203
- };
204
-
205
- //#endregion
206
- //#region src/render/common/RenderContext.ts
207
- var RenderContext = class {
208
- codeBlocks = [];
209
- imports = {};
210
- importAliasCounter = 0;
211
- constructor(schema, config) {
212
- this.schema = schema;
213
- this.config = config;
214
- }
215
- addCodeBlock(block) {
216
- if (block) this.codeBlocks.push(block);
217
- }
218
- addImport(from, isDefault, module, fromAbsolute, noAlias) {
219
- if (this.config && this.config.output) from = fromAbsolute ? from : relativeImportPath(this.config.output, from);
220
- if (!this.imports[from]) this.imports[from] = [];
221
- const imports = this.imports[from];
222
- const existing = imports.find((i) => isDefault && i.isDefault || !isDefault && i.module === module);
223
- if (existing) return existing.alias;
224
- this.importAliasCounter++;
225
- const alias = noAlias ? void 0 : `a${this.importAliasCounter}`;
226
- imports.push({
227
- isDefault,
228
- module,
229
- alias
230
- });
231
- return alias;
232
- }
233
- getImportBlock() {
234
- const imports = [];
235
- Object.keys(this.imports).forEach((from) => {
236
- let defaultImport = this.imports[from].find((i) => i.isDefault);
237
- const namedImports = this.imports[from].filter((i) => !i.isDefault);
238
- const statements = [];
239
- if (defaultImport) statements.push(defaultImport.alias || "");
240
- if (namedImports.length > 0) statements.push(`{${namedImports.map((i) => i.alias ? `${i.module} as ${i.alias}` : i.module).join(",")}}`);
241
- imports.push(`import ${statements.join(",")} from '${from}'`);
242
- });
243
- if (imports.length > 0) return imports.join("\n");
244
- else return;
245
- }
246
- toCode(parser, pretty = false) {
247
- const blocks = [...this.codeBlocks];
248
- if (parser && (parser === "typescript" || parser === "babel")) {
249
- const importBlock = this.getImportBlock();
250
- if (importBlock) blocks.unshift(importBlock);
251
- }
252
- if (parser) {
253
- const whiteSpaced = blocks.join("\n\n");
254
- if (pretty && parser !== "skip") return prettify(whiteSpaced, parser);
255
- return whiteSpaced;
256
- }
257
- return blocks.join("");
258
- }
259
- };
260
-
261
- //#endregion
262
- //#region src/render/common/comment.ts
263
- const comment = (comment$1) => {
264
- const lines = [];
265
- if (comment$1.deprecated) lines.push(`@deprecated ${comment$1.deprecated.replace(/\s/g, " ")}`);
266
- if (comment$1.text) lines.push(...comment$1.text.split("\n"));
267
- return lines.length > 0 ? lines.length === 1 ? `\n/** ${lines[0]} */\n` : `\n/**\n${lines.map((l) => ` * ${l}`).join("\n")}\n */\n` : "";
268
- };
269
- const typeComment = (type) => comment({ text: type.description });
270
- const fieldComment = (field) => comment({
271
- deprecated: field.deprecationReason,
272
- text: field.description
273
- });
274
- const argumentComment = (arg) => comment({ text: arg.description });
275
-
276
- //#endregion
277
- //#region src/render/common/renderTyping.ts
278
- const renderTyping = (type, nonNull = false, wrap = (x) => x) => {
279
- if (isNamedType(type)) {
280
- let typeName = type.name;
281
- if (isScalarType(type)) typeName = `Scalars['${typeName}']`;
282
- const typing = wrap(typeName);
283
- return nonNull ? typing : `(${typing} | null)`;
284
- }
285
- if (isListType(type)) {
286
- const typing = `${renderTyping(type.ofType, false, wrap)}[]`;
287
- return nonNull ? typing : `(${typing} | null)`;
288
- }
289
- return renderTyping(type.ofType, true, wrap);
290
- };
291
-
292
- //#endregion
293
- //#region src/render/common/support.ts
294
- function sortKeys(obj) {
295
- obj = obj || {};
296
- const ordered = {};
297
- Object.keys(obj).sort().forEach(function(key) {
298
- ordered[key] = obj[key];
299
- });
300
- return ordered;
301
- }
302
-
303
- //#endregion
304
- //#region src/render/requestTypes/inputObjectType.ts
305
- const inputObjectType = (type, ctx) => {
306
- let fields = type.getFields();
307
- if (ctx.config?.sortProperties) fields = sortKeys(fields);
308
- const fieldStrings = Object.keys(fields).map((fieldName) => {
309
- const field = fields[fieldName];
310
- return `${argumentComment(field)}${field.name}${isNonNullType(field.type) && field.defaultValue == null ? ":" : "?:"} ${renderTyping(field.type)}`;
311
- });
312
- ctx.addCodeBlock(`${typeComment(type)}export interface ${type.name} {${fieldStrings.join(",")}}`);
313
- };
314
-
315
- //#endregion
316
- //#region src/render/requestTypes/objectType.ts
317
- const INDENTATION$1 = " ";
318
- const objectType$2 = (type, ctx) => {
319
- let fields = type.getFields();
320
- if (ctx.config?.sortProperties) fields = sortKeys(fields);
321
- let fieldStrings = Object.keys(fields).map((fieldName) => {
322
- const field = fields[fieldName];
323
- const types = [];
324
- const resolvedType = getNamedType(field.type);
325
- const resolvable = !(isEnumType(resolvedType) || isScalarType(resolvedType));
326
- const argsPresent = field.args.length > 0;
327
- const argsString = toArgsString(field);
328
- const argsOptional = !argsString.match(/[^?]:/);
329
- if (argsPresent) if (resolvable) types.push(`(${requestTypeName(resolvedType)} & { __args${argsOptional ? "?" : ""}: ${argsString} })`);
330
- else types.push(`{ __args: ${argsString} }`);
331
- if (argsOptional && !resolvable) types.push("boolean | number");
332
- if (!argsPresent && resolvable) types.push(requestTypeName(resolvedType));
333
- return `${fieldComment(field)}${field.name}?: ${types.join(" | ")}`;
334
- });
335
- if (isInterfaceType(type) && ctx.schema) {
336
- let interfaceProperties = ctx.schema.getPossibleTypes(type).map((t) => `on_${t.name}?: ${requestTypeName(t)}`);
337
- if (ctx.config?.sortProperties) interfaceProperties = interfaceProperties.sort();
338
- fieldStrings = fieldStrings.concat(interfaceProperties);
339
- }
340
- fieldStrings.push("__typename?: boolean | number");
341
- fieldStrings.push("__scalar?: boolean | number");
342
- fieldStrings = fieldStrings.map((x) => x.split("\n").filter(Boolean).map((l) => INDENTATION$1 + l).join("\n"));
343
- ctx.addCodeBlock(`${typeComment(type)}export interface ${requestTypeName(type)}{\n${fieldStrings.join("\n")}\n}`);
344
- };
345
- const toArgsString = (field) => {
346
- return `{${field.args.map((a) => `${argumentComment(a)}${a.name}${isNonNullType(a.type) && a.defaultValue == null ? ":" : "?:"} ${renderTyping(a.type)}`).join(", ")}}`;
347
- };
348
-
349
- //#endregion
350
- //#region src/render/requestTypes/unionType.ts
351
- const unionType$2 = (type, ctx) => {
352
- let types = [...type.getTypes()];
353
- if (ctx.config?.sortProperties) types = types.sort();
354
- const fieldStrings = types.map((t) => `on_${t.name}?:${requestTypeName(t)}`);
355
- const commonInterfaces = uniq(types.map((x) => x.getInterfaces()).flat());
356
- fieldStrings.push(...commonInterfaces.map((type$1) => {
357
- return `on_${type$1.name}?: ${requestTypeName(type$1)}`;
358
- }));
359
- fieldStrings.push("__typename?: boolean | number");
360
- ctx.addCodeBlock(`${typeComment(type)}export interface ${requestTypeName(type)}{\n${fieldStrings.map((x) => " " + x).join(",\n")}\n}`);
361
- };
362
-
363
- //#endregion
364
- //#region src/render/requestTypes/renderRequestTypes.ts
365
- const renderRequestTypes = (schema, ctx) => {
366
- let typeMap = schema.getTypeMap();
367
- if (ctx.config?.sortProperties) typeMap = sortKeys(typeMap);
368
- for (const name in typeMap) {
369
- if (excludedTypes.includes(name)) continue;
370
- const type = typeMap[name];
371
- if (isObjectType(type) || isInterfaceType(type)) objectType$2(type, ctx);
372
- if (isInputObjectType(type)) inputObjectType(type, ctx);
373
- if (isUnionType(type)) unionType$2(type, ctx);
374
- }
375
- const aliases = [
376
- {
377
- type: schema.getQueryType(),
378
- name: "QueryGenqlSelection"
379
- },
380
- {
381
- type: schema.getMutationType(),
382
- name: "MutationGenqlSelection"
383
- },
384
- {
385
- type: schema.getSubscriptionType(),
386
- name: "SubscriptionGenqlSelection"
387
- }
388
- ].map(renderAlias$1).filter(Boolean).join("\n");
389
- ctx.addCodeBlock(aliases);
390
- };
391
- function renderAlias$1({ type, name }) {
392
- if (type && requestTypeName(type) !== name) return `export type ${name} = ${requestTypeName(type)}`;
393
- return "";
394
- }
395
-
396
- //#endregion
397
- //#region src/render/responseTypes/enumType.ts
398
- const enumType = (type, ctx) => {
399
- const values = type.getValues().map((v) => `'${v.name}'`);
400
- ctx.addCodeBlock(`${typeComment(type)}export type ${type.name} = ${values.join(" | ")}`);
401
- };
402
-
403
- //#endregion
404
- //#region src/render/responseTypes/objectType.ts
405
- const INDENTATION = " ";
406
- const objectType$1 = (type, ctx) => {
407
- let fieldsMap = type.getFields();
408
- if (ctx.config?.sortProperties) fieldsMap = sortKeys(fieldsMap);
409
- const fields = Object.keys(fieldsMap).map((fieldName) => fieldsMap[fieldName]);
410
- if (!ctx.schema) throw new Error("no schema provided");
411
- const typeNames = isObjectType(type) ? [type.name] : ctx.schema.getPossibleTypes(type).map((t) => t.name);
412
- let fieldStrings = fields.map((f) => {
413
- return `${fieldComment(f)}${f.name}: ${renderTyping(f.type)}`;
414
- }).concat([`__typename: ${typeNames.length > 0 ? typeNames.map((t) => `'${t}'`).join("|") : "string"}`]);
415
- fieldStrings = fieldStrings.map((x) => x.split("\n").filter(Boolean).map((l) => INDENTATION + l).join("\n"));
416
- ctx.addCodeBlock(`${typeComment(type)}export interface ${type.name} {\n${fieldStrings.join("\n")}\n}`);
417
- };
418
-
419
- //#endregion
420
- //#region src/render/responseTypes/typeMappedAlias.ts
421
- const knownTypes = {
422
- Int: "number",
423
- Float: "number",
424
- String: "string",
425
- Boolean: "boolean",
426
- ID: "string"
427
- };
428
- const getTypeMappedAlias = (type, ctx) => {
429
- return {
430
- ...knownTypes,
431
- ...ctx?.config?.scalarTypes || {}
432
- }?.[type.name] || "any";
433
- };
434
-
435
- //#endregion
436
- //#region src/render/responseTypes/scalarType.ts
437
- function renderScalarTypes(ctx, types) {
438
- let content = "";
439
- types.forEach((type) => {
440
- content += ` ${type.name}: ${getTypeMappedAlias(type, ctx)},\n`;
441
- });
442
- return `export type Scalars = {\n${content}}`;
443
- }
444
-
445
- //#endregion
446
- //#region src/render/responseTypes/unionType.ts
447
- const unionType$1 = (type, ctx) => {
448
- let typeNames = type.getTypes().map((t) => t.name);
449
- if (ctx.config?.sortProperties) typeNames = typeNames.sort();
450
- ctx.addCodeBlock(`${typeComment(type)}export type ${type.name} = (${typeNames.join(" | ")}) & { __isUnion?: true }`);
451
- };
452
-
453
- //#endregion
454
- //#region src/render/responseTypes/interfaceType.ts
455
- const interfaceType = (type, ctx) => {
456
- if (!ctx.schema) throw new Error("schema is required to render unionType");
457
- const typeNames = ctx.schema.getPossibleTypes(type).map((t) => t.name);
458
- if (!typeNames.length) objectType$1(type, ctx);
459
- else ctx.addCodeBlock(`${typeComment(type)}export type ${type.name} = (${typeNames.join(" | ")}) & { __isUnion?: true }`);
460
- };
461
-
462
- //#endregion
463
- //#region src/render/responseTypes/renderResponseTypes.ts
464
- const renderResponseTypes = (schema, ctx) => {
465
- let typeMap = schema.getTypeMap();
466
- if (ctx.config?.sortProperties) typeMap = sortKeys(typeMap);
467
- ctx.addCodeBlock(renderScalarTypes(ctx, Object.values(typeMap).filter((type) => isScalarType(type))));
468
- for (const name in typeMap) {
469
- if (excludedTypes.includes(name)) continue;
470
- const type = typeMap[name];
471
- if (isEnumType(type)) enumType(type, ctx);
472
- if (isUnionType(type)) unionType$1(type, ctx);
473
- if (isObjectType(type)) objectType$1(type, ctx);
474
- if (isInterfaceType(type)) interfaceType(type, ctx);
475
- }
476
- const aliases = [
477
- {
478
- type: schema.getQueryType(),
479
- name: "Query"
480
- },
481
- {
482
- type: schema.getMutationType(),
483
- name: "Mutation"
484
- },
485
- {
486
- type: schema.getSubscriptionType(),
487
- name: "Subscription"
488
- }
489
- ].map(renderAlias).filter(Boolean).join("\n");
490
- ctx.addCodeBlock(aliases);
491
- };
492
- function renderAlias({ type, name }) {
493
- if (type && type.name !== name) return `export type ${name} = ${type.name}`;
494
- return "";
495
- }
496
-
497
- //#endregion
498
- //#region src/render/schema/renderSchema.ts
499
- const renderSchema = (schema, ctx) => {
500
- ctx.addCodeBlock(printSchema(schema));
501
- };
502
-
503
- //#endregion
504
- //#region src/render/typeGuards/renderTypeGuards.ts
505
- const renderTypeGuard = (target, possible) => {
506
- return `
507
- const ${target}_possibleTypes: string[] = [${possible.map((t) => `'${t}'`).join(",")}]
508
- export const is${target} = (obj?: { __typename?: any } | null): obj is ${target} => {
509
- if (!obj?.__typename) throw new Error('__typename is missing in "is${target}"')
510
- return ${target}_possibleTypes.includes(obj.__typename)
511
- }
512
- `;
513
- };
514
- const renderTypeGuards = (schema, ctx) => {
515
- const typeMap = schema.getTypeMap();
516
- for (const name in typeMap) {
517
- if (excludedTypes.includes(name)) continue;
518
- const type = typeMap[name];
519
- if (isUnionType(type)) {
520
- const types = type.getTypes().map((t) => t.name);
521
- ctx.addCodeBlock(renderTypeGuard(type.name, types));
522
- } else if (isInterfaceType(type)) {
523
- const types = schema.getPossibleTypes(type).map((t) => t.name);
524
- ctx.addCodeBlock(renderTypeGuard(type.name, types));
525
- } else if (isObjectType(type)) ctx.addCodeBlock(renderTypeGuard(type.name, [type.name]));
526
- }
527
- };
528
-
529
- //#endregion
530
- //#region src/render/typeMap/support.ts
531
- function isEmpty(x) {
532
- if (!x) return true;
533
- return Object.keys(x).length === 0;
534
- }
535
-
536
- //#endregion
537
- //#region src/render/typeMap/objectType.ts
538
- const objectType = (type, ctx) => {
539
- const typeObj = Object.keys(type.getFields()).reduce((r, f) => {
540
- const field = type.getFields()[f];
541
- const fieldObj = { type: getNamedType(field.type).name };
542
- r[f] = fieldObj;
543
- const args = field.args || [];
544
- if (args.length > 0) fieldObj.args = args.reduce((r$1, a) => {
545
- const concreteType = a.type.toString();
546
- const typename = getNamedType(a.type).name;
547
- r$1[a.name] = [typename];
548
- if (typename !== concreteType) r$1[a.name]?.push(concreteType);
549
- return r$1;
550
- }, {});
551
- return r;
552
- }, {});
553
- if (isInterfaceType(type) && ctx.schema) ctx.schema.getPossibleTypes(type).map((t) => {
554
- if (!isEmpty(typeObj)) typeObj[`on_${t.name}`] = { type: t.name };
555
- });
556
- if (!isEmpty(typeObj)) typeObj.__typename = { type: "String" };
557
- return typeObj;
558
- };
559
-
560
- //#endregion
561
- //#region src/render/typeMap/unionType.ts
562
- const unionType = (type, _) => {
563
- const types = type.getTypes();
564
- const typeObj = types.reduce((r, t) => {
565
- r[`on_${t.name}`] = { type: t.name };
566
- return r;
567
- }, {});
568
- uniq(types.map((x) => x.getInterfaces()).flat()).forEach((t) => {
569
- typeObj[`on_${t.name}`] = { type: t.name };
570
- });
571
- typeObj.__typename = { type: "String" };
572
- return typeObj;
573
- };
574
-
575
- //#endregion
576
- //#region src/render/typeMap/renderTypeMap.ts
577
- const renderTypeMap = (schema, ctx) => {
578
- const result = {
579
- scalars: [],
580
- types: {}
581
- };
582
- Object.keys(schema.getTypeMap()).filter((t) => !excludedTypes.includes(t)).map((t) => schema.getTypeMap()[t]).map((t) => {
583
- if (isObjectType(t) || isInterfaceType(t) || isInputObjectType(t)) result.types[t.name] = objectType(t, ctx);
584
- else if (isUnionType(t)) result.types[t.name] = unionType(t, ctx);
585
- else if (isScalarType(t) || isEnumType(t)) {
586
- result.scalars.push(t.name);
587
- result.types[t.name] = {};
588
- }
589
- });
590
- const q = schema.getQueryType();
591
- if (q?.name && q?.name !== "Query") {
592
- delete result.types[q.name];
593
- result.types.Query = objectType(q, ctx);
594
- }
595
- const m = schema.getMutationType();
596
- if (m?.name && m.name !== "Mutation") {
597
- delete result.types[m.name];
598
- result.types.Mutation = objectType(m, ctx);
599
- }
600
- const s = schema.getSubscriptionType();
601
- if (s?.name && s.name !== "Subscription") {
602
- delete result.types[s.name];
603
- result.types.Subscription = objectType(s, ctx);
604
- }
605
- ctx.addCodeBlock(JSON.stringify(replaceTypeNamesWithIndexes(result), null, 4));
606
- };
607
- function replaceTypeNamesWithIndexes(typeMap) {
608
- const nameToIndex = Object.assign({}, ...Object.keys(typeMap.types).map((k, i) => ({ [k]: i })));
609
- return {
610
- scalars: typeMap.scalars.map((x) => nameToIndex[x]),
611
- types: Object.assign({}, ...Object.keys(typeMap.types || {}).map((k) => {
612
- const fieldsMap = typeMap.types[k] || {};
613
- const fields = Object.assign({}, ...Object.keys(fieldsMap).map((f) => {
614
- const content = fieldsMap[f];
615
- if (!content) throw new Error("no content in field " + f);
616
- const [typeName, args] = [content.type, content.args];
617
- const res = [typeName ? nameToIndex[typeName] : -1];
618
- if (args) res[1] = Object.assign({}, ...Object.keys(args || {}).map((k$1) => {
619
- const arg = args?.[k$1];
620
- if (!arg) throw new Error("replaceTypeNamesWithIndexes: no arg for " + k$1);
621
- return { [k$1]: [nameToIndex[arg[0]], ...arg.slice(1)] };
622
- }));
623
- return { [f]: res };
624
- }));
625
- return { [k]: { ...fields } };
626
- }))
627
- };
628
- }
629
-
630
- //#endregion
631
- //#region src/tasks/clientTasks.ts
632
- const typeMapFileEsm = "types.ts";
633
- const schemaTypesFile = "schema.ts";
634
- const schemaGqlFile = "schema.graphql";
635
- const cliRoot = path.dirname(url.fileURLToPath(import.meta.url));
636
- const runtimeFolderPath = path.resolve(cliRoot, "runtime");
637
- const clientTasks = (config) => {
638
- const clientFileEsm = "index.ts";
639
- if (!config.output) throw new Error("`output` must be defined in the config");
640
- const output = config.output;
641
- const tasks = [
642
- {
643
- title: `writing ${schemaGqlFile}`,
644
- async task(ctx) {
645
- const renderCtx = new RenderContext(ctx.schema, config);
646
- renderSchema(ctx.schema, renderCtx);
647
- await writeFileToPath([output, schemaGqlFile], await renderCtx.toCode("graphql"));
648
- }
649
- },
650
- {
651
- title: `copy runtime files`,
652
- async task() {
653
- await fsx.ensureDir(path.resolve(output, "runtime"));
654
- let files = await fsx.readdir(runtimeFolderPath);
655
- for (let file of files) {
656
- let contents = await fsx.readFile(path.resolve(runtimeFolderPath, file), "utf-8");
657
- contents = "// @ts-nocheck\n" + contents;
658
- await fsx.writeFile(path.resolve(output, "runtime", file), contents);
659
- }
660
- }
661
- },
662
- {
663
- title: `writing ${schemaTypesFile}`,
664
- async task(ctx) {
665
- const renderCtx = new RenderContext(ctx.schema, config);
666
- renderResponseTypes(ctx.schema, renderCtx);
667
- renderRequestTypes(ctx.schema, renderCtx);
668
- renderTypeGuards(ctx.schema, renderCtx);
669
- renderEnumsMaps(ctx.schema, renderCtx);
670
- const code = await renderCtx.toCode("typescript");
671
- await writeFileToPath([output, schemaTypesFile], "// @ts-nocheck\n/* istanbul ignore file */\n/* tslint:disable */\n/* eslint-disable */\n\n" + code);
672
- }
673
- },
674
- {
675
- title: `writing types map`,
676
- async task(ctx) {
677
- const renderCtx = new RenderContext(ctx.schema, config);
678
- renderTypeMap(ctx.schema, renderCtx);
679
- const code = await renderCtx.toCode();
680
- await writeFileToPath([output, typeMapFileEsm], `export default ${code}`);
681
- }
682
- },
683
- {
684
- title: `writing ${clientFileEsm}`,
685
- async task(ctx) {
686
- const renderCtx = new RenderContext(ctx.schema, config);
687
- renderClientEsm(ctx.schema, renderCtx);
688
- const code = await renderCtx.toCode("typescript", true);
689
- await writeFileToPath([output, clientFileEsm], "// @ts-nocheck\n" + code);
690
- }
691
- }
692
- ];
693
- return [{
694
- title: "preparing client directory",
695
- task: () => ensurePath([output], true)
696
- }, {
697
- title: `writing files`,
698
- task() {
699
- return new Listr(tasks.filter((x) => Boolean(x)), { concurrent: true });
700
- }
701
- }];
702
- };
703
- function renderEnumsMaps(schema, ctx) {
704
- let typeMap = schema.getTypeMap();
705
- const enums = [];
706
- for (const name in typeMap) {
707
- if (excludedTypes.includes(name)) continue;
708
- const type = typeMap[name];
709
- if (isEnumType(type)) enums.push(type);
710
- }
711
- if (enums.length === 0) return;
712
- ctx.addCodeBlock(enums.map((type) => `export const ${"enum" + capitalize(camelCase(type.name))} = {\n` + type.getValues().map((v) => {
713
- if (!v?.name) return "";
714
- return ` ${v.name}: '${v.name}' as const`;
715
- }).join(",\n") + `\n}\n`).join("\n"));
716
- }
717
-
718
- //#endregion
719
- //#region src/schema/fetchSchema.ts
720
- const fetchSchema = async ({ endpoint, usePost = true, headers, timeout = 20 * 1e3, options }) => {
721
- let controller = new AbortController();
722
- let id = setTimeout(() => {
723
- controller.abort();
724
- }, timeout);
725
- const response = await fetch(usePost ? endpoint : `${endpoint}?${stringifyQuery({ query: getIntrospectionQuery() })}`, {
726
- signal: controller.signal,
727
- ...usePost ? {
728
- method: usePost ? "POST" : "GET",
729
- body: JSON.stringify({ query: getIntrospectionQuery() }),
730
- headers: {
731
- ...headers,
732
- "Content-Type": "application/json"
733
- }
734
- } : { headers }
735
- });
736
- clearTimeout(id);
737
- if (!response.ok) throw new Error(`introspection for ${endpoint} failed, ${response.status} ${response.statusText}`);
738
- const result = await response.json().catch((e) => {
739
- const contentType = response.headers.get("Content-Type");
740
- throw new Error(`endpoint '${endpoint}' did not return valid json, content type is ${contentType}, check that your endpoint points to a valid graphql api`);
741
- });
742
- if (!result.data) throw new Error(`introspection for ${endpoint} failed: ${JSON.stringify(result).slice(0, 400)}...`);
743
- return buildClientSchema(result.data, options);
744
- };
745
-
746
- //#endregion
747
- //#region src/tasks/schemaTask.ts
748
- const schemaTask = (config) => {
749
- const processSchema = (schema) => {
750
- if (config.sortProperties) return lexicographicSortSchema(schema);
751
- return schema;
752
- };
753
- if (config.endpoint) {
754
- const endpoint = config.endpoint;
755
- return {
756
- title: `fetching schema using ${config.useGet ? "GET" : "POST"} ${endpoint} and headers ${JSON.stringify(config.headers)}`,
757
- task: async (ctx) => {
758
- ctx.schema = processSchema(await fetchSchema({
759
- endpoint,
760
- usePost: !config.useGet,
761
- headers: config.headers
762
- }));
763
- }
764
- };
765
- } else if (config.schema) {
766
- const schema = config.schema;
767
- return {
768
- title: "loading schema",
769
- task: async (ctx) => {
770
- ctx.schema = processSchema(await loadSchema(schema, { loaders: [new GraphQLFileLoader()] }));
771
- try {
772
- assertValidSchema(ctx.schema);
773
- } catch (e) {
774
- if (e["message"] === "Query root type must be provided.") return;
775
- throw e;
776
- }
777
- }
778
- };
779
- } else throw new Error("either `endpoint`, `fetcher` or `schema` must be defined in the config");
780
- };
781
-
782
- //#endregion
783
- //#region src/main.ts
784
- const generate = (config) => {
785
- if (!config.output) throw new Error("`output` must be defined in the config");
786
- return new Listr([{
787
- title: `generating the client in \`${config.output}\``,
788
- task: () => new Listr([schemaTask(config), ...clientTasks(config)])
789
- }], {
790
- renderer: config.verbose ? "verbose" : "default",
791
- exitOnError: true
792
- }).run().catch((e) => {
793
- throw e?.errors?.[0];
794
- });
795
- };
796
-
797
- //#endregion
798
- export { generate as t };