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

Commit

Permalink
feat(stitching): export createDelegatingRequest and delegateRequest m…
Browse files Browse the repository at this point in the history
…ethods.

May be useful for use with dataloaders or memoization.

See ardatan#724
  • Loading branch information
yaacovCR committed Feb 27, 2020
1 parent 12e7f43 commit 38bdcea
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 48 deletions.
25 changes: 24 additions & 1 deletion src/Interfaces.ts
Expand Up @@ -112,19 +112,42 @@ export type SchemaLikeObject =
export function isSubschemaConfig(value: SchemaLikeObject): value is SubschemaConfig {
return !!(value as SubschemaConfig).schema;
}

export interface IDelegateToSchemaOptions<TContext = { [key: string]: any }> {
schema: GraphQLSchema | SubschemaConfig;
operation: Operation;
fieldName: string;
returnType?: GraphQLOutputType;
args?: { [key: string]: any };
context: TContext;
context?: TContext;
info: IGraphQLToolsResolveInfo;
rootValue?: Record<string, any>;
transforms?: Array<Transform>;
skipValidation?: boolean;
}

export interface ICreateDelegatingRequestOptions {
schema: GraphQLSchema | SubschemaConfig;
info: IGraphQLToolsResolveInfo;
operation: Operation;
fieldName: string;
args?: { [key: string]: any };
transforms?: Array<Transform>;
skipValidation?: boolean;
}

export interface IDelegateRequestOptions<TContext = { [key: string]: any }> {
request: Request;
schema: GraphQLSchema | SubschemaConfig;
rootValue?: Record<string, any>;
info: IGraphQLToolsResolveInfo;
operation: Operation;
fieldName: string;
returnType?: GraphQLOutputType;
context?: TContext;
transforms?: Array<Transform>;
}

