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' ;
215
316export const isListType = ( typ ?: TypeNode ) : typ is ListTypeNode => typ ?. kind === 'ListType' ;
417export const isNonNullType = ( typ ?: TypeNode ) : typ is NonNullTypeNode => typ ?. kind === 'NonNullType' ;
@@ -20,3 +33,134 @@ export const ObjectTypeDefinitionBuilder = (
2033 return callback ( node ) ;
2134 } ;
2235} ;
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