@devticon-os/graphql-codegen-axios 0.2.17 → 0.3.0-test

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/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@devticon-os/graphql-codegen-axios",
3
- "version": "0.2.17",
3
+ "version": "0.3.0-test",
4
4
  "license": "MIT",
5
- "main": "src/index.js",
5
+ "main": "src/index.ts",
6
6
  "repository": "https://github.com/devticon/graphql-codegen-axios",
7
7
  "author": {
8
8
  "name": "krs",
@@ -12,13 +12,20 @@
12
12
  "src"
13
13
  ],
14
14
  "scripts": {
15
- "start": "node src/index.js"
15
+ "start": "node src/index.ts",
16
+ "build": "tsc",
17
+ "build:watch": "tsc --watch"
16
18
  },
17
19
  "peerDependencies": {
18
20
  "axios": "^1.3.3",
19
21
  "graphql": "^16.6.0"
20
22
  },
21
23
  "devDependencies": {
22
- "prettier": "^2.8.4"
24
+ "@types/node": "^18.15.0",
25
+ "axios": "^1.3.4",
26
+ "graphql": "^16.6.0",
27
+ "prettier": "^2.8.4",
28
+ "ts-node": "^10.9.1",
29
+ "typescript": "^4.9.5"
23
30
  }
24
31
  }
