diff --git a/e2e/federation-example/__snapshots__/federation-example.test.ts.snap b/e2e/federation-example/__snapshots__/federation-example.test.ts.snap index 44ace89f743ee..5f6975f6f3698 100644 --- a/e2e/federation-example/__snapshots__/federation-example.test.ts.snap +++ b/e2e/federation-example/__snapshots__/federation-example.test.ts.snap @@ -93,6 +93,78 @@ exports[`should execute TestQuery 1`] = ` "inStock": true, "name": "Table", "price": 899, + "reviews": [ + { + "author": { + "id": "1", + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "id": "1", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + { + "body": "Too expensive.", + "id": "2", + "product": { + "inStock": false, + "name": "Couch", + "price": 1299, + "shippingEstimate": 0, + "upc": "2", + "weight": 1000, + }, + }, + ], + "username": "@ada", + }, + "body": "Love it!", + "id": "1", + }, + { + "author": { + "id": "2", + "name": "Alan Turing", + "reviews": [ + { + "body": "Could be better.", + "id": "3", + "product": { + "inStock": true, + "name": "Chair", + "price": 54, + "shippingEstimate": 25, + "upc": "3", + "weight": 50, + }, + }, + { + "body": "Prefer something else.", + "id": "4", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + ], + "username": "@complete", + }, + "body": "Prefer something else.", + "id": "4", + }, + ], "shippingEstimate": 50, "upc": "1", "weight": 100, @@ -101,6 +173,43 @@ exports[`should execute TestQuery 1`] = ` "inStock": false, "name": "Couch", "price": 1299, + "reviews": [ + { + "author": { + "id": "1", + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "id": "1", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + { + "body": "Too expensive.", + "id": "2", + "product": { + "inStock": false, + "name": "Couch", + "price": 1299, + "shippingEstimate": 0, + "upc": "2", + "weight": 1000, + }, + }, + ], + "username": "@ada", + }, + "body": "Too expensive.", + "id": "2", + }, + ], "shippingEstimate": 0, "upc": "2", "weight": 1000, @@ -109,6 +218,43 @@ exports[`should execute TestQuery 1`] = ` "inStock": true, "name": "Chair", "price": 54, + "reviews": [ + { + "author": { + "id": "2", + "name": "Alan Turing", + "reviews": [ + { + "body": "Could be better.", + "id": "3", + "product": { + "inStock": true, + "name": "Chair", + "price": 54, + "shippingEstimate": 25, + "upc": "3", + "weight": 50, + }, + }, + { + "body": "Prefer something else.", + "id": "4", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + ], + "username": "@complete", + }, + "body": "Could be better.", + "id": "3", + }, + ], "shippingEstimate": 25, "upc": "3", "weight": 50, @@ -118,66 +264,284 @@ exports[`should execute TestQuery 1`] = ` { "id": "1", "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "id": "1", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "reviews": [ + { + "author": { + "id": "1", + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "id": "1", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + { + "body": "Too expensive.", + "id": "2", + "product": { + "inStock": false, + "name": "Couch", + "price": 1299, + "shippingEstimate": 0, + "upc": "2", + "weight": 1000, + }, + }, + ], + "username": "@ada", + }, + "body": "Love it!", + "id": "1", + }, + { + "author": { + "id": "2", + "name": "Alan Turing", + "reviews": [ + { + "body": "Could be better.", + "id": "3", + "product": { + "inStock": true, + "name": "Chair", + "price": 54, + "shippingEstimate": 25, + "upc": "3", + "weight": 50, + }, + }, + { + "body": "Prefer something else.", + "id": "4", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + ], + "username": "@complete", + }, + "body": "Prefer something else.", + "id": "4", + }, + ], + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + { + "body": "Too expensive.", + "id": "2", + "product": { + "inStock": false, + "name": "Couch", + "price": 1299, + "reviews": [ + { + "author": { + "id": "1", + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "id": "1", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + { + "body": "Too expensive.", + "id": "2", + "product": { + "inStock": false, + "name": "Couch", + "price": 1299, + "shippingEstimate": 0, + "upc": "2", + "weight": 1000, + }, + }, + ], + "username": "@ada", + }, + "body": "Too expensive.", + "id": "2", + }, + ], + "shippingEstimate": 0, + "upc": "2", + "weight": 1000, + }, + }, + ], "username": "@ada", }, { "id": "2", "name": "Alan Turing", + "reviews": [ + { + "body": "Could be better.", + "id": "3", + "product": { + "inStock": true, + "name": "Chair", + "price": 54, + "reviews": [ + { + "author": { + "id": "2", + "name": "Alan Turing", + "reviews": [ + { + "body": "Could be better.", + "id": "3", + "product": { + "inStock": true, + "name": "Chair", + "price": 54, + "shippingEstimate": 25, + "upc": "3", + "weight": 50, + }, + }, + { + "body": "Prefer something else.", + "id": "4", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + ], + "username": "@complete", + }, + "body": "Could be better.", + "id": "3", + }, + ], + "shippingEstimate": 25, + "upc": "3", + "weight": 50, + }, + }, + { + "body": "Prefer something else.", + "id": "4", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "reviews": [ + { + "author": { + "id": "1", + "name": "Ada Lovelace", + "reviews": [ + { + "body": "Love it!", + "id": "1", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + { + "body": "Too expensive.", + "id": "2", + "product": { + "inStock": false, + "name": "Couch", + "price": 1299, + "shippingEstimate": 0, + "upc": "2", + "weight": 1000, + }, + }, + ], + "username": "@ada", + }, + "body": "Love it!", + "id": "1", + }, + { + "author": { + "id": "2", + "name": "Alan Turing", + "reviews": [ + { + "body": "Could be better.", + "id": "3", + "product": { + "inStock": true, + "name": "Chair", + "price": 54, + "shippingEstimate": 25, + "upc": "3", + "weight": 50, + }, + }, + { + "body": "Prefer something else.", + "id": "4", + "product": { + "inStock": true, + "name": "Table", + "price": 899, + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + ], + "username": "@complete", + }, + "body": "Prefer something else.", + "id": "4", + }, + ], + "shippingEstimate": 50, + "upc": "1", + "weight": 100, + }, + }, + ], "username": "@complete", }, ], }, - "errors": [ - { - "extensions": { - "code": "GRAPHQL_VALIDATION_FAILED", - "planNodeId": 3, - }, - "message": "Field "product" of type "Product" must have a selection of subfields. Did you mean "product { ... }"?", - "path": [ - "topProducts", - ], - }, - { - "extensions": { - "code": "GRAPHQL_VALIDATION_FAILED", - "planNodeId": 3, - }, - "message": "Field "product" of type "Product" must have a selection of subfields. Did you mean "product { ... }"?", - "path": [ - "topProducts", - ], - }, - { - "extensions": { - "code": "GRAPHQL_VALIDATION_FAILED", - "planNodeId": 1, - }, - "message": "Field "product" of type "Product" must have a selection of subfields. Did you mean "product { ... }"?", - "path": [ - "users", - ], - }, - { - "extensions": { - "code": "GRAPHQL_VALIDATION_FAILED", - "planNodeId": 1, - }, - "message": "Field "product" of type "Product" must have a selection of subfields. Did you mean "product { ... }"?", - "path": [ - "users", - ], - }, - { - "extensions": { - "code": "GRAPHQL_VALIDATION_FAILED", - "planNodeId": 3, - }, - "message": "Field "product" of type "Product" must have a selection of subfields. Did you mean "product { ... }"?", - "path": [ - "topProducts", - ], - }, - ], } `; diff --git a/packages/fusion/federation/src/index.ts b/packages/fusion/federation/src/index.ts index ff0793e5e9f60..c4da8a2d61055 100644 --- a/packages/fusion/federation/src/index.ts +++ b/packages/fusion/federation/src/index.ts @@ -35,6 +35,7 @@ export function convertSupergraphToFusiongraph( supergraphSchema = buildSchema(federationSupergraph, schemaBuildOpts); } const subgraphLocationMap = new Map(); + const subgraphNameMap = new Map(); const joinGraphEnum = supergraphSchema.getType('join__Graph'); if (!isEnumType(joinGraphEnum)) { throw new Error('Expected join__Graph to be an enum'); @@ -44,8 +45,9 @@ export function convertSupergraphToFusiongraph( if (!joinGraphDirective?.length) { throw new Error('Expected join__Graph to have a join__graph directive'); } - const { url } = joinGraphDirective[0]; - subgraphLocationMap.set(joinGraphEnumValue.value, url); + const { name = joinGraphEnumValue.value, url } = joinGraphDirective[0]; + subgraphLocationMap.set(name, url); + subgraphNameMap.set(joinGraphEnumValue.value, name); } const typeMap = new Map(); @@ -57,7 +59,7 @@ export function convertSupergraphToFusiongraph( const fusiongraphSchema = mapSchema(supergraphSchema, { [MapperKind.TYPE](type) { - if (type.name.startsWith('link__') || type.name.startsWith('join__')) { + if (type.name.startsWith('link__') || type.name.startsWith('join__') || type.name.startsWith('core__')) { return null; } const typeExtensions: any = (type.extensions ||= {}); @@ -72,7 +74,7 @@ export function convertSupergraphToFusiongraph( if (!joinTypeDirective.external) { sourceDirectivesForFusion.push({ name: type.name, - subgraph: joinTypeDirective.graph, + subgraph: subgraphNameMap.get(joinTypeDirective.graph) || joinTypeDirective.graph, }); } if (joinTypeDirective.key) { @@ -98,12 +100,16 @@ export function convertSupergraphToFusiongraph( if (joinFieldDirectives?.length) { availableSubgraphsForField = joinFieldDirectives .filter(joinFieldDirective => !joinFieldDirective.external) - .map(joinFieldDirective => joinFieldDirective.graph); + .map(joinFieldDirective => subgraphNameMap.get(joinFieldDirective.graph) || joinFieldDirective.graph); } else { availableSubgraphsForField = joinTypeDirectives.map( - joinTypeDirective => joinTypeDirective.graph, + joinTypeDirective => subgraphNameMap.get(joinTypeDirective.graph) || joinTypeDirective.graph, ); } + const keyGraph = subgraphNameMap.get(joinTypeDirective.graph) || joinTypeDirective.graph; + if (!availableSubgraphsForField.includes(keyGraph)) { + availableSubgraphsForField.push(keyGraph); + } for (const availableSubgraph of availableSubgraphsForField) { variableDirectivesForFusion.push({ name: varName, @@ -117,13 +123,13 @@ export function convertSupergraphToFusiongraph( } const mainVariable = { name: `representations`, - subgraph: joinTypeDirective.graph, + subgraph: subgraphNameMap.get(joinTypeDirective.graph) || joinTypeDirective.graph, value: `{ __typename: "${type.name}", ${[...combinedVariableValues].join(', ')} }`, }; variableDirectivesForFusion.push(mainVariable); resolverDirectives.push({ operation: `query get${type.name}($representations: [_Any!]!) { _entities(representations: $representations) { ... on ${type.name} { ...__export } } }`, - subgraph: joinTypeDirective.graph, + subgraph: subgraphNameMap.get(joinTypeDirective.graph) || joinTypeDirective.graph, }); } } @@ -162,7 +168,7 @@ export function convertSupergraphToFusiongraph( for (const joinFieldDirective of joinFieldDirectives || []) { if (!joinFieldDirective.external) { sourceDirectivesForFusion.push({ - subgraph: joinFieldDirective.graph, + subgraph: subgraphNameMap.get(joinFieldDirective.graph) || joinFieldDirective.graph, name: fieldName, }); } @@ -190,7 +196,7 @@ export function convertSupergraphToFusiongraph( variableDirectivesForFusion.push({ name: varName, select: print(selection), - subgraph: joinFieldDirective.graph, + subgraph: subgraphNameMap.get(joinFieldDirective.graph) || joinFieldDirective.graph, type: getNamedType(typeFieldMap[selectionName].type).toString(), }); } @@ -230,7 +236,7 @@ export function convertSupergraphToFusiongraph( const resolverDirectives = (fieldDirectiveExtensions.resolver ||= []); // TODO: Later use global resolvers to batch queries for different types resolverDirectives.push({ - subgraph: joinFieldDirective.graph, + subgraph: subgraphNameMap.get(joinFieldDirective.graph) || joinFieldDirective.graph, operation: operationString, }); } @@ -278,7 +284,7 @@ export function convertSupergraphToFusiongraph( const sourceDirectivesForFusion: any[] = (enumValueDirectiveExtensions.source ||= []); for (const joinEnumValueDirective of joinEnumValueDirectives || []) { sourceDirectivesForFusion.push({ - subgraph: joinEnumValueDirective.graph, + subgraph: subgraphNameMap.get(joinEnumValueDirective.graph) || joinEnumValueDirective.graph, name: enumValueName, }); } @@ -287,8 +293,10 @@ export function convertSupergraphToFusiongraph( [MapperKind.DIRECTIVE](directiveConfig) { if ( directiveConfig.name.startsWith('join__') || - directiveConfig.name === 'link__' || - directiveConfig.name === 'link' + directiveConfig.name.startsWith('link__') || + directiveConfig.name.startsWith('core__') || + directiveConfig.name === 'link' || + directiveConfig.name === 'core' ) { return null; } diff --git a/packages/fusion/federation/tests/__snapshots__/supergraphs.spec.ts.snap b/packages/fusion/federation/tests/__snapshots__/supergraphs.spec.ts.snap index 0c58cca1fdaa9..e358054cee94d 100644 --- a/packages/fusion/federation/tests/__snapshots__/supergraphs.spec.ts.snap +++ b/packages/fusion/federation/tests/__snapshots__/supergraphs.spec.ts.snap @@ -438,8 +438,6 @@ exports[`Supergraphs pb Fusiongraph: fusiongraph 1`] = ` mutation: Mutation } -directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA - directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION type DemoAuthInfo @source(name: "DemoAuthInfo", subgraph: "DEMO_FEATURES") @source(name: "DemoAuthInfo", subgraph: "NODE_RESOLVER") { @@ -617,16 +615,5 @@ type Query @source(name: "Query", subgraph: "DEMO_FEATURES") @source(name: "Quer demoMe: DemoAuthInfo! @source(subgraph: "DEMO_FEATURES", name: "demoMe") @resolver(subgraph: "DEMO_FEATURES", operation: "query demoMe { demoMe }") node(id: ID!): Node @source(subgraph: "NODE_RESOLVER", name: "node") @resolver(subgraph: "NODE_RESOLVER", operation: "query node($id: ID!) { node(id: $id) }") nodes(ids: [ID!]!): [Node]! @source(subgraph: "NODE_RESOLVER", name: "nodes") @resolver(subgraph: "NODE_RESOLVER", operation: "query nodes($ids: [ID!]!) { nodes(ids: $ids) }") -} - -enum core__Purpose @source(name: "core__Purpose", subgraph: "DEMO_FEATURES") @source(name: "core__Purpose", subgraph: "NODE_RESOLVER") { - """ - \`EXECUTION\` features provide metadata necessary to for operation execution. - """ - EXECUTION - """ - \`SECURITY\` features provide metadata necessary to securely resolve fields. - """ - SECURITY }" `; diff --git a/packages/fusion/federation/tests/fixtures/supergraphs/apollo-example/supergraph.graphql b/packages/fusion/federation/tests/fixtures/supergraphs/apollo-example/supergraph.graphql index 70344a2739763..016acebb8c075 100644 --- a/packages/fusion/federation/tests/fixtures/supergraphs/apollo-example/supergraph.graphql +++ b/packages/fusion/federation/tests/fixtures/supergraphs/apollo-example/supergraph.graphql @@ -1,103 +1,78 @@ schema - @link(url: "https://specs.apollo.dev/link/v1.0") - @link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION) { + @core(feature: "https://specs.apollo.dev/core/v0.2") + @core(feature: "https://specs.apollo.dev/join/v0.1", for: EXECUTION) { query: Query } -directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE +directive @core(as: String, feature: String!, for: core__Purpose) repeatable on SCHEMA directive @join__field( graph: join__Graph - requires: join__FieldSet provides: join__FieldSet - type: String - external: Boolean - override: String - usedOverridden: Boolean -) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + requires: join__FieldSet +) on FIELD_DEFINITION directive @join__graph(name: String!, url: String!) on ENUM_VALUE -directive @join__implements( - graph: join__Graph! - interface: String! -) repeatable on OBJECT | INTERFACE - -directive @join__type( - graph: join__Graph! - key: join__FieldSet - extension: Boolean! = false - resolvable: Boolean! = true - isInterfaceObject: Boolean! = false -) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR +directive @join__owner(graph: join__Graph!) on INTERFACE | OBJECT -directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION +directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on INTERFACE | OBJECT -directive @link( - url: String - as: String - for: link__Purpose - import: [link__Import] -) repeatable on SCHEMA +type Product + @join__owner(graph: PRODUCTS) + @join__type(graph: PRODUCTS, key: "upc") + @join__type(graph: INVENTORY, key: "upc") + @join__type(graph: REVIEWS, key: "upc") { + inStock: Boolean @join__field(graph: INVENTORY) + name: String @join__field(graph: PRODUCTS) + price: Int @join__field(graph: PRODUCTS) + reviews: [Review] @join__field(graph: REVIEWS) + shippingEstimate: Int @join__field(graph: INVENTORY, requires: "price weight") + upc: String! @join__field(graph: PRODUCTS) + weight: Int @join__field(graph: PRODUCTS) +} -scalar join__FieldSet +type Query { + me: User @join__field(graph: ACCOUNTS) + topProducts(first: Int = 5): [Product] @join__field(graph: PRODUCTS) + user(id: ID!): User @join__field(graph: ACCOUNTS) + users: [User] @join__field(graph: ACCOUNTS) +} -enum join__Graph { - accounts @join__graph(name: "accounts", url: "http://accounts:4001/graphql") - inventory @join__graph(name: "inventory", url: "http://inventory:4002/graphql") - products @join__graph(name: "products", url: "http://products:4003/graphql") - reviews @join__graph(name: "reviews", url: "http://reviews:4004/graphql") +type Review @join__owner(graph: REVIEWS) @join__type(graph: REVIEWS, key: "id") { + author: User @join__field(graph: REVIEWS, provides: "username") + body: String @join__field(graph: REVIEWS) + id: ID! @join__field(graph: REVIEWS) + product: Product @join__field(graph: REVIEWS) } -scalar link__Import +type User + @join__owner(graph: ACCOUNTS) + @join__type(graph: ACCOUNTS, key: "id") + @join__type(graph: REVIEWS, key: "id") { + id: ID! @join__field(graph: ACCOUNTS) + name: String @join__field(graph: ACCOUNTS) + reviews: [Review] @join__field(graph: REVIEWS) + username: String @join__field(graph: ACCOUNTS) +} -enum link__Purpose { +enum core__Purpose { """ - `SECURITY` features provide metadata necessary to securely resolve fields. + `EXECUTION` features provide metadata necessary to for operation execution. """ - SECURITY + EXECUTION """ - `EXECUTION` features provide metadata necessary for operation execution. + `SECURITY` features provide metadata necessary to securely resolve fields. """ - EXECUTION -} - -type Product - @join__type(graph: inventory, key: "upc") - @join__type(graph: products, key: "upc") - @join__type(graph: reviews, key: "upc") { - upc: String! - weight: Int @join__field(graph: inventory, external: true) @join__field(graph: products) - price: Int @join__field(graph: inventory, external: true) @join__field(graph: products) - inStock: Boolean @join__field(graph: inventory) - shippingEstimate: Int @join__field(graph: inventory, requires: "price weight") - name: String @join__field(graph: products) - reviews: [Review] @join__field(graph: reviews) -} - -type Query - @join__type(graph: accounts) - @join__type(graph: inventory) - @join__type(graph: products) - @join__type(graph: reviews) { - me: User @join__field(graph: accounts) - users: [User] @join__field(graph: accounts) - topProducts(first: Int): [Product] @join__field(graph: products) + SECURITY } -type Review @join__type(graph: reviews, key: "id") { - id: ID! - body: String - product: Product - author: User @join__field(graph: reviews, provides: "username") -} +scalar join__FieldSet -type User @join__type(graph: accounts, key: "id") @join__type(graph: reviews, key: "id") { - id: ID! - name: String @join__field(graph: accounts) - username: String @join__field(graph: accounts) @join__field(graph: reviews, external: true) - birthDate: String @join__field(graph: accounts) - numberOfReviews: Int @join__field(graph: reviews) - reviews: [Review] @join__field(graph: reviews) +enum join__Graph { + ACCOUNTS @join__graph(name: "accounts", url: "http://accounts:4001/graphql") + INVENTORY @join__graph(name: "inventory", url: "http://inventory:4002/graphql") + PRODUCTS @join__graph(name: "products", url: "http://products:4003/graphql") + REVIEWS @join__graph(name: "reviews", url: "http://reviews:4004/graphql") } diff --git a/packages/fusion/federation/tests/fixtures/supergraphs/pb/supergraph.graphql b/packages/fusion/federation/tests/fixtures/supergraphs/pb/supergraph.graphql index 5a25e064dfd0b..ea86a3a281c45 100644 --- a/packages/fusion/federation/tests/fixtures/supergraphs/pb/supergraph.graphql +++ b/packages/fusion/federation/tests/fixtures/supergraphs/pb/supergraph.graphql @@ -262,6 +262,6 @@ scalar join__FieldSet enum join__Graph { DEMO_FEATURES - @join__graph(name: "demo-features", url: "http://graphql-demo-features-service/graphql") - NODE_RESOLVER @join__graph(name: "node-resolver", url: "http://graphql-node-resolver/graphql") + @join__graph(name: "DEMO_FEATURES", url: "http://graphql-demo-features-service/graphql") + NODE_RESOLVER @join__graph(name: "NODE_RESOLVER", url: "http://graphql-node-resolver/graphql") } diff --git a/packages/serve-runtime/src/types.ts b/packages/serve-runtime/src/types.ts index efb9866fead08..bbcf60ff2bf3d 100644 --- a/packages/serve-runtime/src/types.ts +++ b/packages/serve-runtime/src/types.ts @@ -75,7 +75,7 @@ interface MeshServeConfigWithFusiongraph extends MeshServeConfigWithou /** * Additional GraphQL schema resolvers. */ - additionalResolvers?: IResolvers; + additionalResolvers?: IResolvers | IResolvers[]; /** * Implement custom executors for transports. */ @@ -94,7 +94,7 @@ interface MeshServeConfigWithSupergraph extends MeshServeConfigWithout /** * Additional GraphQL schema resolvers. */ - additionalResolvers?: IResolvers; + additionalResolvers?: IResolvers | IResolvers[]; /** * Implement custom executors for transports. */