Skip to content

Commit

Permalink
introduce Subschema class (#1583)
Browse files Browse the repository at this point in the history
* add compiled Subschema class

for use with delegation outside of stitching context to ensure that transformSchema methods are called for all transforms

closes #1565

* reorganize subschema functions into subschema file
  • Loading branch information
yaacovCR committed Jun 3, 2020
1 parent 818e986 commit e5d23fe
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 25 deletions.
46 changes: 46 additions & 0 deletions packages/delegate/src/Subschema.ts
@@ -0,0 +1,46 @@
import { GraphQLSchema } from 'graphql';

import { Transform, applySchemaTransforms } from '@graphql-tools/utils';

import { SubschemaConfig, MergedTypeConfig, CreateProxyingResolverFn, Subscriber, Executor } from './types';

import { FIELD_SUBSCHEMA_MAP_SYMBOL, OBJECT_SUBSCHEMA_SYMBOL } from './symbols';

export function getSubschema(result: any, responseKey: string): GraphQLSchema | SubschemaConfig {
const subschema = result[FIELD_SUBSCHEMA_MAP_SYMBOL] && result[FIELD_SUBSCHEMA_MAP_SYMBOL][responseKey];
return subschema || result[OBJECT_SUBSCHEMA_SYMBOL];
}

export function setObjectSubschema(result: any, subschema: GraphQLSchema | SubschemaConfig) {
result[OBJECT_SUBSCHEMA_SYMBOL] = subschema;
}

export function isSubschemaConfig(value: any): value is SubschemaConfig {
return Boolean((value as SubschemaConfig).schema);
}

export function isSubschema(value: any): value is Subschema {
return Boolean((value as Subschema).transformedSchema);
}

export class Subschema {
public schema: GraphQLSchema;
public rootValue?: Record<string, any>;
public executor?: Executor;
public subscriber?: Subscriber;
public createProxyingResolver?: CreateProxyingResolverFn;
public transforms: Array<Transform>;
public transformedSchema: GraphQLSchema;
public merge?: Record<string, MergedTypeConfig>;

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);
}
}
2 changes: 1 addition & 1 deletion packages/delegate/src/defaultMergedResolver.ts
Expand Up @@ -3,7 +3,7 @@ import { defaultFieldResolver, GraphQLResolveInfo } from 'graphql';
import { getResponseKeyFromInfo, getErrors } from '@graphql-tools/utils';

import { handleResult } from './results/handleResult';
import { getSubschema } from './subschema';
import { getSubschema } from './Subschema';

/**
* Resolver that knows how to:
Expand Down
2 changes: 1 addition & 1 deletion packages/delegate/src/delegateToSchema.ts
Expand Up @@ -36,10 +36,10 @@ import {
IDelegateToSchemaOptions,
IDelegateRequestOptions,
SubschemaConfig,
isSubschemaConfig,
ExecutionParams,
StitchingInfo,
} from './types';
import { isSubschemaConfig } from './Subschema';

export function delegateToSchema(options: IDelegateToSchemaOptions | GraphQLSchema): any {
if (isSchema(options)) {
Expand Down
2 changes: 1 addition & 1 deletion packages/delegate/src/index.ts
Expand Up @@ -3,7 +3,7 @@ export { createRequestFromInfo, createRequest } from './createRequest';
export { defaultMergedResolver } from './defaultMergedResolver';
export { createMergedResolver } from './createMergedResolver';
export { handleResult } from './results/handleResult';
export { getSubschema } from './subschema';
export { Subschema, isSubschema, isSubschemaConfig, getSubschema } from './Subschema';

export * from './transforms';

Expand Down
2 changes: 1 addition & 1 deletion packages/delegate/src/proxiedResult.ts
Expand Up @@ -5,7 +5,7 @@ import { mergeDeep, ERROR_SYMBOL, relocatedError, setErrors, getErrors } from '@
import { handleNull } from './results/handleNull';

import { FIELD_SUBSCHEMA_MAP_SYMBOL, OBJECT_SUBSCHEMA_SYMBOL } from './symbols';
import { getSubschema, setObjectSubschema } from './subschema';
import { getSubschema, setObjectSubschema } from './Subschema';
import { SubschemaConfig } from './types';

export function isProxiedResult(result: any) {
Expand Down
4 changes: 2 additions & 2 deletions packages/delegate/src/results/handleObject.ts
Expand Up @@ -9,9 +9,9 @@ import {
} from 'graphql';

import { collectFields, GraphQLExecutionContext, setErrors, slicedError } from '@graphql-tools/utils';
import { setObjectSubschema } from '../subschema';
import { setObjectSubschema, isSubschemaConfig } from '../Subschema';
import { mergeFields } from '../mergeFields';
import { MergedTypeInfo, SubschemaConfig, isSubschemaConfig } from '../types';
import { MergedTypeInfo, SubschemaConfig } from '../types';

export function handleObject(
type: GraphQLCompositeType,
Expand Down
14 changes: 0 additions & 14 deletions packages/delegate/src/subschema.ts

This file was deleted.

8 changes: 3 additions & 5 deletions packages/delegate/src/types.ts
Expand Up @@ -13,8 +13,10 @@ import {
} from 'graphql';
import { Operation, Transform, Request, TypeMap, ExecutionResult } from '@graphql-tools/utils';

import { Subschema } from './Subschema';

export interface IDelegateToSchemaOptions<TContext = Record<string, any>, TArgs = Record<string, any>> {
schema: GraphQLSchema | SubschemaConfig;
schema: GraphQLSchema | SubschemaConfig | Subschema;
operation?: Operation;
fieldName?: string;
returnType?: GraphQLOutputType;
Expand Down Expand Up @@ -123,10 +125,6 @@ export type MergedTypeResolver = (
selectionSet: SelectionSetNode
) => any;

export function isSubschemaConfig(value: any): value is SubschemaConfig {
return Boolean((value as SubschemaConfig).schema);
}

export interface StitchingInfo {
transformedSchemas: Map<GraphQLSchema | SubschemaConfig, GraphQLSchema>;
fragmentsByField: Record<string, Record<string, InlineFragmentNode>>;
Expand Down
133 changes: 133 additions & 0 deletions 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 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,
},
],
},
},
});
});
});

0 comments on commit e5d23fe

Please sign in to comment.