1
1
import { batchDelegateToSchema } from '@graphql-tools/batch-delegate' ;
2
2
import { StitchingInfo , SubschemaConfig } from '@graphql-tools/delegate' ;
3
- import { IResolvers } from '@graphql-tools/utils' ;
3
+ import { IResolvers , parseSelectionSet } from '@graphql-tools/utils' ;
4
4
import {
5
5
DefinitionNode ,
6
6
FieldDefinitionNode ,
7
7
GraphQLList ,
8
8
GraphQLObjectType ,
9
9
InterfaceTypeDefinitionNode ,
10
+ isObjectType ,
10
11
Kind ,
11
12
ObjectTypeExtensionNode ,
13
+ SelectionSetNode ,
12
14
} from 'graphql' ;
13
15
import { fromGlobalId , toGlobalId } from 'graphql-relay' ;
14
- import { MergedTypeConfigFromEntities } from './supergraph' ;
16
+ import { isMergedEntityConfig , MergedEntityConfig } from './supergraph' ;
15
17
16
18
export interface GlobalObjectIdentificationOptions {
17
19
nodeIdField : string ;
@@ -64,7 +66,7 @@ export function createNodeDefinitions({
64
66
65
67
// extend type X implements Node
66
68
67
- for ( const { typeName } of getDistinctResolvableTypes ( subschemas ) ) {
69
+ for ( const { typeName } of getDistinctEntities ( subschemas ) ) {
68
70
const typeExtensionDef : ObjectTypeExtensionNode = {
69
71
kind : Kind . OBJECT_TYPE_EXTENSION ,
70
72
name : {
@@ -147,14 +149,14 @@ export function createResolvers({
147
149
nodeIdField,
148
150
subschemas,
149
151
} : GlobalObjectIdentificationOptions ) : IResolvers {
150
- const types = getDistinctResolvableTypes ( subschemas ) ;
152
+ const types = getDistinctEntities ( subschemas ) ;
151
153
return {
152
154
...types . reduce (
153
- ( resolvers , { typeName, keyFieldNames } ) => ( {
155
+ ( resolvers , { typeName, merge , keyFieldNames } ) => ( {
154
156
...resolvers ,
155
157
[ typeName ] : {
156
158
[ nodeIdField ] : {
157
- selectionSet : `{ ${ keyFieldNames . join ( ' ' ) } }` ,
159
+ selectionSet : merge . selectionSet ,
158
160
resolve ( source ) {
159
161
if ( keyFieldNames . length === 1 ) {
160
162
// single field key
@@ -183,9 +185,7 @@ export function createResolvers({
183
185
}
184
186
185
187
// we must use otherwise different schema
186
- const types = getDistinctResolvableTypes (
187
- stitchingInfo . subschemaMap . values ( ) ,
188
- ) ;
188
+ const types = getDistinctEntities ( stitchingInfo . subschemaMap . values ( ) ) ;
189
189
190
190
const { id : idOrFields , type : typeName } = fromGlobalId ( nodeId ) ;
191
191
const type = types . find ( ( t ) => t . typeName === typeName ) ;
@@ -211,85 +211,106 @@ export function createResolvers({
211
211
}
212
212
213
213
return batchDelegateToSchema ( {
214
- ...type . merge ,
215
- info,
216
214
context,
215
+ info,
217
216
schema : type . subschema ,
217
+ fieldName : type . merge . fieldName ,
218
+ argsFromKeys : type . merge . argsFromKeys ,
219
+ key : { ...keyFields , __typename : typeName } , // we already have all the necessary keys
218
220
returnType : new GraphQLList (
219
221
// wont ever be undefined, we ensured the subschema has the type above
220
222
type . subschema . schema . getType ( typeName ) as GraphQLObjectType ,
221
223
) ,
222
- selectionSet : undefined , // selectionSet is not needed here
223
- key : { ...keyFields , __typename : typeName } , // we already have all the necessary keys
224
+ dataLoaderOptions : type . merge . dataLoaderOptions ,
224
225
} ) ;
225
226
} ,
226
227
} ,
227
228
} ;
228
229
}
229
230
230
- interface DistinctResolvableType {
231
+ interface DistinctEntity {
231
232
typeName : string ;
232
233
subschema : SubschemaConfig ;
233
- merge : MergedTypeConfigFromEntities ;
234
+ merge : MergedEntityConfig ;
234
235
keyFieldNames : string [ ] ;
235
236
}
236
237
237
- function getDistinctResolvableTypes (
238
- subschemas : Iterable < SubschemaConfig > ,
239
- ) : DistinctResolvableType [ ] {
240
- const visitedTypeNames = new Set < string > ( ) ;
241
- const types : DistinctResolvableType [ ] = [ ] ;
242
- for ( const subschema of subschemas ) {
243
- // TODO: respect canonical types
244
- for ( const [ typeName , merge ] of Object . entries ( subschema . merge || { } )
245
- . filter (
246
- // make sure selectionset is defined for the sort to work
247
- ( [ , merge ] ) => merge . selectionSet ,
238
+ function getDistinctEntities (
239
+ subschemasIter : Iterable < SubschemaConfig > ,
240
+ ) : DistinctEntity [ ] {
241
+ const distinctEntities : DistinctEntity [ ] = [ ] ;
242
+
243
+ const subschemas = Array . from ( subschemasIter ) ;
244
+ const types = subschemas . flatMap ( ( subschema ) =>
245
+ Object . values ( subschema . schema . getTypeMap ( ) ) ,
246
+ ) ;
247
+
248
+ const objects = types . filter ( isObjectType ) ;
249
+ for ( const obj of objects ) {
250
+ if (
251
+ distinctEntities . find (
252
+ ( distinctType ) => distinctType . typeName === obj . name ,
248
253
)
249
- . sort (
250
- // sort by shortest keys first
251
- ( [ , a ] , [ , b ] ) => a . selectionSet ! . length - b . selectionSet ! . length ,
252
- ) ) {
253
- if ( visitedTypeNames . has ( typeName ) ) {
254
- // already yielded this type, all types can only have one resolution
254
+ ) {
255
+ // already added this type
256
+ continue ;
257
+ }
258
+ let candidate : {
259
+ subschema : SubschemaConfig ;
260
+ merge : MergedEntityConfig ;
261
+ } | null = null ;
262
+ for ( const subschema of subschemas ) {
263
+ const merge = subschema . merge ?. [ obj . name ] ;
264
+ if ( ! merge ) {
265
+ // not resolvable from this subschema
255
266
continue ;
256
267
}
257
-
258
- if (
259
- ! merge . selectionSet ||
260
- ! merge . argsFromKeys ||
261
- ! merge . key ||
262
- ! merge . fieldName ||
263
- ! merge . dataLoaderOptions
264
- ) {
265
- // cannot be resolved globally
268
+ if ( ! isMergedEntityConfig ( merge ) ) {
269
+ // not a merged entity config, cannot be resolved globally
266
270
continue ;
267
271
}
268
-
269
- // remove first and last characters from the selection set making up the key (curly braces, `{ id } -> id`)
270
- const key = merge . selectionSet . trim ( ) . slice ( 1 , - 1 ) . trim ( ) ;
271
- if (
272
- // the key for fetching this object contains other objects
273
- key . includes ( '{' ) ||
274
- // the key for fetching this object contains arguments
275
- key . includes ( '(' ) ||
276
- // the key contains aliases
277
- key . includes ( ':' )
278
- ) {
279
- // it's too complex to use global object identification
280
- // TODO: do it anyways when need arises
272
+ if ( merge . canonical ) {
273
+ // this subschema is canonical (owner) for this type, no need to check other schemas
274
+ candidate = { subschema, merge } ;
275
+ break ;
276
+ }
277
+ if ( ! candidate ) {
278
+ // first merge candidate
279
+ candidate = { subschema, merge } ;
281
280
continue ;
282
281
}
283
- // what we're left in the "key" are simple field(s) like "id" or "email"
284
-
285
- visitedTypeNames . add ( typeName ) ;
286
- types . push ( {
287
- typeName,
288
- subschema,
289
- merge : merge as MergedTypeConfigFromEntities ,
290
- keyFieldNames : key . trim ( ) . split ( / \s + / ) ,
291
- } ) ;
282
+ if ( merge . selectionSet . length < candidate . merge . selectionSet . length ) {
283
+ // found a better candidate
284
+ candidate = { subschema, merge } ;
285
+ }
286
+ }
287
+ if ( ! candidate ) {
288
+ // no merge candidate found, cannot be resolved globally
289
+ continue ;
292
290
}
291
+ // is an entity that can efficiently be resolved globally
292
+ distinctEntities . push ( {
293
+ ...candidate ,
294
+ typeName : obj . name ,
295
+ keyFieldNames : ( function getRootFieldNames (
296
+ selectionSet : SelectionSetNode ,
297
+ ) : string [ ] {
298
+ const fieldNames : string [ ] = [ ] ;
299
+ for ( const sel of selectionSet . selections ) {
300
+ if ( sel . kind === Kind . FRAGMENT_SPREAD ) {
301
+ throw new Error ( 'Fragment spreads cannot appear in @key fields' ) ;
302
+ }
303
+ if ( sel . kind === Kind . INLINE_FRAGMENT ) {
304
+ fieldNames . push ( ...getRootFieldNames ( sel . selectionSet ) ) ;
305
+ continue ;
306
+ }
307
+ // Kind.FIELD
308
+ fieldNames . push ( sel . alias ?. value || sel . name . value ) ;
309
+ }
310
+ return fieldNames ;
311
+ } ) ( parseSelectionSet ( candidate . merge . selectionSet ) ) ,
312
+ } ) ;
293
313
}
294
- return types ;
314
+
315
+ return distinctEntities ;
295
316
}
0 commit comments