Skip to content

Commit fc63a7d

Browse files
committed
✨ added gql parser
1 parent 8cbfd3b commit fc63a7d

File tree

9 files changed

+680
-200
lines changed

9 files changed

+680
-200
lines changed

README.md

+31
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ Simplier approach to GraphQL parsing. Using graphql-js library and parsing AST t
66

77
## How it works
88

9+
### SDL GraphQL
10+
911
It creates very simple `ParserTree` from GraphQL schema
1012

1113
```js
@@ -27,9 +29,38 @@ const parsedSchema = Parser.parse(schemaFileContents);
2729
const graphqlString = TreeToGraphQL.parse(parsedSchema);
2830
```
2931

32+
### GQL
33+
34+
```js
35+
import { parseGql } from 'graphql-js-tree';
36+
37+
const schemaFileContents = `
38+
type Query{
39+
hello: String!
40+
}
41+
schema{
42+
query: Query
43+
}
44+
`;
45+
46+
const gqlQuery = `
47+
query MyQuery{
48+
hello
49+
}
50+
`;
51+
52+
const parsedTrees = parseGql(gqlQuery, schemaFileContents);
53+
54+
// Backwards
55+
56+
const gqlString = parseGqlTrees(parsedTrees);
57+
```
58+
3059
## Table of contents
3160

