Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(runtime): choose correct root type name for the source #4571

Merged
merged 6 commits into from
Sep 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/@graphql-mesh_cli-4571-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@graphql-mesh/cli": patch
---

dependencies updates:

- Updated dependency [`[email protected]` ↗︎](https://www.npmjs.com/package/ws/v/8.9.0) (from `8.8.1`, in `dependencies`)
7 changes: 7 additions & 0 deletions .changeset/@graphql-mesh_grpc-4571-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@graphql-mesh/grpc": patch
---

dependencies updates:

- Updated dependency [`[email protected]` ↗︎](https://www.npmjs.com/package/protobufjs/v/7.1.2) (from `7.1.1`, in `dependencies`)
7 changes: 7 additions & 0 deletions .changeset/@graphql-mesh_http-4571-dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@graphql-mesh/http": patch
---

dependencies updates:

- Updated dependency [`[email protected]` ↗︎](https://www.npmjs.com/package/graphql-yoga/v/3.0.0-next.1) (from `3.0.0-next.0`, in `dependencies`)
5 changes: 5 additions & 0 deletions .changeset/ninety-pots-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-mesh/runtime': patch
---

Choose the root type name for a specific operation type from the source schema not from the gateway schema, because source schema might have a different like `QueryType` instead of `Query`.
12 changes: 8 additions & 4 deletions packages/runtime/src/get-mesh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,14 @@ export function wrapFetchWithPlugins(plugins: MeshPlugin<any>[]): MeshFetch {
}

// Use in-context-sdk for tracing
function createProxyingResolverFactory(apiName: string): CreateProxyingResolverFn {
return function createProxyingResolver() {
function createProxyingResolverFactory(apiName: string, apiSchema: GraphQLSchema): CreateProxyingResolverFn {
return function createProxyingResolver({ operation, fieldName, subschemaConfig }) {
const rootType = apiSchema.getRootType(operation);
return function proxyingResolver(root, args, context, info) {
return context[apiName][info.parentType.name][info.fieldName]({ root, args, context, info });
if (!context[apiName][rootType.name][info.fieldName]) {
throw new Error(`${info.fieldName} couldn't find in ${rootType.name} of ${apiName} as a ${operation}`);
}
return context[apiName][rootType.name][info.fieldName]({ root, args, context, info });
};
};
}
Expand Down Expand Up @@ -197,7 +201,7 @@ export async function getMesh(options: GetMeshOptions): Promise<MeshInstance> {
handler: apiSource.handler,
batch: 'batch' in source ? source.batch : true,
merge: source.merge,
createProxyingResolver: createProxyingResolverFactory(apiName),
createProxyingResolver: createProxyingResolverFactory(apiName, apiSchema),
});
} catch (e: any) {
sourceLogger.error(`Failed to generate the schema`, e);
Expand Down
211 changes: 211 additions & 0 deletions packages/runtime/test/getMesh.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/* eslint-disable import/no-extraneous-dependencies */
import LocalforageCache from '@graphql-mesh/cache-localforage';
import GraphQLHandler from '@graphql-mesh/graphql';
import StitchingMerger from '@graphql-mesh/merger-stitching';
import { InMemoryStoreStorageAdapter, MeshStore } from '@graphql-mesh/store';
import { defaultImportFn, DefaultLogger, PubSub } from '@graphql-mesh/utils';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { printSchemaWithDirectives } from '@graphql-tools/utils';
import { parse } from 'graphql';
import { getMesh } from '../src/get-mesh';
import { MeshResolvedSource } from '../src/types';

describe('getMesh', () => {
const baseDir = __dirname;
let cache: LocalforageCache;
let pubsub: PubSub;
let store: MeshStore;
let logger: DefaultLogger;
let merger: StitchingMerger;
beforeEach(() => {
cache = new LocalforageCache();
pubsub = new PubSub();
store = new MeshStore('test', new InMemoryStoreStorageAdapter(), {
readonly: false,
validate: false,
});
logger = new DefaultLogger('Mesh Test');
merger = new StitchingMerger({
store,
cache,
pubsub,
logger,
});
});

interface CreateSchemaConfiguration {
suffix: string;
suffixRootTypeNames: boolean;
suffixFieldNames: boolean;
suffixResponses: boolean;
}

function createGraphQLSchema(config: CreateSchemaConfiguration) {
const queryTypeName = config.suffixRootTypeNames ? `Query${config.suffix}` : 'Query';
const mutationTypeName = config.suffixRootTypeNames ? `Mutation${config.suffix}` : 'Mutation';
const subscriptionTypeName = config.suffixRootTypeNames ? `Subscription${config.suffix}` : 'Subscription';

return makeExecutableSchema({
typeDefs: `
type ${queryTypeName} {
hello${config.suffixFieldNames ? config.suffix : ''}: String
}

type ${mutationTypeName} {
bye${config.suffixFieldNames ? config.suffix : ''}: String
}

type ${subscriptionTypeName} {
wave${config.suffixFieldNames ? config.suffix : ''}: String
}

schema {
query: ${queryTypeName}
mutation: ${mutationTypeName}
subscription: ${subscriptionTypeName}
}
`,
resolvers: {
[queryTypeName]: {
[`hello${config.suffixFieldNames ? config.suffix : ''}`]: () =>
`Hello from service${config.suffixResponses ? config.suffix : ''}`,
},
[mutationTypeName]: {
[`bye${config.suffixFieldNames ? config.suffix : ''}`]: () =>
`Bye from service${config.suffixResponses ? config.suffix : ''}schema`,
},
},
});
}

function createGraphQLSource(config: CreateSchemaConfiguration): MeshResolvedSource {
const name = `service${config.suffix}`;
return {
name,
handler: new GraphQLHandler({
baseDir,
cache,
pubsub,
name,
config: {
schema: `./schema${config.suffix}.ts`,
},
store,
logger,
async importFn(moduleId) {
if (moduleId.endsWith(`schema${config.suffix}.ts`)) {
return createGraphQLSchema(config);
}
return defaultImportFn(moduleId);
},
}),
transforms: [],
};
}

it('handle sources with different query type names', async () => {
const mesh = await getMesh({
cache,
pubsub,
logger,
sources: new Array(3).fill(0).map((_, i) =>
createGraphQLSource({
suffix: i.toString(),
suffixRootTypeNames: true,
suffixFieldNames: false,
suffixResponses: false,
})
),
merger,
});

expect(printSchemaWithDirectives(mesh.schema)).toMatchInlineSnapshot(`
"schema {
query: Query
mutation: Mutation
subscription: Subscription
}

type Query {
hello: String
}

type Mutation {
bye: String
}

type Subscription {
wave: String
}"
`);

const result = await mesh.execute(
`
{
hello0
hello1
hello2
}
`,
{}
);

expect(result).toMatchInlineSnapshot(`
{
"data": {},
}
`);
});

it('can stitch a mutation field to a query field', async () => {
const mesh = await getMesh({
cache,
pubsub,
logger,
merger,
sources: [
createGraphQLSource({
suffix: 'Foo',
suffixRootTypeNames: false,
suffixFieldNames: true,
suffixResponses: true,
}),
createGraphQLSource({
suffix: 'Bar',
suffixRootTypeNames: false,
suffixFieldNames: true,
suffixResponses: true,
}),
],
additionalTypeDefs: [
parse(/* GraphQL */ `
extend type Mutation {
strikeBack: String
}
`),
],
additionalResolvers: {
Mutation: {
strikeBack: (root, args, context, info) => context.serviceFoo.Query.helloFoo({ root, args, context, info }),
},
},
});

const result = await mesh.execute(
/* GraphQL */ `
mutation {
strikeBack
}
`,
{}
);

expect(result).toMatchInlineSnapshot(`
{
"data": {
"strikeBack": "Hello from serviceFoo",
},
}
`);
});
});