Skip to content

Commit

Permalink
Introduce 'batch' flag, improve loadSchema and merge wrapped schemas …
Browse files Browse the repository at this point in the history
…properly (#4535)

* Introduce 'batch' flag, improve loadSchema and merge wrapped schemas properly

* Update description and param name

* Go
  • Loading branch information
ardatan committed Jun 23, 2022
1 parent 6df204d commit 4914970
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 10 deletions.
7 changes: 7 additions & 0 deletions .changeset/angry-bobcats-double.md
@@ -0,0 +1,7 @@
---
'@graphql-tools/load': patch
---

No longer call `mergeSchemas` if a single schema is loaded.
Previously all typeDefs and resolvers were extracted and the schema was rebuilt from scratch.
But this is not necessary if there is only one schema loaded with `loadSchema`
9 changes: 9 additions & 0 deletions .changeset/gold-timers-thank.md
@@ -0,0 +1,9 @@
---
'@graphql-tools/load': minor
'@graphql-tools/schema': minor
'@graphql-tools/utils': minor
---

`mergeSchemas` was skipping `defaultFieldResolver` and `defaultMergedResolver` by default while extracting resolvers for each given schema to reduce the overhead. But this doesn't work properly if you mix wrapped schemas and local schemas. So new `includeDefaultMergedResolver` flag is introduced in `getResolversFromSchema` to put default "proxy" resolvers in the extracted resolver map for `mergeSchemas`.

This fixes an issue with alias issue, so nested aliased fields weren't resolved properly because of the missing `defaultMergedResolver` in the final merged schema which should come from the wrapped schema.
5 changes: 5 additions & 0 deletions .changeset/sweet-clouds-grab.md
@@ -0,0 +1,5 @@
---
'@graphql-tools/url-loader': minor
---

New 'batch' flag! Now you can configure your remote schema to batch parallel queries to the upstream.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -79,7 +79,7 @@
"packages/**/src/**/*.{ts,tsx}": [
"eslint --fix"
],
"**/*.{ts,tsx,graphql,yml}": [
"**/*.{ts,tsx,graphql,yml,md,mdx}": [
"prettier --write"
]
},
Expand Down
3 changes: 2 additions & 1 deletion packages/load/src/schema.ts
Expand Up @@ -37,13 +37,14 @@ export async function loadSchema(
});

const { schemas, typeDefs } = collectSchemasAndTypeDefs(sources);
schemas.push(...(options.schemas ?? []));
const mergeSchemasOptions: MergeSchemasConfig = {
...options,
schemas: schemas.concat(options.schemas ?? []),
typeDefs,
};

const schema = mergeSchemas(mergeSchemasOptions);
const schema = typeDefs?.length === 0 && schemas?.length === 1 ? schemas[0] : mergeSchemas(mergeSchemasOptions);

if (options?.includeSources) {
includeSources(schema, sources);
Expand Down
5 changes: 5 additions & 0 deletions packages/loaders/url/src/index.ts
Expand Up @@ -133,6 +133,10 @@ export interface LoadFromUrlOptions extends BaseLoaderOptions, Partial<Introspec
* Connection Parameters for WebSockets connection
*/
connectionParams?: any;
/**
* Enable Batching
*/
batch?: boolean;
}