3261
- [How it works](#how-it-works)
62+
- [SDL GraphQL](#sdl-graphql)
63+
- [GQL](#gql)
3364
- [Table of contents](#table-of-contents)
3465
- [License](#license)
3566
- [Support](#support)

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "graphql-js-tree",
3-
"version": "0.3.1",
3+
"version": "0.3.2",
44
"private": false,
55
"license": "MIT",
66
"description": "GraphQL Parser providing simplier structure",

src/GqlParser/GqlParserTreeToGql.ts

+24-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1+
import { parseGql } from '@/GqlParser';
12
import { getValueAsGqlStringNode } from '@/GqlParser/valueNode';
23
import { Instances, TypeSystemDefinition } from '@/Models';
34
import { GqlParserTree, VariableDefinitionWithoutLoc } from '@/Models/GqlParserTree';
45
import { compileType } from '@/shared';
5-
export const GqlParserTreeToGql = (mainTree: GqlParserTree) => {
6+
export const parseGqlTree = (mainTree: GqlParserTree) => {
67
const generateName = (tree: GqlParserTree): string => {
7-
return `${
8-
tree.operation
9-
? `${tree.operation}${tree.name ? ` ${tree.name}` : ''}${generateVariableDefinitions(tree)}`
10-
: tree.name
11-
}`;
8+
if (tree.operation) {
9+
return `${tree.operation}${tree.name ? ` ${tree.name}` : ''}${generateVariableDefinitions(tree)}`;
10+
}
11+
if (tree.fragment) {
12+
return `fragment ${tree.name} on ${tree.node.name}`;
13+
}
14+
if (tree.fragmentSpread) {
15+
return `...${tree.name}`;
16+
}
17+
if (tree.inlineFragment) {
18+
return `...${tree.name ? ` on ${tree.name}` : ''}`;
19+
}
20+
return tree.name || '';
1221
};
1322
const generateChildren = (tree: GqlParserTree): string => {
1423
return `${tree.children ? `{\n ${tree.children.map(generateGql).join('\n ')}\n}` : ''}`;
@@ -32,6 +41,10 @@ export const GqlParserTreeToGql = (mainTree: GqlParserTree) => {
3241
return generateGql(mainTree);
3342
};
3443

44+
export const parseGqlTrees = (trees: GqlParserTree[]) => {
45+
return trees.map(parseGqlTree).join('\n');
46+
};
47+
3548
export const enrichFieldNodeWithVariables = (
3649
fieldTree: GqlParserTree,
3750
variableDefinitionsUpdate: (
@@ -92,3 +105,8 @@ export const enrichWholeTreeWithVars = (mainTree: GqlParserTree): GqlParserTree
92105
};
93106
return { ...recursiveEnrich(mainTree), variableDefinitions };
94107
};
108+
109+
export const enrichGqlQueryWithAllVars = (query: string, schema: string) => {
110+
const trees = parseGql(query, schema);
111+
return parseGqlTrees(trees.map(enrichWholeTreeWithVars));
112+
};

src/GqlParser/index.ts

+68-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { getValueWithoutLoc } from '@/GqlParser/valueNode';
2-
import { OperationType, ParserField } from '@/Models';
2+
import { OperationType, ParserField, TypeDefinition } from '@/Models';
33
import { GqlParserTree, VariableDefinitionWithoutLoc } from '@/Models/GqlParserTree';
44
import { Parser } from '@/Parser';
55
import { TypeResolver } from '@/Parser/typeResolver';
6-
import { compileType } from '@/shared';
6+
import { compileType, getTypeName } from '@/shared';
77
import {
88
DefinitionNode,
99
parse,
@@ -13,10 +13,14 @@ import {
1313
DirectiveNode,
1414
FieldNode,
1515
VariableDefinitionNode,
16+
FragmentDefinitionNode,
17+
FragmentSpreadNode,
18+
InlineFragmentNode,
1619
} from 'graphql';
17-
export const GqlParser = (gql: string, schema: string) => {
20+
export const parseGql = (gql: string, schema: string) => {
1821
const { definitions } = parse(schema + '\n' + gql);
1922
const ops = definitions.filter(onlyOperations);
23+
const frags = definitions.filter(onlyFragments);
2024
const { nodes } = Parser.parse(schema);
2125

2226
const composeDefinition = (d: OperationDefinitionNode): GqlParserTree => {
@@ -42,7 +46,21 @@ export const GqlParser = (gql: string, schema: string) => {
4246
variableDefinitions: d.variableDefinitions.map((vd) => composeVariableDefinition(vd)),
4347
}
4448
: {}),
45-
children: d.selectionSet.selections.filter(onlyFieldNodes).map((s) => composeSelectionNode(s, node)),
49+
children: d.selectionSet.selections.map((s) => composeSelectionNode(s, node)),
50+
};
51+
};
52+
53+
const composeFragment = (f: FragmentDefinitionNode): GqlParserTree => {
54+
const node = nodes.find((n) => n.name == f.typeCondition.name.value);
55+
if (!node) {
56+
throw new Error(`Type ${f.typeCondition.name.value} does not exist in schema`);
57+
}
58+
return {
59+
node,
60+
fragment: true,
61+
name: f.name.value,
62+
...(f.directives?.length ? { directives: f.directives.map((a) => composeDirectiveNode(a, node)) } : {}),
63+
children: f.selectionSet.selections.map((s) => composeSelectionNode(s, node)),
4664
};
4765
};
4866

@@ -74,29 +92,66 @@ export const GqlParser = (gql: string, schema: string) => {
7492
return {
7593
node,
7694
name: a.name.value,
77-
arguments: a.arguments?.map((a) => composeArgumentNode(a, node)),
95+
...(a.arguments?.length ? { arguments: a.arguments?.map((ar) => composeArgumentNode(ar, node)) } : {}),
7896
};
7997
};
8098

81-
const composeSelectionNode = (s: FieldNode, parentNode: ParserField): GqlParserTree => {
99+
const composeFragmentSpread = (f: FragmentSpreadNode, node: ParserField): GqlParserTree => {
100+
return {
101+
node,
102+
name: f.name.value,
103+
...(f.directives?.length ? { directives: f.directives?.map((a) => composeDirectiveNode(a, node)) } : {}),
104+
fragmentSpread: true,
105+
};
106+
};
107+
const composeInlineFragment = (f: InlineFragmentNode, node: ParserField): GqlParserTree => {
108+
const chosenNode = nodes.find((n) => n.name === f.typeCondition?.name.value);
109+
const rightNode = chosenNode || node;
110+
return {
111+
node: rightNode,
112+
name: rightNode.name,
113+
...(f.directives?.length ? { directives: f.directives?.map((a) => composeDirectiveNode(a, rightNode)) } : {}),
114+
inlineFragment: true,
115+
children: f.selectionSet.selections.map((s) => composeSelectionNode(s, rightNode)),
116+
};
117+
};
118+
119+
const composeSelectionNode = (s: SelectionNode, node: ParserField): GqlParserTree => {
120+
if (s.kind === 'Field') return composeFieldNode(s, node);
121+
if (s.kind === 'FragmentSpread') return composeFragmentSpread(s, node);
122+
return composeInlineFragment(s, node);
123+
};
124+
125+
const composeFieldNode = (s: FieldNode, parentNode: ParserField): GqlParserTree => {
82126
const fieldNode = parentNode.args.find((a) => a.name === s.name.value);
83127
if (!fieldNode) {
84-
throw new Error('Field does not exist in schema');
128+
throw new Error(`Field "${s.name.value}" does not exist in "${parentNode.name}" node`);
85129
}
130+
const passParentDown = getTypeName(fieldNode.type.fieldType);
131+
const isParentObjectNode = nodes.find(
132+
(n) =>
133+
n.name === passParentDown &&
134+
(n.data.type === TypeDefinition.ObjectTypeDefinition ||
135+
n.data.type === TypeDefinition.UnionTypeDefinition ||
136+
n.data.type === TypeDefinition.InterfaceTypeDefinition),
137+
);
138+
86139
return {
87140
node: fieldNode,
88141
name: s.name.value,
89-
arguments: s.arguments?.map((a) => composeArgumentNode(a, fieldNode)),
90-
directives: s.directives?.map((a) => composeDirectiveNode(a, fieldNode)),
91-
children: s.selectionSet?.selections.filter(onlyFieldNodes).map((a) => composeSelectionNode(a, fieldNode)),
142+
...(s.arguments?.length ? { arguments: s.arguments?.map((a) => composeArgumentNode(a, fieldNode)) } : {}),
143+
...(s.directives?.length ? { directives: s.directives?.map((a) => composeDirectiveNode(a, fieldNode)) } : {}),
144+
...(s.selectionSet?.selections.length
145+
? { children: s.selectionSet.selections.map((s) => composeSelectionNode(s, isParentObjectNode || fieldNode)) }
146+
: {}),
92147
} as GqlParserTree;
93148
};
94-
return ops.map((o) => composeDefinition(o));
149+
return [...frags.map((f) => composeFragment(f)), ...ops.map((o) => composeDefinition(o))];
95150
};
96151

97152
const onlyOperations = (definition: DefinitionNode): definition is OperationDefinitionNode => {
98153
return definition.kind === 'OperationDefinition';
99154
};
100-
const onlyFieldNodes = (definition: SelectionNode): definition is FieldNode => {
101-
return definition.kind === 'Field';
155+
const onlyFragments = (definition: DefinitionNode): definition is FragmentDefinitionNode => {
156+
return definition.kind === 'FragmentDefinition';
102157
};

src/Models/GqlParserTree.ts

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export type GqlParserTree = {
3838
arguments?: GqlParserTree[];
3939
directives?: GqlParserTree[];
4040
fragment?: boolean;
41+
fragmentSpread?: boolean;
42+
inlineFragment?: boolean;
4143
value?: ValueNodeWithoutLoc;
4244
operation?: OperationType;
4345
variableDefinitions?: VariableDefinitionWithoutLoc[];

0 commit comments

Comments
 (0)