export type Delegator = ({ document, context, variables }: {
document: DocumentNode;
context?: { [key: string]: any };
Expand Down
148 changes: 102 additions & 46 deletions src/stitching/delegateToSchema.ts
@@ -1,6 +1,5 @@
import {
ArgumentNode,
DocumentNode,
FieldNode,
FragmentDefinitionNode,
Kind,
Expand All @@ -10,20 +9,21 @@ import {
subscribe,
execute,
validate,
VariableDefinitionNode,
GraphQLSchema,
ExecutionResult,
NameNode,
} from 'graphql';

import {
IDelegateToSchemaOptions,
ICreateDelegatingRequestOptions,
IDelegateRequestOptions,
Operation,
Request,
Fetcher,
Delegator,
SubschemaConfig,
isSubschemaConfig,
IGraphQLToolsResolveInfo,
} from '../Interfaces';

import {
Expand All @@ -48,61 +48,73 @@ import { isAsyncIterable } from 'iterall';

export default function delegateToSchema(
options: IDelegateToSchemaOptions | GraphQLSchema,
...args: any[]
): any {
if (options instanceof GraphQLSchema) {
throw new Error(
'Passing positional arguments to delegateToSchema is a deprecated. ' +
'Please pass named parameters instead.',
);
}
return delegateToSchemaImplementation(options);

const {
schema: subschema,
rootValue,
info,
operation = info.operation.operation,
fieldName,
returnType = info.returnType,
args,
context,
transforms = [],
skipValidation,
} = options;

const request = createDelegatingRequest({
schema: subschema,
info,
operation,
fieldName,
args,
transforms,
skipValidation,
});

return delegateRequest({
request,
schema: subschema,
rootValue,
info,
operation,
fieldName,
returnType,
context,
transforms,
});
}

function delegateToSchemaImplementation({
export function createDelegatingRequest({
schema: subschema,
rootValue,
info,
operation = info.operation.operation,
fieldName,
returnType = info.returnType,
args,
context,
transforms = [],
skipValidation,
}: IDelegateToSchemaOptions,
): any {
}: ICreateDelegatingRequestOptions): any {
let targetSchema: GraphQLSchema;
let subschemaConfig: SubschemaConfig;

if (isSubschemaConfig(subschema)) {
subschemaConfig = subschema;
targetSchema = subschemaConfig.schema;
rootValue = rootValue || subschemaConfig.rootValue || info.rootValue;
transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse());
} else {
targetSchema = subschema;
rootValue = rootValue || info.rootValue;
}

const rawDocument: DocumentNode = createDocument(
fieldName,
operation,
info.fieldNodes,
Object.keys(info.fragments).map(
fragmentName => info.fragments[fragmentName],
),
info.operation.variableDefinitions,
info.operation.name,
);

const rawRequest: Request = {
document: rawDocument,
variables: info.variableValues as Record<string, any>,
};
const initialRequest = createInitialRequest(fieldName, operation, info);

transforms = [
new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType),
...transforms,
new ExpandAbstractTypes(info.schema, targetSchema),
];
Expand All @@ -124,41 +136,76 @@ function delegateToSchemaImplementation({
);
}

transforms = transforms.concat([
transforms.push(
new FilterToSchema(targetSchema),
new AddTypenameToAbstract(targetSchema),
]);
);

const processedRequest = applyRequestTransforms(rawRequest, transforms);
const delegatingRequest = applyRequestTransforms(initialRequest, transforms);

if (!skipValidation) {
const errors = validate(targetSchema, processedRequest.document);
const errors = validate(targetSchema, delegatingRequest.document);
if (errors.length > 0) {
throw errors;
}
}

return delegatingRequest;
}

export function delegateRequest({
request,
schema: subschema,
rootValue,
info,
operation = info.operation.operation,
fieldName,
returnType = info.returnType,
context,
transforms = [],
}: IDelegateRequestOptions): any {
let targetSchema: GraphQLSchema;
let subschemaConfig: SubschemaConfig;

if (isSubschemaConfig(subschema)) {
subschemaConfig = subschema;
targetSchema = subschemaConfig.schema;
rootValue = rootValue || subschemaConfig.rootValue || info.rootValue;
transforms = transforms.concat((subschemaConfig.transforms || []).slice().reverse());
} else {
targetSchema = subschema;
rootValue = rootValue || info.rootValue;
}

transforms = [
new CheckResultAndHandleErrors(info, fieldName, subschema, context, returnType),
...transforms,
];

if (operation === 'query' || operation === 'mutation') {

const executor = createExecutor(targetSchema, rootValue, subschemaConfig);

const executionResult: ExecutionResult | Promise<ExecutionResult> = executor({
document: processedRequest.document,
document: request.document,
context,
variables: processedRequest.variables
variables: request.variables
});

if (executionResult instanceof Promise) {
return executionResult.then((originalResult: any) => applyResultTransforms(originalResult, transforms));
} else {
return applyResultTransforms(executionResult, transforms);
}

} else if (operation === 'subscription') {

const subscriber = createSubscriber(targetSchema, rootValue, subschemaConfig);

return subscriber({
document: processedRequest.document,
document: request.document,
context,
variables: processedRequest.variables,
variables: request.variables,
}).then((subscriptionResult: AsyncIterableIterator<ExecutionResult> | ExecutionResult) => {
if (isAsyncIterable(subscriptionResult)) {
// "subscribe" to the subscription result and map the result through the transforms
Expand All @@ -175,20 +222,19 @@ function delegateToSchemaImplementation({
return applyResultTransforms(subscriptionResult, transforms);
}
});

}
}

function createDocument(
function createInitialRequest(
targetField: string,
targetOperation: Operation,
originalSelections: ReadonlyArray<SelectionNode>,
fragments: Array<FragmentDefinitionNode>,
variables: ReadonlyArray<VariableDefinitionNode>,
operationName: NameNode,
): DocumentNode {
info: IGraphQLToolsResolveInfo,
): Request {
let selections: Array<SelectionNode> = [];
let args: Array<ArgumentNode> = [];

const originalSelections: ReadonlyArray<SelectionNode> = info.fieldNodes;
originalSelections.forEach((field: FieldNode) => {
const fieldSelections = field.selectionSet
? field.selectionSet.selections
Expand All @@ -215,6 +261,7 @@ function createDocument(
value: targetField,
},
};

const rootSelectionSet: SelectionSetNode = {
kind: Kind.SELECTION_SET,
selections: [rootField],
Expand All @@ -223,15 +270,24 @@ function createDocument(
const operationDefinition: OperationDefinitionNode = {
kind: Kind.OPERATION_DEFINITION,
operation: targetOperation,
variableDefinitions: variables,
variableDefinitions: info.operation.variableDefinitions,
selectionSet: rootSelectionSet,
name: operationName,
name: info.operation.name,
};

return {
const fragments: Array<FragmentDefinitionNode> = Object.keys(info.fragments).map(
fragmentName => info.fragments[fragmentName],
);

const document = {
kind: Kind.DOCUMENT,
definitions: [operationDefinition, ...fragments],
};

return {
document,
variables: info.variableValues,
};
}

function createExecutor(
Expand Down
4 changes: 3 additions & 1 deletion src/stitching/index.ts
@@ -1,7 +1,7 @@
import makeRemoteExecutableSchema, { createResolver as defaultCreateRemoteResolver } from './makeRemoteExecutableSchema';
import introspectSchema from './introspectSchema';
import mergeSchemas from './mergeSchemas';
import delegateToSchema from './delegateToSchema';
import { default as delegateToSchema, createDelegatingRequest, delegateRequest } from './delegateToSchema';
import defaultMergedResolver from './defaultMergedResolver';
import { createMergedResolver } from './createMergedResolver';
import { dehoistResult, unwrapResult } from './proxiedResult';
Expand All @@ -14,6 +14,8 @@ export {
// These are currently undocumented and not part of official API,
// but exposed for the community use
delegateToSchema,
createDelegatingRequest,
delegateRequest,
defaultCreateRemoteResolver,
defaultMergedResolver,
createMergedResolver,
Expand Down

0 comments on commit 38bdcea

Please sign in to comment.