From 5d225530aa687eb867b7b5b7781b2d94a4960e5c Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Wed, 3 Jun 2020 00:56:47 -0400 Subject: [PATCH] add compiled Subschema class for use with delegation outside of stitching context to ensure that transformSchema methods are called for all transforms closes #1565 --- packages/delegate/src/GraphQLSubschema.ts | 31 +++++ packages/delegate/src/index.ts | 2 + packages/delegate/src/types.ts | 4 +- packages/delegate/tests/transforms.test.ts | 133 +++++++++++++++++++++ 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 packages/delegate/src/GraphQLSubschema.ts create mode 100644 packages/delegate/tests/transforms.test.ts diff --git a/packages/delegate/src/GraphQLSubschema.ts b/packages/delegate/src/GraphQLSubschema.ts new file mode 100644 index 00000000000..01f428733ef --- /dev/null +++ b/packages/delegate/src/GraphQLSubschema.ts @@ -0,0 +1,31 @@ +import { GraphQLSchema } from 'graphql'; + +import { Transform, applySchemaTransforms } from '@graphql-tools/utils'; + +import { SubschemaConfig, MergedTypeConfig, CreateProxyingResolverFn, Subscriber, Executor } from './types'; + +export function isSubschema(value: any): value is Subschema { + return Boolean((value as Subschema).transformedSchema); +} + +export class Subschema { + public schema: GraphQLSchema; + public rootValue?: Record; + public executor?: Executor; + public subscriber?: Subscriber; + public createProxyingResolver?: CreateProxyingResolverFn; + public transforms: Array; + public transformedSchema: GraphQLSchema; + public merge?: Record; + + constructor(config: SubschemaConfig) { + this.schema = config.schema; + this.executor = config.executor; + this.subscriber = config.subscriber; + this.createProxyingResolver = config.createProxyingResolver; + this.transforms = config.transforms ?? []; + this.merge = config.merge; + + this.transformedSchema = applySchemaTransforms(this.schema, this.transforms); + } +} diff --git a/packages/delegate/src/index.ts b/packages/delegate/src/index.ts index 93f95bf2e0d..8b3a5c8457b 100644 --- a/packages/delegate/src/index.ts +++ b/packages/delegate/src/index.ts @@ -7,4 +7,6 @@ export { getSubschema } from './subschema'; export * from './transforms'; +export { Subschema } from './GraphQLSubschema'; + export * from './types'; diff --git a/packages/delegate/src/types.ts b/packages/delegate/src/types.ts index fa115821913..73ec599fd12 100644 --- a/packages/delegate/src/types.ts +++ b/packages/delegate/src/types.ts @@ -13,8 +13,10 @@ import { } from 'graphql'; import { Operation, Transform, Request, TypeMap, ExecutionResult } from '@graphql-tools/utils'; +import { Subschema } from './GraphQLSubschema'; + export interface IDelegateToSchemaOptions, TArgs = Record> { - schema: GraphQLSchema | SubschemaConfig; + schema: GraphQLSchema | SubschemaConfig | Subschema; operation?: Operation; fieldName?: string; returnType?: GraphQLOutputType; diff --git a/packages/delegate/tests/transforms.test.ts b/packages/delegate/tests/transforms.test.ts new file mode 100644 index 00000000000..f290bab5cca --- /dev/null +++ b/packages/delegate/tests/transforms.test.ts @@ -0,0 +1,133 @@ +import { Subschema, delegateToSchema } from '@graphql-tools/delegate'; +import { makeExecutableSchema } from '@graphql-tools/schema'; +import { RenameRootFields, RenameObjectFields } from '@graphql-tools/wrap'; +import { graphql, GraphQLSchema } from 'graphql'; + +describe('can delegate to subschema with transforms', () => { + let sourceSchema: GraphQLSchema; + + beforeAll(() => { + const ITEM = { + id: '123', + // eslint-disable-next-line @typescript-eslint/camelcase + camel_case: "I'm a camel!", + }; + + const targetSchema = makeExecutableSchema({ + typeDefs: ` + type Item { + id: ID! + camel_case: String + } + type ItemConnection { + edges: [ItemEdge!]! + } + type ItemEdge { + node: Item! + } + type Query { + item: Item + allItems: ItemConnection! + } + `, + resolvers: { + Query: { + item: () => ITEM, + allItems: () => ({ + edges: [ + { + node: ITEM, + }, + ], + }), + }, + }, + }); + + const subschema = new Subschema({ + schema: targetSchema, + transforms: [ + new RenameRootFields((_operation, fieldName) => { + if (fieldName === 'allItems') { + return 'items'; + } + return fieldName; + }), + new RenameObjectFields((_typeName, fieldName) => { + if (fieldName === 'camel_case') { + return 'camelCase'; + } + return fieldName; + }), + ] + }); + + sourceSchema = makeExecutableSchema({ + typeDefs: ` + type Item { + id: ID! + camelCase: String + } + type ItemConnection { + edges: [ItemEdge!]! + } + type ItemEdge { + node: Item! + } + type Query { + item: Item + items: ItemConnection! + } + `, + resolvers: { + Query: { + item: (_root, _args, _context, info) => delegateToSchema({ + schema: subschema, + info, + }), + items: (_root, _args, _context, info) => delegateToSchema({ + schema: subschema, + info, + }), + } + } + }) + }); + + test('renaming should work', async () => { + const result = await graphql( + sourceSchema, + ` + query { + item { + camelCase + } + items { + edges { + node { + camelCase + } + } + } + } + `, + ); + + const TRANSFORMED_ITEM = { + camelCase: "I'm a camel!", + }; + + expect(result).toEqual({ + data: { + item: TRANSFORMED_ITEM, + items: { + edges: [ + { + node: TRANSFORMED_ITEM, + }, + ], + }, + }, + }); + }); +});