package/src/_types.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { DocumentNode, OperationDefinitionNode } from 'graphql/language/ast';
2
+ import { GraphQLSchema } from 'graphql/type';
3
+
4
+ export type TsType = {
5
+ fields: TsTypeField[];
6
+ unions?: string[];
7
+ };
8
+
9
+ export type NamedTsType = TsType & {
10
+ name: string;
11
+ };
12
+ export type TsTypeFieldBase = {
13
+ name: string;
14
+ isList: boolean;
15
+ isNullable: boolean;
16
+ };
17
+ export type TsTypeFieldInLine = TsTypeFieldBase & { kind: 'inLine'; type: string };
18
+ export type TsTypeFieldObject = TsTypeFieldBase & { kind: 'object' } & TsType;
19
+ export type TsTypeField = TsTypeFieldInLine | TsTypeFieldObject;
20
+ export type Query = {
21
+ name: string;
22
+ ast: OperationDefinitionNode;
23
+ allFragments: TsType[];
24
+ };
25
+ export type Config = {
26
+ autoSingleResult?: boolean;
27
+ prettier?: boolean;
28
+ emitDirectives?: string | boolean;
29
+ emitSchema?: string | boolean;
30
+
31
+ suffix?: {
32
+ fragment?: string;
33
+ input?: string;
34
+ enum?: string;
35
+ };
36
+ scalars?: Record<string, string>;
37
+ };
38
+ export type CodegenDocuments = { document: DocumentNode }[];
39
+ export type CodegenPlugin = {
40
+ plugin: (schema: GraphQLSchema, documents: CodegenDocuments, config: Config) => string | Promise<string>;
41
+ addToSchema?: string;
42
+ };
43
+
44
+ export type Operation = {
45
+ name: string;
46
+ definition: OperationDefinitionNode;
47
+ singleResultKey?: string;
48
+ variables?: NamedTsType;
49
+ results: NamedTsType;
50
+ fragments: string[];
51
+ directives: Directive[];
52
+ };
53
+
54
+ export type Directive = {
55
+ name: string;
56
+ path: string;
57
+ args: Record<string, string>;
58
+ };
59
+
60
+ export type FieldsMap = Record<string, TsTypeFieldBase & { fields: FieldsMap }>;
package/src/enums.ts ADDED
@@ -0,0 +1,50 @@
1
+ import {
2
+ assertEnumType,
3
+ assertObjectType,
4
+ GraphQLEnumType,
5
+ GraphQLInputObjectType,
6
+ GraphQLSchema,
7
+ isEnumType,
8
+ } from 'graphql/type';
9
+ import { getNamedType } from 'graphql/type/definition';
10
+ import { FragmentDefinitionNode } from 'graphql/language/ast';
11
+ import { Kind } from 'graphql/language';
12
+ import { Operation } from './_types';
13
+
14
+ export const findUsageEnums = (
15
+ inputs: GraphQLInputObjectType[],
16
+ fragments: FragmentDefinitionNode[],
17
+ operations: Operation[],
18
+ schema: GraphQLSchema,
19
+ ): GraphQLEnumType[] => {
20
+ const enums = new Set<GraphQLEnumType>();
21
+ for (let input of inputs) {
22
+ for (let field of Object.values(input.getFields())) {
23
+ if (isEnumType(getNamedType(field.type))) {
24
+ enums.add(assertEnumType(getNamedType(field.type)));
25
+ }
26
+ }
27
+ }
28
+ for (let fragment of fragments) {
29
+ const parent = assertObjectType(schema.getType(fragment.typeCondition.name.value));
30
+ for (let selection of fragment.selectionSet.selections) {
31
+ if (selection.kind === Kind.FIELD) {
32
+ const parentField = parent.getFields()[selection.name.value];
33
+ if (isEnumType(getNamedType(parentField.type))) {
34
+ enums.add(assertEnumType(getNamedType(parentField.type)));
35
+ }
36
+ }
37
+ }
38
+ }
39
+ for (let operation of operations) {
40
+ for (let field of operation.variables?.fields || []) {
41
+ if (field.kind === 'inLine') {
42
+ const type = schema.getType(field.type);
43
+ if (isEnumType(getNamedType(type))) {
44
+ enums.add(assertEnumType(getNamedType(type)));
45
+ }
46
+ }
47
+ }
48
+ }
49
+ return [...enums];
50
+ };
@@ -0,0 +1,15 @@
1
+ import { FragmentDefinitionNode } from 'graphql/language/ast';
2
+ import { CodegenDocuments } from './_types';
3
+ import { Kind } from 'graphql/language';
4
+
5
+ export const findUsageFragments = (documents: CodegenDocuments) => {
6
+ const fragments: FragmentDefinitionNode[] = [];
7
+ for (let { document } of documents) {
8
+ for (const definition of document.definitions) {
9
+ if (definition.kind === Kind.FRAGMENT_DEFINITION) {
10
+ fragments.push(definition);
11
+ }
12
+ }
13
+ }
14
+ return fragments;
15
+ };
package/src/graphql.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { SelectionNode, TypeNode } from 'graphql/language/ast';
2
+ import { Config, TsType } from './_types';
3
+ import { getNamedType, GraphQLType } from 'graphql/type/definition';
4
+ import {
5
+ assertObjectType,
6
+ getNullableType,
7
+ GraphQLList,
8
+ GraphQLNonNull,
9
+ GraphQLObjectType,
10
+ GraphQLScalarType,
11
+ GraphQLSchema,
12
+ isListType,
13
+ isNonNullType,
14
+ isNullableType,
15
+ } from 'graphql/type';
16
+ import { Kind } from 'graphql/language';
17
+
18
+ export const selectionSetToTsType = (
19
+ parent: GraphQLObjectType,
20
+ selections: SelectionNode[] | readonly SelectionNode[],
21
+ config: Config,
22
+ ): TsType => {
23
+ const type: TsType = {
24
+ fields: [],
25
+ unions: [],
26
+ };
27
+ for (let selection of selections) {
28
+ if (selection.kind === Kind.FRAGMENT_SPREAD) {
29
+ type.unions.push(selection.name.value);
30
+ }
31
+
32
+ if (selection.kind === Kind.FIELD) {
33
+ const parentField = parent.getFields()[selection.name.value];
34
+ if (selection.selectionSet && selection.selectionSet.selections.length) {
35
+ const nestedParent = assertObjectType(getNamedType(parentField.type));
36
+ type.fields.push({
37
+ kind: 'object',
38
+ name: selection.alias?.value || selection.name.value,
39
+ ...selectionSetToTsType(nestedParent, selection.selectionSet.selections, config),
40
+ ...getGraphqlTypeWrappers(parentField.type),
41
+ });
42
+ } else {
43
+ type.fields.push({
44
+ kind: 'inLine',
45
+ name: selection.alias?.value || selection.name.value,
46
+ type: graphqlTypeToTypescript(parentField.type, config),
47
+ ...getGraphqlTypeWrappers(parentField.type),
48
+ });
49
+ }
50
+ }
51
+ }
52
+ return type;
53
+ };
54
+
55
+ export const getGraphqlTypeWrappers = (type: GraphQLType) => {
56
+ return {
57
+ isList: isListType(isNonNullType(type) ? getNullableType(type) : type),
58
+ isNullable: isNullableType(type),
59
+ };
60
+ };
61
+
62
+ export const graphqlTypeToTypescript = (type: GraphQLType, config: Config) => {
63
+ const namedType = getNamedType(type);
64
+
65
+ if (namedType instanceof GraphQLScalarType) {
66
+ return `Scalar["${namedType.name}"]`;
67
+ }
68
+
69
+ switch (namedType.astNode.kind) {
70
+ case Kind.ENUM_TYPE_DEFINITION:
71
+ return namedType.name + (config?.suffix?.enum || '');
72
+ case Kind.INPUT_OBJECT_TYPE_DEFINITION:
73
+ return namedType.name + (config?.suffix?.input || '');
74
+ default:
75
+ throw new Error(`unsupported kind: ${namedType.astNode.kind}`);
76
+ }
77
+ };
78
+
79
+ const typeNodeToGraphqlType = (
80
+ type: TypeNode,
81
+ schema: GraphQLSchema,
82
+ isNullable = true,
83
+ isList = false,
84
+ ): GraphQLType => {
85
+ if (type.kind === Kind.NON_NULL_TYPE) {
86
+ return typeNodeToGraphqlType(type.type, schema, false, isList);
87
+ }
88
+ if (type.kind === Kind.LIST_TYPE) {
89
+ return typeNodeToGraphqlType(type.type, schema, isNullable, true);
90
+ }
91
+
92
+ let gqlType: GraphQLType = schema.getType(type.name.value);
93
+ if (isList) {
94
+ gqlType = new GraphQLList(gqlType);
95
+ }
96
+ if (!isNullable) {
97
+ gqlType = new GraphQLNonNull(gqlType);
98
+ }
99
+
100
+ return gqlType;
101
+ };
package/src/index.ts ADDED
@@ -0,0 +1,70 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { findUsageInputs } from './input';
4
+ import { CodegenPlugin, Config } from './_types';
5
+ import {
6
+ printCreateSdkFunction,
7
+ printEnum,
8
+ printFragmentGql,
9
+ printFragmentType,
10
+ printHelpers,
11
+ printInput,
12
+ printOperationTypes,
13
+ printScalars,
14
+ } from './print';
15
+ import { findUsageEnums } from './enums';
16
+ import { findUsageFragments } from './fragments';
17
+ import { findUsageOperation, pluginDirectives } from './operation';
18
+ import { findScalars } from './scalar';
19
+ import { runPrettierIfExists } from './prettier';
20
+ import { printSchema } from 'graphql/utilities';
21
+
22
+ const configDefaults: Partial<Config> = {
23
+ autoSingleResult: true,
24
+ prettier: true,
25
+ };
26
+ const directives = pluginDirectives.map(d => `directive @${d} on FIELD`).join('\n');
27
+ const plugin: CodegenPlugin = {
28
+ plugin(schema, documents, config) {
29
+ try {
30
+ config = {
31
+ ...configDefaults,
32
+ ...config,
33
+ };
34
+ if (config.emitDirectives) {
35
+ const directivesPath = typeof config.emitDirectives === 'string' ? config.emitDirectives : 'directives.graphql';
36
+ fs.writeFileSync(path.join(directivesPath), directives);
37
+ }
38
+ if (config.emitSchema) {
39
+ const schemaPath = typeof config.emitSchema === 'string' ? config.emitSchema : 'schema.graphql';
40
+ fs.writeFileSync(path.join(schemaPath), printSchema(schema));
41
+ }
42
+
43
+ const imports = findUsageInputs(documents, schema);
44
+ const fragments = findUsageFragments(documents);
45
+ const operations = findUsageOperation(documents, schema, config);
46
+ const enums = findUsageEnums(imports, fragments, operations, schema);
47
+ const scalars = findScalars(schema);
48
+
49
+ return runPrettierIfExists(
50
+ config,
51
+ [
52
+ printHelpers(),
53
+ printScalars(scalars, config),
54
+ ...enums.map(e => printEnum(e, config)),
55
+ ...imports.map(i => printInput(i, config)),
56
+ ...fragments.map(f => printFragmentType(f, schema, config)),
57
+ ...fragments.map(f => printFragmentGql(f)),
58
+ ...operations.map(o => printOperationTypes(o, config)),
59
+ printCreateSdkFunction(operations, config),
60
+ ].join('\n'),
61
+ );
62
+ } catch (e) {
63
+ console.log(e);
64
+ process.exit(1);
65
+ }
66
+ },
67
+ addToSchema: directives,
68
+ };
69
+
70
+ module.exports = plugin;
package/src/input.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLSchema } from 'graphql/type';
2
+ import { getNamedType, GraphQLEnumType, GraphQLInputType, GraphQLScalarType } from 'graphql/type/definition';
3
+ import { CodegenDocuments, TsType } from './_types';
4
+ import { NamedTypeNode, TypeNode } from 'graphql/language/ast';
5
+ import { Kind } from 'graphql/language';
6
+
7
+ export const findUsageInputs = (documents: CodegenDocuments, schema: GraphQLSchema) => {
8
+ const inputs = new Set<GraphQLInputObjectType>();
9
+ for (let { document } of documents) {
10
+ for (let definition of document.definitions) {
11
+ if (definition.kind === Kind.OPERATION_DEFINITION) {
12
+ for (const variableDefinition of definition.variableDefinitions) {
13
+ const type = unpackVariableType(variableDefinition.type);
14
+ const name = type.name.value;
15
+ const input = findInputInSchema(name, schema);
16
+ if (input) {
17
+ inputs.add(input);
18
+ findDeepInputs(input, inputs);
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+
25
+ return [...inputs];
26
+ };
27
+
28
+ const findDeepInputs = (parent: GraphQLInputObjectType, imports = new Set<GraphQLInputObjectType>()) => {
29
+ for (let field of Object.values(parent.getFields())) {
30
+ const fieldType = getNamedType(field.type);
31
+ if (fieldType instanceof GraphQLInputObjectType) {
32
+ imports.add(fieldType);
33
+ findDeepInputs(fieldType, imports);
34
+ }
35
+ }
36
+ return imports;
37
+ };
38
+
39
+ export const findInputInSchema = (name: string, schema: GraphQLSchema) => {
40
+ const type = schema.getType(name);
41
+ if (type instanceof GraphQLInputObjectType) {
42
+ return type;
43
+ }
44
+ };
45
+
46
+ const unpackInputType = (type: GraphQLInputType): GraphQLScalarType | GraphQLEnumType | GraphQLInputObjectType => {
47
+ if (type instanceof GraphQLNonNull) {
48
+ return unpackInputType(type.ofType);
49
+ }
50
+ if (type instanceof GraphQLList) {
51
+ return unpackInputType(type.ofType);
52
+ }
53
+ return type;
54
+ };
55
+
56
+ const unpackVariableType = (type: TypeNode): NamedTypeNode => {
57
+ if (type.kind === Kind.NON_NULL_TYPE) {
58
+ return unpackVariableType(type.type);
59
+ }
60
+ if (type.kind === Kind.LIST_TYPE) {
61
+ return unpackVariableType(type.type);
62
+ }
63
+
64
+ return type;
65
+ };
@@ -0,0 +1,212 @@
1
+ import {
2
+ CodegenDocuments,
3
+ Config,
4
+ Directive,
5
+ FieldsMap,
6
+ NamedTsType,
7
+ Operation,
8
+ TsType,
9
+ TsTypeField,
10
+ TsTypeFieldObject,
11
+ } from './_types';
12
+ import { Kind } from 'graphql/language/kinds';
13
+ import { FieldNode, FragmentDefinitionNode, OperationTypeNode, SelectionNode, TypeNode } from 'graphql/language/ast';
14
+ import { capitalize } from './utils';
15
+ import { getGraphqlTypeWrappers, graphqlTypeToTypescript, selectionSetToTsType } from './graphql';
16
+ import { GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema } from 'graphql/type';
17
+ import { GraphQLType } from 'graphql/type/definition';
18
+ import { findUsageFragments } from './fragments';
19
+
20
+ export const pluginDirectives = ['firstOrFail', 'first', 'singleResult', 'required'];
21
+ export const findUsageOperation = (documents: CodegenDocuments, schema: GraphQLSchema, config: Config) => {
22
+ const allFragments = findUsageFragments(documents);
23
+ const operations: Operation[] = [];
24
+ for (let { document } of documents) {
25
+ for (let definition of document.definitions) {
26
+ if (definition.kind === Kind.OPERATION_DEFINITION) {
27
+ const name = definition.name.value;
28
+ let root: GraphQLObjectType;
29
+ if (definition.operation === OperationTypeNode.QUERY) {
30
+ root = schema.getQueryType();
31
+ } else if (definition.operation === OperationTypeNode.MUTATION) {
32
+ root = schema.getMutationType();
33
+ } else if (definition.operation === OperationTypeNode.SUBSCRIPTION) {
34
+ throw new Error('subscription operation is not supported');
35
+ }
36
+
37
+ const directives = findDirectives(definition.selectionSet.selections);
38
+ if (
39
+ config.autoSingleResult !== false &&
40
+ definition.selectionSet.selections.length === 1 &&
41
+ !directives.some(d => d.name === 'singleResult')
42
+ ) {
43
+ const fields = definition.selectionSet.selections.filter(f => f.kind === Kind.FIELD) as FieldNode[];
44
+ directives.push({
45
+ name: 'singleResult',
46
+ path: fields[0].name.value,
47
+ args: {},
48
+ });
49
+ }
50
+
51
+ let resultsType: NamedTsType = {
52
+ name: capitalize(name) + 'Results',
53
+ ...selectionSetToTsType(root, definition.selectionSet.selections, config),
54
+ };
55
+ for (let directive of directives) {
56
+ switch (directive.name) {
57
+ case 'first':
58
+ changeTsTypeField(resultsType, directive.path, { isList: false });
59
+ break;
60
+ case 'firstOrFail':
61
+ changeTsTypeField(resultsType, directive.path, { isList: false, isNullable: false });
62
+ break;
63
+ case 'required':
64
+ changeTsTypeField(resultsType, directive.path, { isNullable: false });
65
+ break;
66
+ }
67
+ }
68
+ const singleResult = directives.find(d => d.name === 'singleResult');
69
+
70
+ if (singleResult) {
71
+ const type = getNestedType(resultsType, singleResult.path);
72
+ resultsType = {
73
+ ...type,
74
+ name: resultsType.name,
75
+ };
76
+ }
77
+
78
+ operations.push({
79
+ name,
80
+ definition,
81
+ directives,
82
+ singleResultKey: singleResult ? singleResult.path : '',
83
+ variables: definition.variableDefinitions.length
84
+ ? {
85
+ name: capitalize(name) + 'Variables',
86
+ fields: definition.variableDefinitions.map(variableDefinition => {
87
+ const type = parseVariableNode(variableDefinition.type, schema);
88
+ return {
89
+ name: variableDefinition.variable.name.value,
90
+ kind: 'inLine',
91
+ type: graphqlTypeToTypescript(type, config),
92
+ ...getGraphqlTypeWrappers(type),
93
+ };
94
+ }),
95
+ }
96
+ : undefined,
97
+ results: resultsType,
98
+ fragments: findFragmentsUsedInSelection(definition.selectionSet.selections, allFragments),
99
+ });
100
+ }
101
+ }
102
+ }
103
+ return operations;
104
+ };
105
+
106
+ const findDirectives = (selections: readonly SelectionNode[], key = '') => {
107
+ const directives: Directive[] = [];
108
+ for (let selection of selections) {
109
+ if (selection.kind === Kind.FIELD) {
110
+ const path = key + selection.name.value;
111
+ for (let directive of selection.directives) {
112
+ if (pluginDirectives.includes(directive.name.value)) {
113
+ directives.push({
114
+ name: directive.name.value,
115
+ path,
116
+ args: directive.arguments.reduce((args, arg) => {
117
+ if ('value' in arg.value) {
118
+ args[arg.name.value] = arg.value.value;
119
+ }
120
+ return args;
121
+ }, {} as Record<string, any>),
122
+ });
123
+ }
124
+ }
125
+ if (selection.selectionSet?.selections.length) {
126
+ directives.push(...findDirectives(selection.selectionSet.selections, path + '.'));
127
+ }
128
+ }
129
+ }
130
+ return directives;
131
+ };
132
+
133
+ const getNestedType = (type: TsType, path: string) => {
134
+ let t: TsType = type;
135
+ for (let key of path.split('.')) {
136
+ const field = t.fields.find(f => f.name === key);
137
+ if (!field) {
138
+ throw new Error(`cannot find field ${key}`);
139
+ }
140
+ t = field as TsType;
141
+ }
142
+ return t;
143
+ };
144
+
145
+ const changeTsTypeField = <T extends any>(type: TsType, path: string, value: Partial<TsTypeField>) => {
146
+ const p = path.split('.');
147
+ let t: { fields: TsTypeField[] } = type;
148
+ while (p.length) {
149
+ const k = p.shift();
150
+ const f = t.fields.find(f => f.name === k);
151
+ if (!f) {
152
+ throw new Error(`cannot find field ${k}`);
153
+ }
154
+ if (p.length === 0) {
155
+ Object.assign(f, value);
156
+ } else if (f.kind === 'object') {
157
+ t = f;
158
+ } else {
159
+ throw new Error('logic error');
160
+ }
161
+ }
162
+ };
163
+
164
+ const toFieldsMap = (f: TsTypeFieldObject) => {
165
+ const fields: FieldsMap = {};
166
+ for (let field of f.fields) {
167
+ fields[field.name] = {
168
+ ...field,
169
+ fields: field.kind === 'object' ? toFieldsMap(field) : {},
170
+ };
171
+ }
172
+ return fields;
173
+ };
174
+
175
+ const parseVariableNode = (type: TypeNode, schema: GraphQLSchema, isNullable = true, isList = false): GraphQLType => {
176
+ if (type.kind === Kind.NON_NULL_TYPE) {
177
+ return parseVariableNode(type.type, schema, false, isList);
178
+ }
179
+ if (type.kind === Kind.LIST_TYPE) {
180
+ return parseVariableNode(type.type, schema, isNullable, true);
181
+ }
182
+
183
+ let gqlType: GraphQLType = schema.getType(type.name.value);
184
+ if (isList) {
185
+ gqlType = new GraphQLList(gqlType);
186
+ }
187
+ if (!isNullable) {
188
+ gqlType = new GraphQLNonNull(gqlType);
189
+ }
190
+
191
+ return gqlType;
192
+ };
193
+
194
+ const findFragmentsUsedInSelection = (
195
+ selections: readonly SelectionNode[],
196
+ allFragments: FragmentDefinitionNode[],
197
+ fragments = new Set<string>(),
198
+ ) => {
199
+ for (let selection of selections) {
200
+ if (selection.kind === Kind.FRAGMENT_SPREAD) {
201
+ const name = selection.name.value;
202
+ const fragment = allFragments.find(f => f.name.value === name);
203
+ fragments.add(name);
204
+ findFragmentsUsedInSelection(fragment.selectionSet.selections, allFragments, fragments);
205
+ }
206
+ if (selection.kind === Kind.FIELD && selection.selectionSet?.selections.length) {
207
+ findFragmentsUsedInSelection(selection.selectionSet.selections, allFragments, fragments);
208
+ }
209
+ }
210
+
211
+ return [...fragments];
212
+ };
@@ -0,0 +1,13 @@
1
+ import { Config } from './_types';
2
+
3
+ export const runPrettierIfExists = (config: Config, content: string) => {
4
+ if (config.prettier === false) {
5
+ return content;
6
+ }
7
+ try {
8
+ const prettier = require('prettier');
9
+ return prettier.format(content, { parser: 'typescript' });
10
+ } catch (e) {
11
+ return content;
12
+ }
13
+ };