function isCompatibleUri(uri: string): boolean {
Expand Down Expand Up @@ -815,6 +819,7 @@ export class UrlLoader implements Loader<LoadFromUrlOptions> {
source.schema = wrapSchema({
schema: source.schema,
executor,
batch: options?.batch,
});
}

Expand Down
59 changes: 59 additions & 0 deletions packages/loaders/url/tests/url-loader.spec.ts
Expand Up @@ -31,6 +31,7 @@ import { createServer } from '@graphql-yoga/node';
import { GraphQLLiveDirectiveSDL, useLiveQuery } from '@envelop/live-query';
import { InMemoryLiveQueryStore } from '@n1ru4l/in-memory-live-query-store';
import { LiveExecutionResult } from '@n1ru4l/graphql-live-query';
import { loadSchema } from '@graphql-tools/load';

describe('Schema URL Loader', () => {
const loader = new UrlLoader();
Expand All @@ -45,7 +46,27 @@ scalar Upload
type CustomQuery {
"""Test field comment"""
a(testVariable: String): String
complexField(complexArg: ComplexInput): ComplexType
}
input ComplexInput {
id: ID
}
input ComplexChildInput {
id: ID
}
type ComplexType {
id: ID
complexChildren(complexChildArg: ComplexChildInput): [ComplexChild]
}
type ComplexChild {
id: ID
}
type Mutation {
uploadFile(file: Upload, dummyVar: TestInput, secondDummyVar: String): File
}
Expand All @@ -69,6 +90,14 @@ input TestInput {
const testResolvers = {
CustomQuery: {
a: (_: never, { testVariable }: { testVariable: string }) => testVariable || 'a',
complexField: (_: never, { complexArg }: { complexArg: { id: string } }) => {
return complexArg;
},
},
ComplexType: {
complexChildren: (_: never, { complexChildArg }: { complexChildArg: { id: string } }) => {
return [{ id: complexChildArg.id }];
},
},
Upload: GraphQLUpload,
File: {
Expand Down Expand Up @@ -818,6 +847,36 @@ input TestInput {
});
});

it('should handle aliases properly', async () => {
const yoga = createServer({
schema: testSchema,
port: 9876,
logging: false,
});
httpServer = await yoga.start();
const schema = await loadSchema(`http://0.0.0.0:9876/graphql`, {
loaders: [loader],
});
const document = parse(/* GraphQL */ `
query {
b: a
foo: complexField(complexArg: { id: "FOO" }) {
id
bar: complexChildren(complexChildArg: { id: "BAR" }) {
id
}
}
}
`);
const result: any = await execute({
schema,
document,
});
expect(result?.data?.['b']).toBe('a');
expect(result?.data?.['foo']?.id).toBe('FOO');
expect(result?.data?.['foo']?.bar?.[0]?.id).toBe('BAR');
});

describe('sync', () => {
it('should handle introspection', () => {
const [{ schema }] = loader.loadSync(`https://swapi-graphql.netlify.app/.netlify/functions/index`, {});
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/src/merge-schemas.ts
Expand Up @@ -27,7 +27,7 @@ export function mergeSchemas(config: MergeSchemasConfig) {
const schemas = config.schemas || [];
for (const schema of schemas) {
extractedTypeDefs.push(schema);
extractedResolvers.push(getResolversFromSchema(schema));
extractedResolvers.push(getResolversFromSchema(schema, true));
extractedSchemaExtensions.push(extractExtensionsFromSchema(schema));
}

Expand Down
21 changes: 15 additions & 6 deletions packages/utils/src/getResolversFromSchema.ts
Expand Up @@ -11,7 +11,11 @@ import {

import { IResolvers } from './Interfaces';

export function getResolversFromSchema(schema: GraphQLSchema): IResolvers {
export function getResolversFromSchema(
schema: GraphQLSchema,
// Include default merged resolvers
includeDefaultMergedResolver?: boolean
): IResolvers {
const resolvers = Object.create(null);

const typeMap = schema.getTypeMap();
Expand Down Expand Up @@ -59,11 +63,16 @@ export function getResolversFromSchema(schema: GraphQLSchema): IResolvers {
resolvers[typeName][fieldName] = resolvers[typeName][fieldName] || {};
resolvers[typeName][fieldName].subscribe = field.subscribe;
}
if (
field.resolve != null &&
field.resolve?.name !== 'defaultFieldResolver' &&
field.resolve?.name !== 'defaultMergedResolver'
) {
if (field.resolve != null && field.resolve?.name !== 'defaultFieldResolver') {
switch (field.resolve?.name) {
case 'defaultMergedResolver':
if (!includeDefaultMergedResolver) {
continue;
}
break;
case 'defaultFieldResolver':
continue;
}
resolvers[typeName][fieldName] = resolvers[typeName][fieldName] || {};
resolvers[typeName][fieldName].resolve = field.resolve;
}
Expand Down
24 changes: 23 additions & 1 deletion packages/wrap/tests/makeRemoteExecutableSchema.test.ts
Expand Up @@ -17,7 +17,7 @@ describe('remote queries', () => {
schema = wrapSchema(remoteSubschemaConfig);
});

test('should work', async () => {
test('should handle interfaces correctly', async () => {
const query = /* GraphQL */ `
{
interfaceTest(kind: ONE) {
Expand Down Expand Up @@ -46,6 +46,28 @@ describe('remote queries', () => {
const result = await graphql({ schema, source: query });
expect(result).toEqual(expected);
});

test('should handle aliases properly', async () => {
const query = /* GraphQL */ `
query AliasedExample {
propertyInAnArray: properties(limit: 1) {
id
name
loc: location {
title: name
}
}
}
`;

const result: any = await graphql({ schema, source: query });

expect(result?.data?.['propertyInAnArray']).toHaveLength(1);
expect(result?.data?.['propertyInAnArray'][0]['id']).toBeTruthy();
expect(result?.data?.['propertyInAnArray'][0]['name']).toBeTruthy();
expect(result?.data?.['propertyInAnArray'][0]['loc']).toBeTruthy();
expect(result?.data?.['propertyInAnArray'][0]['loc']['title']).toBeTruthy();
});
});

describe('remote subscriptions', () => {
Expand Down

0 comments on commit 4914970

Please sign in to comment.