Skip to content

Commit 98085ed

Browse files
committed
✨ Added schema node and schema extension node
1 parent cd6e024 commit 98085ed

File tree

13 files changed

+517
-102
lines changed

13 files changed

+517
-102
lines changed

Diff for: src/GqlParser/index.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getValueWithoutLoc } from '@/GqlParser/valueNode';
2-
import { OperationType, ParserField, TypeDefinition } from '@/Models';
2+
import { OperationType, ParserField, TypeDefinition, TypeSystemDefinition } from '@/Models';
33
import { GqlParserTree, VariableDefinitionWithoutLoc } from '@/Models/GqlParserTree';
44
import { Parser } from '@/Parser';
55
import { TypeResolver } from '@/Parser/typeResolver';
@@ -24,9 +24,16 @@ export const parseGql = (gql: string, schema: string) => {
2424
const { nodes } = Parser.parse(schema);
2525

2626
const composeDefinition = (d: OperationDefinitionNode): GqlParserTree => {
27-
const node = nodes.find((n) => n.type.operations?.includes(d.operation as OperationType));
27+
const schemaNode = nodes.find((n) => n.data.type === TypeSystemDefinition.SchemaDefinition);
28+
const operationField = schemaNode?.args.find((a) => a.name === d.operation);
29+
if (!operationField) {
30+
console.log(JSON.stringify(schemaNode, null, 2));
31+
throw new Error(`Operation ${d.name?.value} does not exist in schema`);
32+
}
33+
const operationType = getTypeName(operationField.type.fieldType);
34+
const node = nodes.find((n) => n.name === operationType);
2835
if (!node) {
29-
throw new Error(`Operation ${d.name} does not exist in schema`);
36+
throw new Error(`Operation ${d.name?.value} does not exist in schema`);
3037
}
3138
return {
3239
name: d.name?.value,

Diff for: src/Models/ParserTree.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { Directive, OperationType, FieldType, Value } from './Spec';
1+
import { Directive, FieldType, Value } from './Spec';
22
import { GraphQLNodeParams } from './Types';
33

44
export interface ParserField {
55
name: string;
66
id: string;
77
type: {
88
fieldType: FieldType;
9-
operations?: OperationType[];
109
directiveOptions?: Directive[];
1110
};
1211
data: GraphQLNodeParams;

Diff for: src/Models/Types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
TypeDefinition,
88
TypeExtension,
99
TypeSystemDefinition,
10+
TypeSystemExtension,
1011
Value,
1112
ValueDefinition,
1213
} from './Spec';
@@ -25,6 +26,7 @@ export type AllTypes =
2526
| TypeSystemDefinition
2627
| TypeSystemDefinitionDisplayStrings
2728
| TypeExtension
29+
| TypeSystemExtension
2830
| Instances
2931
| Helpers
3032
| Type;

Diff for: src/Parser/index.ts

+124-52
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
import {
22
buildASTSchema,
3-
DefinitionNode,
43
DocumentNode,
54
GraphQLSchema,
65
isTypeSystemDefinitionNode,
76
isTypeSystemExtensionNode,
87
parse,
8+
SchemaDefinitionNode,
9+
SchemaExtensionNode,
10+
TypeDefinitionNode,
11+
TypeSystemDefinitionNode,
912
} from 'graphql';
1013
import { ParserField, ParserTree, TypeDefinitionDisplayMap, Options, kindAsAllTypes } from '@/Models';
11-
import { Directive, Helpers, OperationType, TypeDefinition, TypeExtension } from '@/Models/Spec';
14+
import {
15+
Directive,
16+
Helpers,
17+
TypeDefinition,
18+
TypeExtension,
19+
TypeSystemDefinition,
20+
TypeSystemExtension,
21+
} from '@/Models/Spec';
1222
import { TypeResolver } from './typeResolver';
1323
import { ParserUtils } from './ParserUtils';
14-
import { createParserField, generateNodeId } from '@/shared';
24+
import { createParserField, createSchemaDefinition, generateNodeId } from '@/shared';
1525
export class Parser {
1626
static findComments(schema: string): string[] {
1727
const stripDocs = schema
@@ -29,35 +39,97 @@ export class Parser {
2939
* @param schema
3040
*/
3141
static importSchema = (schema: string): GraphQLSchema => buildASTSchema(parse(schema));
32-
static documentDefinitionToSerializedNodeTree = (d: DefinitionNode): ParserField | undefined => {
42+
static documentDefinitionToSerializedNodeTree = (
43+
d: TypeSystemDefinitionNode | TypeDefinitionNode | SchemaDefinitionNode | SchemaExtensionNode,
44+
): ParserField | undefined => {
3345
if (isTypeSystemDefinitionNode(d) || isTypeSystemExtensionNode(d)) {
3446
const args = TypeResolver.resolveFieldsFromDefinition(d);
35-
if ('name' in d) {
36-
const interfaces = 'interfaces' in d && d.interfaces ? d.interfaces.map((i) => i.name.value) : [];
37-
const directives = 'directives' in d && d.directives ? TypeResolver.iterateDirectives(d.directives) : [];
38-
47+
const interfaces = 'interfaces' in d && d.interfaces ? d.interfaces.map((i) => i.name.value) : [];
48+
const directives = 'directives' in d && d.directives ? TypeResolver.iterateDirectives(d.directives) : [];
49+
if (d.kind === 'SchemaDefinition') {
3950
return {
40-
name: d.name.value,
41-
type:
42-
d.kind === 'DirectiveDefinition'
43-
? {
44-
fieldType: { name: TypeDefinitionDisplayMap[d.kind], type: Options.name },
45-
directiveOptions: d.locations.map((l) => l.value as Directive),
46-
}
47-
: {
48-
fieldType: { name: TypeDefinitionDisplayMap[d.kind], type: Options.name },
51+
name: 'schema',
52+
args: d.operationTypes.map((ot) =>
53+
createParserField({
54+
name: ot.operation,
55+
data: {
56+
type: TypeSystemDefinition.FieldDefinition,
57+
},
58+
type: {
59+
fieldType: {
60+
name: ot.type.name.value,
61+
type: Options.name,
4962
},
63+
},
64+
}),
65+
),
5066
data: {
51-
type: kindAsAllTypes(d.kind),
67+
type: TypeSystemDefinition.SchemaDefinition,
68+
},
69+
directives: d.directives ? TypeResolver.iterateDirectives(d.directives) : [],
70+
id: generateNodeId('schema', kindAsAllTypes(d.kind), []),
71+
interfaces: [],
72+
type: {
73+
fieldType: {
74+
type: Options.name,
75+
name: 'schema',
76+
},
5277
},
53-
54-
...('description' in d && d.description?.value ? { description: d.description.value } : {}),
55-
interfaces,
56-
directives,
57-
args,
58-
id: generateNodeId(d.name.value, kindAsAllTypes(d.kind), args),
5978
};
6079
}
80+
if (d.kind === 'SchemaExtension') {
81+
return {
82+
name: 'schema',
83+
data: {
84+
type: TypeSystemExtension.SchemaExtension,
85+
},
86+
directives: d.directives ? TypeResolver.iterateDirectives(d.directives) : [],
87+
interfaces: [],
88+
type: {
89+
fieldType: {
90+
type: Options.name,
91+
name: 'schema',
92+
},
93+
},
94+
args:
95+
d.operationTypes?.map((ot) =>
96+
createParserField({
97+
name: ot.operation,
98+
data: {
99+
type: TypeSystemDefinition.FieldDefinition,
100+
},
101+
type: {
102+
fieldType: {
103+
name: ot.type.name.value,
104+
type: Options.name,
105+
},
106+
},
107+
}),
108+
) || [],
109+
id: generateNodeId('schema', kindAsAllTypes(d.kind), []),
110+
};
111+
}
112+
return {
113+
name: d.name.value,
114+
type:
115+
d.kind === 'DirectiveDefinition'
116+
? {
117+
fieldType: { name: TypeDefinitionDisplayMap[d.kind], type: Options.name },
118+
directiveOptions: d.locations.map((l) => l.value as Directive),
119+
}
120+
: {
121+
fieldType: { name: TypeDefinitionDisplayMap[d.kind], type: Options.name },
122+
},
123+
data: {
124+
type: kindAsAllTypes(d.kind),
125+
},
126+
127+
...('description' in d && d.description?.value ? { description: d.description.value } : {}),
128+
interfaces,
129+
directives,
130+
args,
131+
id: generateNodeId(d.name.value, kindAsAllTypes(d.kind), args),
132+
};
61133
}
62134
};
63135
/**
@@ -84,25 +156,19 @@ export class Parser {
84156
if (!parsedSchema) {
85157
throw new Error('Cannot parse the schema');
86158
}
87-
const operations: { Query?: string; Mutation?: string; Subscription?: string } = {};
88159

89-
const schemaDefinition = parsedSchema.definitions.find((d) => d.kind === 'SchemaDefinition');
90-
if (schemaDefinition && 'operationTypes' in schemaDefinition) {
91-
schemaDefinition.operationTypes?.forEach((ot) => {
92-
if (ot.operation === 'query') {
93-
operations.Query = ot.type.name.value;
94-
}
95-
if (ot.operation === 'mutation') {
96-
operations.Mutation = ot.type.name.value;
97-
}
98-
if (ot.operation === 'subscription') {
99-
operations.Subscription = ot.type.name.value;
100-
}
101-
});
102-
}
103160
const nodes = parsedSchema.definitions
104-
.filter((t) => 'name' in t && t.name && !excludeRoots.includes(t.name.value))
105-
.map(Parser.documentDefinitionToSerializedNodeTree)
161+
.filter((t) =>
162+
t.kind === 'SchemaExtension' || t.kind === 'SchemaDefinition'
163+
? true
164+
: 'name' in t && t.name && !excludeRoots.includes(t.name.value),
165+
)
166+
.filter((t) => t.kind !== 'FragmentDefinition')
167+
.map((t) =>
168+
Parser.documentDefinitionToSerializedNodeTree(
169+
t as TypeDefinitionNode | SchemaDefinitionNode | TypeSystemDefinitionNode | SchemaExtensionNode,
170+
),
171+
)
106172
.filter((d) => !!d) as ParserField[];
107173
const comments: ParserField[] = Parser.findComments(schema).map((description) =>
108174
createParserField({
@@ -124,17 +190,6 @@ export class Parser {
124190
};
125191
const allInterfaceNodes = nodeTree.nodes.filter((n) => n.data.type === TypeDefinition.InterfaceTypeDefinition);
126192
nodeTree.nodes.forEach((n) => {
127-
if (n.data.type === TypeDefinition.ObjectTypeDefinition) {
128-
if (operations.Query ? operations.Query === n.name : n.name === 'Query') {
129-
n.type.operations = [OperationType.query];
130-
}
131-
if (operations.Mutation ? operations.Mutation === n.name : n.name === 'Mutation') {
132-
n.type.operations = [OperationType.mutation];
133-
}
134-
if (operations.Subscription ? operations.Subscription === n.name : n.name === 'Subscription') {
135-
n.type.operations = [OperationType.subscription];
136-
}
137-
}
138193
if (
139194
n.data.type === TypeDefinition.ObjectTypeDefinition ||
140195
n.data.type === TypeDefinition.InterfaceTypeDefinition
@@ -160,6 +215,23 @@ export class Parser {
160215
}
161216
}
162217
});
218+
const schemaNode = nodeTree.nodes.find((n) => n.data.type === TypeSystemDefinition.SchemaDefinition);
219+
if (!schemaNode) {
220+
const query = nodeTree.nodes.find((n) => n.name === 'Query')?.name;
221+
const mutation = nodeTree.nodes.find((n) => n.name === 'Mutation')?.name;
222+
const subscription = nodeTree.nodes.find((n) => n.name === 'Subscription')?.name;
223+
if (query || mutation || subscription) {
224+
nodeTree.nodes.push(
225+
createSchemaDefinition({
226+
operations: {
227+
query,
228+
mutation,
229+
subscription,
230+
},
231+
}),
232+
);
233+
}
234+
}
163235
return nodeTree;
164236
};
165237
static parseAddExtensions = (schema: string, excludeRoots: string[] = []): ParserTree => {

Diff for: src/Parser/typeResolver.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
FieldDefinitionNode,
55
InputValueDefinitionNode,
66
ObjectFieldNode,
7+
SchemaDefinitionNode,
78
TypeDefinitionNode,
89
TypeNode,
910
TypeSystemDefinitionNode,
@@ -266,7 +267,9 @@ export class TypeResolver {
266267
return fields;
267268
}
268269

269-
static resolveFieldsFromDefinition(n: TypeSystemDefinitionNode | TypeSystemExtensionNode): ParserField[] {
270+
static resolveFieldsFromDefinition(
271+
n: TypeSystemDefinitionNode | TypeSystemExtensionNode | SchemaDefinitionNode,
272+
): ParserField[] {
270273
if ('values' in n && n.values) {
271274
return n.values.map((v) => ({
272275
name: v.name.value,

Diff for: src/TreeToGraphQL/index.ts

+2-27
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,10 @@
1-
import { OperationType, ParserTree } from '@/Models';
1+
import { ParserTree } from '@/Models';
22
import { TemplateUtils } from './templates/TemplateUtils';
33

44
export class TreeToGraphQL {
55
static parse(parserTree: ParserTree): string {
66
const joinDefinitions = (...defintions: string[]): string => defintions.join('\n\n');
77
const alldefs = parserTree.nodes.map((a) => TemplateUtils.resolverForConnection(a));
8-
const schemaOperations: Record<OperationType, string | null> = {
9-
[OperationType.query]: null,
10-
[OperationType.mutation]: null,
11-
[OperationType.subscription]: null,
12-
};
13-
parserTree.nodes.forEach((n) => {
14-
const { operations } = n.type;
15-
if (operations && operations.length > 0) {
16-
if (operations.find((o) => o === OperationType.query)) {
17-
schemaOperations[OperationType.query] = n.name;
18-
}
19-
if (operations.find((o) => o === OperationType.mutation)) {
20-
schemaOperations[OperationType.mutation] = n.name;
21-
}
22-
if (operations.find((o) => o === OperationType.subscription)) {
23-
schemaOperations[OperationType.subscription] = n.name;
24-
}
25-
}
26-
});
27-
const resolvedOperations = Object.keys(schemaOperations)
28-
.filter((k) => schemaOperations[k as OperationType])
29-
.map((k) => `\t${k}: ${schemaOperations[k as OperationType]}`)
30-
.join(',\n');
31-
return joinDefinitions(...alldefs)
32-
.concat('\n')
33-
.concat(schemaOperations[OperationType.query] ? `schema{\n${resolvedOperations}\n}` : '');
8+
return joinDefinitions(...alldefs).concat('\n');
349
}
3510
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ParserField } from '@/Models';
2+
import { TemplateUtils } from '@/TreeToGraphQL/templates/TemplateUtils';
3+
import { getTypeName } from '@/shared';
4+
5+
/**
6+
* resolve comment node
7+
*/
8+
export class SchemaDefinitionTemplate {
9+
static resolve(f: ParserField): string {
10+
return `schema${TemplateUtils.resolveDirectives(f.directives)}{${f.args.map(
11+
(a) => `${a.name}: ${getTypeName(a.type.fieldType)}`,
12+
)}}`;
13+
}
14+
}
15+
16+
export class SchemaExtensionTemplate {
17+
static resolve(f: ParserField): string {
18+
return `extend schema${TemplateUtils.resolveDirectives(f.directives)}${
19+
f.args.length ? `{${f.args.map((a) => `\n\t${a.name}: ${getTypeName(a.type.fieldType)}`)}}` : ''
20+
}`;
21+
}
22+
}

Diff for: src/TreeToGraphQL/templates/TemplateUtils.ts

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
TypeDefinition,
66
TypeExtension,
77
TypeSystemDefinition,
8+
TypeSystemExtension,
89
ValueDefinition,
910
} from '@/Models';
1011
import { compileType } from '@/shared';
@@ -17,6 +18,7 @@ import { FieldTemplate } from './FieldTemplate';
1718
import { InputValueTemplate } from './InputValueTemplate';
1819
import { TypeDefinitionsTemplates } from './TypeDefinitionsTemplates';
1920
import { UnionMemberTemplate } from './UnionMemberTemplate';
21+
import { SchemaDefinitionTemplate, SchemaExtensionTemplate } from '@/TreeToGraphQL/templates/SchemaDefinitionTemplate';
2022

2123
const dedent = new RegExp('\n([\t ]*)', 'gm');
2224

@@ -103,6 +105,10 @@ export class TemplateUtils {
103105
return TypeDefinitionsTemplates.resolve(f);
104106
}
105107
switch (type) {
108+
case TypeSystemDefinition.SchemaDefinition:
109+
return SchemaDefinitionTemplate.resolve(f);
110+
case TypeSystemExtension.SchemaExtension:
111+
return SchemaExtensionTemplate.resolve(f);
106112
case TypeSystemDefinition.FieldDefinition:
107113
return FieldTemplate.resolve(f, prefix);
108114
case TypeSystemDefinition.DirectiveDefinition:

Diff for: src/__tests__/GqlParser/GqlParserTreeToGql.spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ const queryNode = createRootField({
6060
}),
6161
],
6262
});
63-
queryNode.type.operations = [OperationType.query];
6463

6564
const personNode = createRootField({
6665
name: 'Person',

0 commit comments

Comments
 (0)