11import { batchDelegateToSchema } from '@graphql-tools/batch-delegate' ;
22import { StitchingInfo , SubschemaConfig } from '@graphql-tools/delegate' ;
3- import { IResolvers } from '@graphql-tools/utils' ;
3+ import { IResolvers , parseSelectionSet } from '@graphql-tools/utils' ;
44import {
55 DefinitionNode ,
66 FieldDefinitionNode ,
77 GraphQLList ,
88 GraphQLObjectType ,
99 InterfaceTypeDefinitionNode ,
10+ isObjectType ,
1011 Kind ,
1112 ObjectTypeExtensionNode ,
13+ SelectionSetNode ,
1214} from 'graphql' ;
1315import { fromGlobalId , toGlobalId } from 'graphql-relay' ;
14- import { MergedTypeConfigFromEntities } from './supergraph' ;
16+ import { isMergedEntityConfig , MergedEntityConfig } from './supergraph' ;
1517
1618export interface GlobalObjectIdentificationOptions {
1719 nodeIdField : string ;
@@ -64,7 +66,7 @@ export function createNodeDefinitions({
6466
6567 // extend type X implements Node
6668
67- for ( const { typeName } of getDistinctResolvableTypes ( subschemas ) ) {
69+ for ( const { typeName } of getDistinctEntities ( subschemas ) ) {
6870 const typeExtensionDef : ObjectTypeExtensionNode = {
6971 kind : Kind . OBJECT_TYPE_EXTENSION ,
7072 name : {
@@ -147,14 +149,14 @@ export function createResolvers({
147149 nodeIdField,
148150 subschemas,
149151} : GlobalObjectIdentificationOptions ) : IResolvers {
150- const types = getDistinctResolvableTypes ( subschemas ) ;
152+ const types = getDistinctEntities ( subschemas ) ;
151153 return {
152154 ...types . reduce (
153- ( resolvers , { typeName, keyFieldNames } ) => ( {
155+ ( resolvers , { typeName, merge , keyFieldNames } ) => ( {
154156 ...resolvers ,
155157 [ typeName ] : {
156158 [ nodeIdField ] : {
157- selectionSet : `{ ${ keyFieldNames . join ( ' ' ) } }` ,
159+ selectionSet : merge . selectionSet ,
158160 resolve ( source ) {
159161 if ( keyFieldNames . length === 1 ) {
160162 // single field key
@@ -183,9 +185,7 @@ export function createResolvers({
183185 }
184186
185187 // we must use otherwise different schema
186- const types = getDistinctResolvableTypes (
187- stitchingInfo . subschemaMap . values ( ) ,
188- ) ;
188+ const types = getDistinctEntities ( stitchingInfo . subschemaMap . values ( ) ) ;
189189
190190 const { id : idOrFields , type : typeName } = fromGlobalId ( nodeId ) ;
191191 const type = types . find ( ( t ) => t . typeName === typeName ) ;
@@ -211,85 +211,106 @@ export function createResolvers({
211211 }
212212
213213 return batchDelegateToSchema ( {
214- ...type . merge ,
215- info,
216214 context,
215+ info,
217216 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
218220 returnType : new GraphQLList (
219221 // wont ever be undefined, we ensured the subschema has the type above
220222 type . subschema . schema . getType ( typeName ) as GraphQLObjectType ,
221223 ) ,
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 ,
224225 } ) ;
225226 } ,
226227 } ,
227228 } ;
228229}
229230
230- interface DistinctResolvableType {
231+ interface DistinctEntity {
231232 typeName : string ;
232233 subschema : SubschemaConfig ;
233- merge : MergedTypeConfigFromEntities ;
234+ merge : MergedEntityConfig ;
234235 keyFieldNames : string [ ] ;
235236}
236237
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 ,
248253 )
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
255266 continue ;
256267 }
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
266270 continue ;
267271 }
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 } ;
281280 continue ;
282281 }
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 ;
292290 }
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+ } ) ;
293313 }
294- return types ;
314+
315+ return distinctEntities ;
295316}
0 commit comments