1
1
import {
2
2
buildSchema ,
3
+ GraphQLFieldConfig ,
3
4
GraphQLFieldConfigMap ,
4
- GraphQLFieldMap ,
5
5
GraphQLInterfaceType ,
6
6
GraphQLList ,
7
7
GraphQLNamedType ,
8
8
GraphQLNonNull ,
9
9
GraphQLObjectType ,
10
+ GraphQLOutputType ,
10
11
GraphQLSchema ,
11
12
GraphQLSemanticNonNull ,
12
13
GraphQLType ,
13
14
GraphQLUnionType ,
15
+ Kind ,
14
16
printSchema ,
15
17
validateSchema ,
16
18
} from "graphql" ;
17
19
import type { Maybe } from "graphql/jsutils/Maybe" ;
18
- import { ObjMap } from "graphql/jsutils/ObjMap" ;
19
20
import { readFile , writeFile } from "node:fs/promises" ;
20
21
import { parseArgs } from "node:util" ;
21
22
@@ -59,6 +60,7 @@ export async function main(toStrict = false) {
59
60
types : config . types
60
61
. filter ( ( t ) => ! t . name . startsWith ( "__" ) )
61
62
. map ( ( t ) => convertType ( t ) ) ,
63
+ directives : config . directives . filter ( ( d ) => d . name !== "semanticNonNull" ) ,
62
64
} ) ;
63
65
64
66
const newSdl = printSchema ( derivedSchema ) ;
@@ -72,13 +74,16 @@ function makeConvertType(toStrict: boolean) {
72
74
function convertFields ( fields : GraphQLFieldConfigMap < any , any > ) {
73
75
return ( ) => {
74
76
return Object . fromEntries (
75
- Object . entries ( fields ) . map ( ( [ fieldName , spec ] ) => [
76
- fieldName ,
77
- {
78
- ...spec ,
79
- type : convertType ( spec . type ) ,
80
- } ,
81
- ] ) ,
77
+ Object . entries ( fields ) . map ( ( [ fieldName , inSpec ] ) => {
78
+ const spec = applySemanticNonNullDirective ( inSpec ) ;
79
+ return [
80
+ fieldName ,
81
+ {
82
+ ...spec ,
83
+ type : convertType ( spec . type ) ,
84
+ } ,
85
+ ] ;
86
+ } ) ,
82
87
) as any ;
83
88
} ;
84
89
}
@@ -161,3 +166,71 @@ function makeConvertType(toStrict: boolean) {
161
166
162
167
return convertType ;
163
168
}
169
+
170
+ /**
171
+ * Takes a GraphQL field config and checks to see if the `@semanticNonNull`
172
+ * directive was applied; if so, converts to a field config using explicit
173
+ * GraphQLSemanticNonNull wrapper types instead.
174
+ *
175
+ * @see {@url https://www.apollographql.com/docs/kotlin/advanced/nullability/#semanticnonnull }
176
+ */
177
+ function applySemanticNonNullDirective (
178
+ spec : GraphQLFieldConfig < any , any , any > ,
179
+ ) : GraphQLFieldConfig < any , any , any > {
180
+ const directive = spec . astNode ?. directives ?. find (
181
+ ( d ) => d . name . value === "semanticNonNull" ,
182
+ ) ;
183
+ if ( ! directive ) {
184
+ return spec ;
185
+ }
186
+ const levelsArg = directive . arguments ?. find ( ( a ) => a . name . value === "levels" ) ;
187
+ const levels =
188
+ levelsArg ?. value ?. kind === Kind . LIST
189
+ ? levelsArg . value . values
190
+ . filter ( ( v ) => v . kind === Kind . INT )
191
+ . map ( ( v ) => Number ( v . value ) )
192
+ : [ 0 ] ;
193
+ function recurse ( type : GraphQLOutputType , level : number ) : GraphQLOutputType {
194
+ if ( type instanceof GraphQLSemanticNonNull ) {
195
+ // Strip semantic-non-null types; this should never happen but if someone
196
+ // uses both semantic-non-null and the `@semanticNonNull` directive, we
197
+ // want the directive to win (I guess?)
198
+ return recurse ( type . ofType , level ) ;
199
+ } else if ( type instanceof GraphQLNonNull ) {
200
+ const inner = recurse ( type . ofType , level ) ;
201
+ if ( levels . includes ( level ) ) {
202
+ // Semantic non-null from `inner` replaces our GrpahQLNonNull wrapper
203
+ return inner ;
204
+ } else {
205
+ // Keep non-null wrapper; no semantic-non-null was added to `inner`
206
+ return new GraphQLNonNull ( inner ) ;
207
+ }
208
+ } else if ( type instanceof GraphQLList ) {
209
+ const inner = new GraphQLList ( recurse ( type . ofType , level + 1 ) ) ;
210
+ if ( levels . includes ( level ) ) {
211
+ return new GraphQLSemanticNonNull ( inner ) ;
212
+ } else {
213
+ return inner ;
214
+ }
215
+ } else {
216
+ if ( levels . includes ( level ) ) {
217
+ return new GraphQLSemanticNonNull ( type ) ;
218
+ } else {
219
+ return type ;
220
+ }
221
+ }
222
+ }
223
+
224
+ return {
225
+ ...spec ,
226
+ type : recurse ( spec . type , 0 ) ,
227
+ astNode : spec . astNode
228
+ ? {
229
+ ...spec . astNode ,
230
+ directives : spec . astNode . directives ?. filter (
231
+ ( d ) => d . name . value !== "semanticNonNull" ,
232
+ ) ,
233
+ }
234
+ : undefined ,
235
+ } ;
236
+ }
0 commit comments