Skip to content
This repository has been archived by the owner on Apr 15, 2020. It is now read-only.

Commit

Permalink
feat(stitching): allow delegateToSchema, mergeSchemas and transformSc…
Browse files Browse the repository at this point in the history
…hema to take remote schema configurations as parameters

This removes the need for makeRemoteExecutableSchema, removing an unnecessary layer of schema delegation.

This change introduces a RemoteGraphQLSchema type which consists of a GraphQLSchema object annotated with link, fetcher, or dispatcher properties that can be used by delegateToSchema to access the remote schema. The dispatcher function takes the graphql context eventually passed to delegateToSchema as an argument and returns a link or fetcher function.
  • Loading branch information
yaacovCR committed Sep 22, 2019
1 parent d4fec68 commit 48cdd59
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 125 deletions.
51 changes: 51 additions & 0 deletions src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
GraphQLField,
ExecutionResult,
GraphQLType,
GraphQLNamedType,
GraphQLFieldResolver,
GraphQLResolveInfo,
GraphQLIsTypeOfFn,
Expand All @@ -13,6 +14,8 @@ import {

import { SchemaDirectiveVisitor } from './schemaVisitor';

import { ApolloLink } from 'apollo-link';

/* TODO: Add documentation */

export type UnitOrList<Type> = Type | Array<Type>;
Expand Down Expand Up @@ -49,6 +52,46 @@ export interface IGraphQLToolsResolveInfo extends GraphQLResolveInfo {
mergeInfo?: MergeInfo;
}

export type Fetcher = (operation: IFetcherOperation) => Promise<ExecutionResult>;

export interface IFetcherOperation {
query: DocumentNode;
operationName?: string;
variables?: { [key: string]: any };
context?: { [key: string]: any };
}

export type Dispatcher = (context: any) => ApolloLink | Fetcher;

export type SchemaExecutionConfig = {
schema: GraphQLSchemaWithTransforms;
};

export type GraphQLSchemaWithTransforms = GraphQLSchema & { transforms?: Array<Transform> };

export type RemoteSchemaExecutionConfig = {
schema: GraphQLSchemaWithTransforms;
link?: ApolloLink;
fetcher?: Fetcher;
dispatcher?: Dispatcher;
};

export function isSchemaExecutionConfig(
schema: string | GraphQLSchema | SchemaExecutionConfig | DocumentNode | Array<GraphQLNamedType>
): schema is SchemaExecutionConfig {
return !!(schema as SchemaExecutionConfig).schema;
}

export function isRemoteSchemaExecutionConfig(
schema: GraphQLSchema | SchemaExecutionConfig
): schema is RemoteSchemaExecutionConfig {
return (
!!(schema as RemoteSchemaExecutionConfig).dispatcher ||
!!(schema as RemoteSchemaExecutionConfig).link ||
!!(schema as RemoteSchemaExecutionConfig).fetcher
);
}

export interface IDelegateToSchemaOptions<TContext = { [key: string]: any }> {
schema: GraphQLSchema;
operation: Operation;
Expand All @@ -58,8 +101,16 @@ export interface IDelegateToSchemaOptions<TContext = { [key: string]: any }> {
info: IGraphQLToolsResolveInfo;
transforms?: Array<Transform>;
skipValidation?: boolean;
executor?: Delegator;
subscriber?: Delegator;
}

export type Delegator = ({ document, context, variables }: {
document: DocumentNode;
context?: { [key: string]: any };
variables?: { [key: string]: any };
}) => any;

export type MergeInfo = {
delegate: (
type: 'query' | 'mutation' | 'subscription',
Expand Down
60 changes: 60 additions & 0 deletions src/stitching/delegateToRemoteSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
IDelegateToSchemaOptions,
RemoteSchemaExecutionConfig,
Fetcher
} from '../Interfaces';
import { ApolloLink } from 'apollo-link';
import { observableToAsyncIterable } from './observableToAsyncIterable';
import linkToFetcher, { execute } from './linkToFetcher';
import delegateToSchema from './delegateToSchema';

export default function delegateToRemoteSchema(
options: IDelegateToSchemaOptions & RemoteSchemaExecutionConfig
): Promise<any> {
if (options.operation === 'query' || options.operation === 'mutation') {
let fetcher: Fetcher;
if (options.dispatcher) {
const dynamicLinkOrFetcher = options.dispatcher(context);
fetcher = (typeof dynamicLinkOrFetcher === 'function') ?
dynamicLinkOrFetcher :
linkToFetcher(dynamicLinkOrFetcher);
} else if (options.link) {
fetcher = linkToFetcher(options.link);
} else {
fetcher = options.fetcher;
}

if (!options.executor) {
options.executor = ({ document, context, variables }) => fetcher({
query: document,
variables,
context: { graphqlContext: context }
});
}

return delegateToSchema(options);
}

if (options.operation === 'subscription') {
let link: ApolloLink;
if (options.dispatcher) {
link = options.dispatcher(context) as ApolloLink;
} else {
link = options.link;
}

if (!options.subscriber) {
options.subscriber = ({ document, context, variables }) => {
const operation = {
query: document,
variables,
context: { graphqlContext: context }
};
const observable = execute(link, operation);
return observableToAsyncIterable(observable);
};
}

return delegateToSchema(options);
}
}
52 changes: 36 additions & 16 deletions src/stitching/delegateToSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import {
NameNode,
} from 'graphql';

import { Operation, Request, IDelegateToSchemaOptions } from '../Interfaces';
import {
IDelegateToSchemaOptions,
Operation,
Request,
} from '../Interfaces';

import {
applyRequestTransforms,
Expand Down Expand Up @@ -93,29 +97,45 @@ async function delegateToSchemaImplementation(
}

if (operation === 'query' || operation === 'mutation') {
if (!options.executor) {
options.executor = ({ document, context, variables }) => execute({
schema: options.schema,
document,
rootValue: info.rootValue,
contextValue: context,
variableValues: variables,
});
}

return applyResultTransforms(
await execute(
options.schema,
processedRequest.document,
info.rootValue,
options.context,
processedRequest.variables,
),
await options.executor({
document: processedRequest.document,
context: options.context,
variables: processedRequest.variables
}),
transforms,
);
}

if (operation === 'subscription') {
const executionResult = (await subscribe(
options.schema,
processedRequest.document,
info.rootValue,
options.context,
processedRequest.variables,
)) as AsyncIterator<ExecutionResult>;
if (!options.subscriber) {
options.subscriber = ({ document, context, variables }) => subscribe({
schema: options.schema,
document,
rootValue: info.rootValue,
contextValue: context,
variableValues: variables,
});
}

const originalAsyncIterator = (await options.subscriber({
document: processedRequest.document,
context: options.context,
variables: processedRequest.variables,
})) as AsyncIterator<ExecutionResult>;

// "subscribe" to the subscription result and map the result through the transforms
return mapAsyncIterator<ExecutionResult, any>(executionResult, result => {
return mapAsyncIterator<ExecutionResult, any>(originalAsyncIterator, result => {
const transformedResult = applyResultTransforms(result, transforms);

// wrap with fieldName to return for an additional round of resolutioon
Expand Down
2 changes: 2 additions & 0 deletions src/stitching/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolv
import introspectSchema from './introspectSchema';
import mergeSchemas from './mergeSchemas';
import delegateToSchema from './delegateToSchema';
import delegateToRemoteSchema from './delegateToRemoteSchema';
import defaultMergedResolver from './defaultMergedResolver';

export {
Expand All @@ -11,6 +12,7 @@ export {
// Those are currently undocumented and not part of official API,
// but exposed for the community use
delegateToSchema,
delegateToRemoteSchema,
defaultMergedResolver,
defaultCreateRemoteResolver
};
2 changes: 1 addition & 1 deletion src/stitching/introspectSchema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GraphQLSchema, DocumentNode } from 'graphql';
import { introspectionQuery, buildClientSchema, parse } from 'graphql';
import { ApolloLink } from 'apollo-link';
import { Fetcher } from './makeRemoteExecutableSchema';
import { Fetcher } from '../Interfaces';
import linkToFetcher from './linkToFetcher';

const parsedIntrospectionQuery: DocumentNode = parse(introspectionQuery);
Expand Down
4 changes: 2 additions & 2 deletions src/stitching/linkToFetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fetcher, FetcherOperation } from './makeRemoteExecutableSchema';
import { Fetcher, IFetcherOperation } from '../Interfaces';

import {
ApolloLink, // This import doesn't actually import code - only the types.
Expand All @@ -10,7 +10,7 @@ import {
export { execute } from 'apollo-link';

export default function linkToFetcher(link: ApolloLink): Fetcher {
return (fetcherOperation: FetcherOperation) => {
return (fetcherOperation: IFetcherOperation) => {
return makePromise(execute(link, fetcherOperation as GraphQLRequest));
};
}
13 changes: 1 addition & 12 deletions src/stitching/makeRemoteExecutableSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,15 @@ import {
GraphQLBoolean,
GraphQLInt,
GraphQLScalarType,
ExecutionResult,
buildSchema,
printSchema,
Kind,
GraphQLResolveInfo,
DocumentNode,
BuildSchemaOptions
} from 'graphql';
import linkToFetcher, { execute } from './linkToFetcher';
import isEmptyObject from '../isEmptyObject';
import { IResolvers, IResolverObject } from '../Interfaces';
import { IResolvers, IResolverObject, Fetcher } from '../Interfaces';
import { makeExecutableSchema } from '../makeExecutableSchema';
import { recreateType } from './schemaRecreation';
import resolveParentFromTypename from './resolveFromParentTypename';
Expand All @@ -40,15 +38,6 @@ export type ResolverFn = (
info?: GraphQLResolveInfo
) => AsyncIterator<any>;

export type Fetcher = (operation: FetcherOperation) => Promise<ExecutionResult>;

export type FetcherOperation = {
query: DocumentNode;
operationName?: string;
variables?: { [key: string]: any };
context?: { [key: string]: any };
};

export default function makeRemoteExecutableSchema({
schema,
link,
Expand Down

0 comments on commit 48cdd59

Please sign in to comment.