Skip to content

Commit

Permalink
fix(federation): handle shared root fields (#552)
Browse files Browse the repository at this point in the history
  • Loading branch information
ardatan authored Jan 29, 2025
1 parent 180c2c4 commit b0bc26b
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 29 deletions.
8 changes: 8 additions & 0 deletions .changeset/tiny-rivers-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@graphql-tools/federation': patch
---

Handle shared subscription root fields correctly

In case of conflicting subscription root fields coming from different subgraphs or different entry points(multiple keys),
subscription was failing.
20 changes: 16 additions & 4 deletions packages/federation/src/supergraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
EnumValueDefinitionNode,
FieldDefinitionNode,
FieldNode,
GraphQLFieldResolver,
GraphQLInterfaceType,
GraphQLOutputType,
GraphQLSchema,
Expand Down Expand Up @@ -1216,9 +1217,8 @@ export function getStitchingOptionsFromSupergraphSdl(
}
if (operationType) {
const defaultMergedField = defaultMerger(candidates);
return {
...defaultMergedField,
resolve(_root, _args, context, info) {
const mergedResolver: GraphQLFieldResolver<{}, {}> =
function mergedResolver(_root, _args, context, info) {
const originalSelectionSet: SelectionSetNode = {
kind: Kind.SELECTION_SET,
selections: info.fieldNodes,
Expand Down Expand Up @@ -1361,7 +1361,19 @@ export function getStitchingOptionsFromSupergraphSdl(
return Promise.all(jobs).then((results) => mergeResults(results));
}
return mergeResults(jobs);
},
};
if (operationType === 'subscription') {
return {
...defaultMergedField,
subscribe: mergedResolver,
resolve: function identityFn(payload) {
return payload;
},
};
}
return {
...defaultMergedField,
resolve: mergedResolver,
};
}
const filteredCandidates = candidates.filter((candidate) => {
Expand Down
52 changes: 41 additions & 11 deletions packages/federation/tests/getStitchedSchemaFromLocalSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { createDefaultExecutor } from '@graphql-tools/delegate';
import {
ExecutionRequest,
ExecutionResult,
getDocumentNodeFromSchema,
mapMaybePromise,
} from '@graphql-tools/utils';
import { composeLocalSchemasWithApollo } from '@internal/testing';
import { composeServices } from '@theguild/federation-composition';
import { GraphQLSchema } from 'graphql';
import { kebabCase } from 'lodash';
import { getStitchedSchemaFromSupergraphSdl } from '../src/supergraph';
Expand All @@ -14,21 +16,49 @@ export interface LocalSchemaItem {
schema: GraphQLSchema;
}

export async function getStitchedSchemaFromLocalSchemas(
localSchemas: Record<string, GraphQLSchema>,
export async function getStitchedSchemaFromLocalSchemas({
localSchemas,
onSubgraphExecute,
composeWith = 'apollo',
ignoreRules,
}: {
localSchemas: Record<string, GraphQLSchema>;
onSubgraphExecute?: (
subgraph: string,
executionRequest: ExecutionRequest,
result: ExecutionResult | AsyncIterable<ExecutionResult>,
) => void,
): Promise<GraphQLSchema> {
const supergraphSdl = await composeLocalSchemasWithApollo(
Object.entries(localSchemas).map(([name, schema]) => ({
name,
schema,
url: `http://localhost/${name}`,
})),
);
) => void;
composeWith?: 'apollo' | 'guild';
ignoreRules?: string[];
}): Promise<GraphQLSchema> {
let supergraphSdl: string;
if (composeWith === 'apollo') {
supergraphSdl = await composeLocalSchemasWithApollo(
Object.entries(localSchemas).map(([name, schema]) => ({
name,
schema,
url: `http://localhost/${name}`,
})),
);
} else if (composeWith === 'guild') {
const result = composeServices(
Object.entries(localSchemas).map(([name, schema]) => ({
name,
typeDefs: getDocumentNodeFromSchema(schema),
url: `http://localhost/${name}`,
})),
{ disableValidationRules: ignoreRules },
);
result.errors?.forEach((error) => {
console.error(error);
});
if (!result.supergraphSdl) {
throw new Error('Failed to compose services');
}
supergraphSdl = result.supergraphSdl;
} else {
throw new Error(`Unknown composeWith ${composeWith}`);
}
function createTracedExecutor(name: string, schema: GraphQLSchema) {
const executor = createDefaultExecutor(schema);
return function tracedExecutor(request: ExecutionRequest) {
Expand Down
8 changes: 4 additions & 4 deletions packages/federation/tests/optimizations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,15 +630,15 @@ it('nested recursive requirements', async () => {

const subgraphCalls: Record<string, number> = {};

const schema = await getStitchedSchemaFromLocalSchemas(
{
const schema = await getStitchedSchemaFromLocalSchemas({
localSchemas: {
inventory,
spatial,
},
(subgraph) => {
onSubgraphExecute: (subgraph) => {
subgraphCalls[subgraph] = (subgraphCalls[subgraph] || 0) + 1;
},
);
});

expect(
await normalizedExecutor({
Expand Down
Loading

0 comments on commit b0bc26b

Please sign in to comment.