@@ -61,6 +61,7 @@ function nullthrows<T>(obj: T | null | undefined): T {
61
61
function makeProp (
62
62
selection : Selection ,
63
63
state : State ,
64
+ unmasked : boolean ,
64
65
concreteType ?: string
65
66
) : ts . PropertySignature {
66
67
let { value } = selection ;
@@ -69,7 +70,11 @@ function makeProp(
69
70
value = transformScalarType (
70
71
nodeType ,
71
72
state ,
72
- selectionsToAST ( [ Array . from ( nullthrows ( nodeSelections ) . values ( ) ) ] , state )
73
+ selectionsToAST (
74
+ [ Array . from ( nullthrows ( nodeSelections ) . values ( ) ) ] ,
75
+ state ,
76
+ unmasked
77
+ )
73
78
) ;
74
79
}
75
80
if ( schemaName === "__typename" && concreteType ) {
@@ -88,6 +93,7 @@ const onlySelectsTypename = (selections: Selection[]) =>
88
93
function selectionsToAST (
89
94
selections : Selection [ ] [ ] ,
90
95
state : State ,
96
+ unmasked : boolean ,
91
97
refTypeName ?: string
92
98
) : ts . TypeNode {
93
99
const baseFields = new Map < string , Selection > ( ) ;
@@ -109,77 +115,95 @@ function selectionsToAST(
109
115
} ) ;
110
116
111
117
const types : ts . PropertySignature [ ] [ ] = [ ] ;
112
- const discriminators = Array . from ( baseFields . values ( ) ) . filter (
113
- isTypenameSelection
114
- ) ;
115
- for ( const concreteType in byConcreteType ) {
116
- types . push (
117
- groupRefs ( [ ...discriminators , ...byConcreteType [ concreteType ] ] ) . map (
118
- selection => makeProp ( selection , state , concreteType )
119
- )
120
- ) ;
121
- }
122
-
123
- if ( types . length ) {
124
- // It might be some other type than the listed concrete types.
125
- // Ideally, we would set the type to Exclude<string, set of listed concrete types>,
126
- // but this doesn't work with TypeScript's discriminated unions.
127
- const otherProp = readOnlyObjectTypeProperty (
128
- "__typename" ,
129
- ts . createLiteralTypeNode ( ts . createLiteral ( "%other" ) )
130
- ) ;
131
- const otherPropWithComment = ts . addSyntheticLeadingComment (
132
- otherProp ,
133
- ts . SyntaxKind . MultiLineCommentTrivia ,
134
- "This will never be '% other', but we need some\n" +
135
- "value in case none of the concrete values match." ,
136
- true
137
- ) ;
138
- types . push ( [ otherPropWithComment ] ) ;
139
- }
118
+ if (
119
+ Object . keys ( byConcreteType ) . length > 0 &&
120
+ onlySelectsTypename ( Array . from ( baseFields . values ( ) ) ) &&
121
+ ( hasTypenameSelection ( Array . from ( baseFields . values ( ) ) ) ||
122
+ Object . keys ( byConcreteType ) . every ( type =>
123
+ hasTypenameSelection ( byConcreteType [ type ] )
124
+ ) )
125
+ ) {
126
+ const typenameAliases = new Set < string > ( ) ;
127
+ for ( const concreteType in byConcreteType ) {
128
+ types . push (
129
+ groupRefs ( [
130
+ ...Array . from ( baseFields . values ( ) ) ,
131
+ ...byConcreteType [ concreteType ]
132
+ ] ) . map ( selection => {
133
+ if ( selection . schemaName === "__typename" ) {
134
+ typenameAliases . add ( selection . key ) ;
135
+ }
136
+ return makeProp ( selection , state , unmasked , concreteType ) ;
137
+ } )
138
+ ) ;
139
+ }
140
140
141
- let selectionMap = selectionsToMap ( Array . from ( baseFields . values ( ) ) ) ;
142
- for ( const concreteType in byConcreteType ) {
143
- selectionMap = mergeSelections (
144
- selectionMap ,
145
- selectionsToMap (
146
- byConcreteType [ concreteType ] . map ( sel => ( {
147
- ...sel ,
148
- conditional : true
149
- } ) )
150
- )
141
+ types . push (
142
+ Array . from ( typenameAliases ) . map ( typenameAlias => {
143
+ const otherProp = readOnlyObjectTypeProperty (
144
+ typenameAlias ,
145
+ ts . createLiteralTypeNode ( ts . createLiteral ( "%other" ) )
146
+ ) ;
147
+ const otherPropWithComment = ts . addSyntheticLeadingComment (
148
+ otherProp ,
149
+ ts . SyntaxKind . MultiLineCommentTrivia ,
150
+ "This will never be '%other', but we need some\n" +
151
+ "value in case none of the concrete values match." ,
152
+ true
153
+ ) ;
154
+ return otherPropWithComment ;
155
+ } )
151
156
) ;
152
- }
153
- const baseProps : ts . PropertySignature [ ] = groupRefs (
154
- Array . from ( selectionMap . values ( ) )
155
- ) . map ( sel =>
156
- isTypenameSelection ( sel ) && sel . concreteType
157
- ? makeProp ( { ...sel , conditional : false } , state , sel . concreteType )
158
- : makeProp ( sel , state )
159
- ) ;
160
-
161
- if ( refTypeName ) {
162
- baseProps . push (
163
- readOnlyObjectTypeProperty (
164
- REF_TYPE ,
165
- ts . createTypeReferenceNode ( ts . createIdentifier ( refTypeName ) , undefined )
166
- )
157
+ } else {
158
+ let selectionMap = selectionsToMap ( Array . from ( baseFields . values ( ) ) ) ;
159
+ for ( const concreteType in byConcreteType ) {
160
+ selectionMap = mergeSelections (
161
+ selectionMap ,
162
+ selectionsToMap (
163
+ byConcreteType [ concreteType ] . map ( sel => ( {
164
+ ...sel ,
165
+ conditional : true
166
+ } ) )
167
+ )
168
+ ) ;
169
+ }
170
+ const selectionMapValues = groupRefs ( Array . from ( selectionMap . values ( ) ) ) . map (
171
+ sel =>
172
+ isTypenameSelection ( sel ) && sel . concreteType
173
+ ? makeProp (
174
+ {
175
+ ...sel ,
176
+ conditional : false
177
+ } ,
178
+ state ,
179
+ unmasked ,
180
+ sel . concreteType
181
+ )
182
+ : makeProp ( sel , state , unmasked )
167
183
) ;
184
+ types . push ( selectionMapValues ) ;
168
185
}
169
186
170
- if ( types . length > 0 ) {
171
- const unionType = ts . createUnionTypeNode (
172
- types . map ( props => {
173
- return exactObjectTypeAnnotation ( props ) ;
174
- } )
175
- ) ;
176
- return ts . createIntersectionTypeNode ( [
177
- exactObjectTypeAnnotation ( baseProps ) ,
178
- unionType
179
- ] ) ;
180
- } else {
181
- return exactObjectTypeAnnotation ( baseProps ) ;
187
+ const typeElements = types . map ( props => {
188
+ if ( refTypeName ) {
189
+ props . push (
190
+ readOnlyObjectTypeProperty (
191
+ REF_TYPE ,
192
+ ts . createTypeReferenceNode (
193
+ ts . createIdentifier ( refTypeName ) ,
194
+ undefined
195
+ )
196
+ )
197
+ ) ;
198
+ }
199
+ return unmasked
200
+ ? ts . createTypeLiteralNode ( props )
201
+ : exactObjectTypeAnnotation ( props ) ;
202
+ } ) ;
203
+ if ( typeElements . length === 1 ) {
204
+ return typeElements [ 0 ] ;
182
205
}
206
+ return ts . createUnionTypeNode ( typeElements ) ;
183
207
}
184
208
185
209
// We don't have exact object types in typescript.
@@ -290,7 +314,7 @@ function createVisitor(options: TypeGeneratorOptions) {
290
314
const inputObjectTypes = generateInputObjectTypes ( state ) ;
291
315
const responseType = exportType (
292
316
`${ node . name } Response` ,
293
- selectionsToAST ( node . selections , state )
317
+ selectionsToAST ( node . selections , state , false )
294
318
) ;
295
319
const operationType = exportType (
296
320
node . name ,
@@ -368,7 +392,13 @@ function createVisitor(options: TypeGeneratorOptions) {
368
392
) ;
369
393
refTypeNodes . push ( refType ) ;
370
394
}
371
- const baseType = selectionsToAST ( selections , state , refTypeName ) ;
395
+ const unmasked = node . metadata != null && node . metadata . mask === false ;
396
+ const baseType = selectionsToAST (
397
+ selections ,
398
+ state ,
399
+ unmasked ,
400
+ refTypeName
401
+ ) ;
372
402
const type = isPlural ( node )
373
403
? ts . createTypeReferenceNode ( ts . createIdentifier ( "ReadonlyArray" ) , [
374
404
baseType
0 commit comments