Skip to content

Commit

Permalink
enhance(stitch): introduce SubschemaSetConfig (#1994)
Browse files Browse the repository at this point in the history
to improve ergonomics of sharing endpoint details between subschemas
  • Loading branch information
yaacovCR committed Sep 4, 2020
1 parent da95b8b commit 6d92ada
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 47 deletions.
18 changes: 14 additions & 4 deletions packages/delegate/src/Subschema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { GraphQLSchema } from 'graphql';

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

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

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

Expand All @@ -16,11 +23,14 @@ export function setObjectSubschema(result: any, subschema: GraphQLSchema | Subsc
}

export function isSubschemaConfig(value: any): value is SubschemaConfig | Subschema {
return Boolean((value as SubschemaConfig).schema);
return Boolean(value.schema && value.permutations === undefined);
}

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

export function isSubschemaSetConfig(value: any): value is SubschemaSetConfig {
return Boolean(value.permutations);
}

export class Subschema {
Expand Down
6 changes: 4 additions & 2 deletions packages/delegate/src/delegateToSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,19 @@ export function delegateRequest({
if (isSubschemaConfig(subschemaOrSubschemaConfig)) {
subschemaConfig = subschemaOrSubschemaConfig;
targetSchema = subschemaConfig.schema;
targetRootValue = rootValue ?? subschemaConfig?.rootValue ?? info?.rootValue;
allTransforms =
subschemaOrSubschemaConfig.transforms != null
? subschemaOrSubschemaConfig.transforms.concat(transforms)
: transforms;
if (subschemaConfig.endpoint != null) {
if (typeof subschemaConfig.endpoint === 'object') {
endpoint = subschemaConfig.endpoint;
} else if (typeof subschemaConfig.endpoint === 'string') {
const stitchingInfo: StitchingInfo = info?.schema.extensions?.stitchingInfo;
endpoint = stitchingInfo.endpoints[subschemaConfig.endpoint];
} else {
endpoint = subschemaConfig;
}
targetRootValue = rootValue ?? endpoint?.rootValue ?? info?.rootValue;
} else {
targetSchema = subschemaOrSubschemaConfig;
targetRootValue = rootValue ?? info?.rootValue;
Expand Down
2 changes: 1 addition & 1 deletion packages/delegate/src/index.ts
Original file line number Diff line number Diff line change
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 { Subschema, isSubschema, isSubschemaConfig, getSubschema } from './Subschema';
export { Subschema, isSubschema, isSubschemaConfig, isSubschemaSetConfig, getSubschema } from './Subschema';

export * from './delegationBindings';
export * from './transforms';
Expand Down
17 changes: 13 additions & 4 deletions packages/delegate/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export interface ICreateProxyingResolverOptions {
export type CreateProxyingResolverFn = (options: ICreateProxyingResolverOptions) => GraphQLFieldResolver<any, any>;

export interface Endpoint<K = any, V = any, C = K> {
rootValue?: Record<string, any>;
executor?: Executor;
subscriber?: Subscriber;
batch?: boolean;
Expand All @@ -142,13 +143,21 @@ export interface NamedEndpoint extends Endpoint {
name: string;
}

export interface SubschemaConfig<K = any, V = any, C = K> extends Endpoint<K, V, C> {
schema: GraphQLSchema;
rootValue?: Record<string, any>;
export interface SubschemaPermutation {
createProxyingResolver?: CreateProxyingResolverFn;
transforms?: Array<Transform>;
merge?: Record<string, MergedTypeConfig>;
endpoint?: string;
}

export interface SubschemaConfig<K = any, V = any, C = K> extends SubschemaPermutation, Endpoint<K, V, C> {
schema: GraphQLSchema;
endpoint?: Endpoint | string;
}

export interface SubschemaSetConfig<K = any, V = any, C = K> extends Endpoint<K, V, C> {
schema: GraphQLSchema;
permutations: Array<SubschemaPermutation>;
endpoint: Endpoint;
}

export interface MergedTypeConfig<K = any, V = any> {
Expand Down
56 changes: 25 additions & 31 deletions packages/delegate/tests/batchExecution.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { graphql, execute, ExecutionResult } from 'graphql';

import { makeExecutableSchema } from '@graphql-tools/schema';
import { delegateToSchema, SubschemaConfig, ExecutionParams, SyncExecutor, NamedEndpoint } from '../src';
import { delegateToSchema, SubschemaConfig, ExecutionParams, SyncExecutor, SubschemaSetConfig } from '../src';
import { stitchSchemas } from '@graphql-tools/stitch';
import { FilterObjectFields } from '@graphql-tools/wrap';

Expand Down Expand Up @@ -104,37 +104,32 @@ describe('batch execution', () => {

let executions = 0;

const endpoint: NamedEndpoint = {
name: 'commonEndpoint',
batch: true,
executor: ((params: ExecutionParams): ExecutionResult => {
executions++;
return execute(innerSchemaA, params.document, undefined, params.context, params.variables) as ExecutionResult;
}) as SyncExecutor
}

const innerSubschemaConfigA1: SubschemaConfig = {
const innerSubschemaSetConfigA: SubschemaSetConfig = {
schema: innerSchemaA,
transforms: [new FilterObjectFields((typeName, fieldName) => typeName !== 'Object' || fieldName !== 'field2')],
merge: {
Object: {
fieldName: 'objectA',
args: () => ({}),
permutations: [{
transforms: [new FilterObjectFields((typeName, fieldName) => typeName !== 'Object' || fieldName !== 'field2')],
merge: {
Object: {
fieldName: 'objectA',
args: () => ({}),
},
},
},
endpoint: 'commonEndpoint',
}

const innerSubschemaConfigA2: SubschemaConfig = {
schema: innerSchemaA,
transforms: [new FilterObjectFields((typeName, fieldName) => typeName !== 'Object' || fieldName !== 'field1')],
merge: {
Object: {
fieldName: 'objectA',
args: () => ({}),
}, {
transforms: [new FilterObjectFields((typeName, fieldName) => typeName !== 'Object' || fieldName !== 'field1')],
merge: {
Object: {
fieldName: 'objectA',
args: () => ({}),
},
},
},
endpoint: 'commonEndpoint',
}],
endpoint: {
batch: true,
executor: ((params: ExecutionParams): ExecutionResult => {
executions++;
return execute(innerSchemaA, params.document, undefined, params.context, params.variables) as ExecutionResult;
}) as SyncExecutor
}
}

const innerSubschemaConfigB: SubschemaConfig = {
Expand All @@ -148,8 +143,7 @@ describe('batch execution', () => {
}

const outerSchema = stitchSchemas({
subschemas: [innerSubschemaConfigA1, innerSubschemaConfigA2, innerSubschemaConfigB],
endpoints: [endpoint],
subschemas: [innerSubschemaSetConfigA, innerSubschemaConfigB],
});

const expectedResult = {
Expand Down
21 changes: 18 additions & 3 deletions packages/stitch/src/stitchSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
import { buildTypeCandidates, buildTypeMap } from './typeCandidates';
import { createStitchingInfo, completeStitchingInfo, addStitchingInfo } from './stitchingInfo';
import { IStitchSchemasOptions } from './types';
import { SubschemaConfig, isSubschemaConfig } from '@graphql-tools/delegate';
import { SubschemaConfig, isSubschemaConfig, isSubschemaSetConfig } from '@graphql-tools/delegate';

export function stitchSchemas({
subschemas = [],
Expand All @@ -53,6 +53,23 @@ export function stitchSchemas({
throw new Error('Expected `resolverValidationOptions` to be an object');
}

let schemaLikeObjects: Array<GraphQLSchema | SubschemaConfig | DocumentNode | GraphQLNamedType> = [];

subschemas.forEach(subschema => {
if (isSubschemaSetConfig(subschema)) {
const { schema, permutations, endpoint } = subschema;
permutations.forEach(permutation => {
schemaLikeObjects.push({
schema,
...permutation,
endpoint,
});
});
} else {
schemaLikeObjects.push(subschema);
}
});

schemas.forEach(schemaLikeObject => {
if (
!isSchema(schemaLikeObject) &&
Expand All @@ -64,8 +81,6 @@ export function stitchSchemas({
throw new Error('Invalid schema passed');
}
});

let schemaLikeObjects: Array<GraphQLSchema | SubschemaConfig | DocumentNode | GraphQLNamedType> = [...subschemas];
schemas.forEach(schemaLikeObject => {
if (isSchema(schemaLikeObject) || isSubschemaConfig(schemaLikeObject)) {
schemaLikeObjects.push(schemaLikeObject);
Expand Down
4 changes: 2 additions & 2 deletions packages/stitch/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
GraphQLInputObjectType,
} from 'graphql';
import { ITypeDefinitions, TypeMap } from '@graphql-tools/utils';
import { SubschemaConfig, NamedEndpoint, Endpoint } from '@graphql-tools/delegate';
import { SubschemaConfig, NamedEndpoint, Endpoint, SubschemaSetConfig } from '@graphql-tools/delegate';
import { IExecutableSchemaDefinition } from '@graphql-tools/schema';

export interface MergeTypeCandidate {
Expand Down Expand Up @@ -61,7 +61,7 @@ export interface StitchingInfo {
export type SchemaLikeObject = SubschemaConfig | GraphQLSchema | string | DocumentNode | Array<GraphQLNamedType>;

export interface IStitchSchemasOptions<TContext = any> extends Omit<IExecutableSchemaDefinition<TContext>, 'typeDefs'> {
subschemas?: Array<GraphQLSchema | SubschemaConfig>;
subschemas?: Array<GraphQLSchema | SubschemaConfig | SubschemaSetConfig>;
endpoints?: Array<NamedEndpoint>;
typeDefs?: ITypeDefinitions;
types?: Array<GraphQLNamedType>;
Expand Down

0 comments on commit 6d92ada

Please sign in to comment.