1
- import { ListTypeNode , NonNullTypeNode , NamedTypeNode , TypeNode , ObjectTypeDefinitionNode } from 'graphql' ;
1
+ import {
2
+ ListTypeNode ,
3
+ NonNullTypeNode ,
4
+ NamedTypeNode ,
5
+ TypeNode ,
6
+ ObjectTypeDefinitionNode ,
7
+ visit ,
8
+ DocumentNode ,
9
+ DefinitionNode ,
10
+ NameNode ,
11
+ ASTNode ,
12
+ GraphQLSchema ,
13
+ } from 'graphql' ;
14
+ import { Graph } from 'graphlib' ;
2
15
3
16
export const isListType = ( typ ?: TypeNode ) : typ is ListTypeNode => typ ?. kind === 'ListType' ;
4
17
export const isNonNullType = ( typ ?: TypeNode ) : typ is NonNullTypeNode => typ ?. kind === 'NonNullType' ;
@@ -20,3 +33,134 @@ export const ObjectTypeDefinitionBuilder = (
20
33
return callback ( node ) ;
21
34
} ;
22
35
} ;
36
+
37
+ export const topologicalSortAST = ( schema : GraphQLSchema , ast : DocumentNode ) : DocumentNode => {
38
+ const dependencyGraph = new Graph ( ) ;
39
+ const targetKinds = [
40
+ 'ObjectTypeDefinition' ,
41
+ 'InputObjectTypeDefinition' ,
42
+ 'EnumTypeDefinition' ,
43
+ 'UnionTypeDefinition' ,
44
+ 'ScalarTypeDefinition' ,
45
+ ] ;
46
+
47
+ visit < DocumentNode > ( ast , {
48
+ enter : node => {
49
+ switch ( node . kind ) {
50
+ case 'ObjectTypeDefinition' :
51
+ case 'InputObjectTypeDefinition' : {
52
+ const typeName = node . name . value ;
53
+ dependencyGraph . setNode ( typeName ) ;
54
+
55
+ if ( node . fields ) {
56
+ node . fields . forEach ( field => {
57
+ if ( field . type . kind === 'NamedType' ) {
58
+ const dependency = field . type . name . value ;
59
+ const typ = schema . getType ( dependency ) ;
60
+ if ( typ ?. astNode ?. kind === undefined || ! targetKinds . includes ( typ . astNode . kind ) ) {
61
+ return ;
62
+ }
63
+ if ( ! dependencyGraph . hasNode ( dependency ) ) {
64
+ dependencyGraph . setNode ( dependency ) ;
65
+ }
66
+ dependencyGraph . setEdge ( typeName , dependency ) ;
67
+ }
68
+ } ) ;
69
+ }
70
+ break ;
71
+ }
72
+ case 'ScalarTypeDefinition' :
73
+ case 'EnumTypeDefinition' : {
74
+ dependencyGraph . setNode ( node . name . value ) ;
75
+ break ;
76
+ }
77
+ case 'UnionTypeDefinition' : {
78
+ const dependency = node . name . value ;
79
+ if ( ! dependencyGraph . hasNode ( dependency ) ) {
80
+ dependencyGraph . setNode ( dependency ) ;
81
+ }
82
+ node . types ?. forEach ( type => {
83
+ const dependency = type . name . value ;
84
+ const typ = schema . getType ( dependency ) ;
85
+ if ( typ ?. astNode ?. kind === undefined || ! targetKinds . includes ( typ . astNode . kind ) ) {
86
+ return ;
87
+ }
88
+ dependencyGraph . setEdge ( node . name . value , dependency ) ;
89
+ } ) ;
90
+ break ;
91
+ }
92
+ default :
93
+ break ;
94
+ }
95
+ } ,
96
+ } ) ;
97
+
98
+ const sorted = topsort ( dependencyGraph ) ;
99
+
100
+ // Create a map of definitions for quick access, using the definition's name as the key.
101
+ const definitionsMap : Map < string , DefinitionNode > = new Map ( ) ;
102
+ ast . definitions . forEach ( definition => {
103
+ if ( hasNameField ( definition ) && definition . name ) {
104
+ definitionsMap . set ( definition . name . value , definition ) ;
105
+ }
106
+ } ) ;
107
+
108
+ // Two arrays to store sorted and not sorted definitions.
109
+ const sortedDefinitions : DefinitionNode [ ] = [ ] ;
110
+ const notSortedDefinitions : DefinitionNode [ ] = [ ] ;
111
+
112
+ // Iterate over sorted type names and retrieve their corresponding definitions.
113
+ sorted . forEach ( sortedType => {
114
+ const definition = definitionsMap . get ( sortedType ) ;
115
+ if ( definition ) {
116
+ sortedDefinitions . push ( definition ) ;
117
+ definitionsMap . delete ( sortedType ) ;
118
+ }
119
+ } ) ;
120
+
121
+ // Definitions that are left in the map were not included in sorted list
122
+ // Add them to notSortedDefinitions.
123
+ definitionsMap . forEach ( definition => notSortedDefinitions . push ( definition ) ) ;
124
+
125
+ const definitions = [ ...sortedDefinitions , ...notSortedDefinitions ] ;
126
+
127
+ if ( definitions . length !== ast . definitions . length ) {
128
+ throw new Error (
129
+ `unexpected definition length after sorted: want ${ ast . definitions . length } but got ${ definitions . length } `
130
+ ) ;
131
+ }
132
+
133
+ return {
134
+ ...ast ,
135
+ definitions : definitions as ReadonlyArray < DefinitionNode > ,
136
+ } ;
137
+ } ;
138
+
139
+ const hasNameField = ( node : ASTNode ) : node is DefinitionNode & { name ?: NameNode } => {
140
+ return 'name' in node ;
141
+ } ;
142
+
143
+ // Re-implemented w/o CycleException version
144
+ // https://github.com/dagrejs/graphlib/blob/8d27cb89029081c72eb89dde652602805bdd0a34/lib/alg/topsort.js
145
+ export const topsort = ( g : Graph ) : string [ ] => {
146
+ const visited : Record < string , boolean > = { } ;
147
+ const stack : Record < string , boolean > = { } ;
148
+ const results : any [ ] = [ ] ;
149
+
150
+ function visit ( node : string ) {
151
+ if ( ! ( node in visited ) ) {
152
+ stack [ node ] = true ;
153
+ visited [ node ] = true ;
154
+ const predecessors = g . predecessors ( node ) ;
155
+ if ( Array . isArray ( predecessors ) ) {
156
+ predecessors . forEach ( node => visit ( node ) ) ;
157
+ }
158
+ delete stack [ node ] ;
159
+ results . push ( node ) ;
160
+ }
161
+ }
162
+
163
+ g . sinks ( ) . forEach ( node => visit ( node ) ) ;
164
+
165
+ return results . reverse ( ) ;
166
+ } ;
0 commit comments