Skip to content

Commit

Permalink
Some refactor (#3229)
Browse files Browse the repository at this point in the history
* Experiment LRU Cache

No need for deepMerge

use createDefaultExecutor

Few improvements

workflow_dispatch

Not only master

refactor(stitchingInfo): shift more calculation to build time (#3199)

-- run collectFields on selectionSet hints at build time
-- use a fieldNode cache at build time so that at run time we can collect unique fieldNodes simply by using a Set

Use collectFields and ExecutionContext from graphql-js (#3200)

refactor: use Set to avoid repetition (#3202)

* use Set to deduplicate

* further refactor

refactor ExpandAbstractTypes as PrepareGatewayRequest

will consolidate remaining pre-user supplied transforms into this transform to hopefully reduce repetition

also adds memoization of schema metadata

small refactors

add visitorKeys argument

small refactor

import WrapConcreteFields into PrepareGatewayRequest

this does not use the same visitors and does not visit beneath fields, so can just be inlined

info should be optional in DelegationContext

fix types

Consolidate VisitSelectionSets

rename var

internalize PrepareGatewayDocument as transform

as prepareGatewayDocument function

move varName generator out of loop

to prevent recurrent looping

refactor AddArgumentsAsVariables

rename AddArgumentsAsVariables

to FinalizeGatewayRequest to consolidate post custom transforms

arguments should be added to target final targetSchema

not transformedSchema

move FilterToSchema tests to delegate package

starting refacotr of FilterToSchema

add visitorArgs

also -- unfortunately -- requires another visit pass to properly remove variables if an object or interface field does not have selections -- another approach could be to keep track of variable uses, and subtract at the end of the first pass

integrate FilterToSchema into FinalizeGatewayRequest

fold AddTypenameToAbstract into FinalizeGatewayRequest

extract finalizeGatewayRequest function from the transform

retire all delegation transforms

fix build

typo

Experiment LRU Cache

More promise

LRU with Refactor

Fix TS Build

Try

Print instead

More cache

Use JSON.stringify?

Fix caching

* Fix mergeExtensions

* Fix benchmark

* More

* Fix tests

* Remove schemaTransforms

* More fixes

* Fix

* More improvements

* More improvements

* More fixes?

* Refactor w/o caching

* Try sth

* Reduce number of iterations

* Reduce number of iterations

* Small

* Fix build

* Cleanup

* Add changeset

* Fix build

* Fix tests
  • Loading branch information
ardatan committed Jul 26, 2021
1 parent d53e3be commit 8c8d4fc
Show file tree
Hide file tree
Showing 44 changed files with 417 additions and 1,861 deletions.
5 changes: 5 additions & 0 deletions .changeset/mighty-nails-bake.md
@@ -0,0 +1,5 @@
---
'@graphql-tools/utils': major
---

BREAKING CHANGE: remove cloneSchema
5 changes: 2 additions & 3 deletions .github/workflows/benchmark.yml
@@ -1,9 +1,8 @@
name: Benchmark

on:
pull_request:
branches:
- master
pull_request: {}
workflow_dispatch: {}

jobs:
federation-benchmark:
Expand Down
6 changes: 4 additions & 2 deletions benchmark/federation/call.js
Expand Up @@ -3,7 +3,7 @@ const fetch = require('cross-fetch');
fetch('http://localhost:3000/stitching', {
method: 'POST',
headers: {
'content-type': 'application/json'
'content-type': 'application/json',
},
body: JSON.stringify({
query: /* GraphQL */ `
Expand Down Expand Up @@ -49,4 +49,6 @@ fetch('http://localhost:3000/stitching', {
}
`,
}),
}).then(res => res.json()).then(data => console.log(JSON.stringify(data, null, 2)));
})
.then(res => res.json())
.then(data => console.log(JSON.stringify(data, null, 2)));
39 changes: 21 additions & 18 deletions benchmark/federation/monolith.js
Expand Up @@ -59,7 +59,7 @@ const resolvers = {
return object.weight * 0.5;
},
reviews(product) {
return reviews.filter((review) => review.product.upc === product.upc);
return reviews.filter((review) => review.productUpc === product.upc);
},
inStock(product) {
return inventory.find((inv) => inv.upc === product.upc)?.inStock;
Expand All @@ -69,6 +69,9 @@ const resolvers = {
author(review) {
return users.find((user) => user.id === review.authorID);
},
product(review) {
return products.find(product => review.productUpc === product.upc)
}
},
};

Expand Down Expand Up @@ -122,7 +125,7 @@ const definedProducts = [
weight: 50,
},
];
const products = [...Array(listSize)].map((_, index) => definedProducts[index % 3]);
const products = Array(listSize).fill({}).map((_, index) => definedProducts[index % 3]);

const usernames = [
{ id: "1", username: "@ada" },
Expand All @@ -131,27 +134,27 @@ const usernames = [

const reviews = [
{
id: "1",
authorID: "1",
product: { upc: "1" },
body: "Love it!",
id: '1',
authorID: '1',
productUpc: '1',
body: 'Love it!',
},
{
id: "2",
authorID: "1",
product: { upc: "2" },
body: "Too expensive.",
id: '2',
authorID: '1',
productUpc: '2',
body: 'Too expensive.',
},
{
id: "3",
authorID: "2",
product: { upc: "3" },
body: "Could be better.",
id: '3',
authorID: '2',
productUpc: '3',
body: 'Could be better.',
},
{
id: "4",
authorID: "2",
product: { upc: "1" },
body: "Prefer something else.",
id: '4',
authorID: '2',
productUpc: '1',
body: 'Prefer something else.',
},
];
14 changes: 2 additions & 12 deletions benchmark/federation/stitching.js
Expand Up @@ -2,6 +2,7 @@ const { stitchSchemas } = require('@graphql-tools/stitch');
const { federationToStitchingSDL, stitchingDirectives } = require('@graphql-tools/stitching-directives');
const { stitchingDirectivesTransformer } = stitchingDirectives();
const { buildSchema, execute, print } = require('graphql');
const { createDefaultExecutor } = require('@graphql-tools/delegate');

const services = [
require('./services/accounts'),
Expand All @@ -10,17 +11,6 @@ const services = [
require('./services/reviews'),
];

function createExecutor(schema) {
return function serviceExecutor({ document, variables, context }) {
return execute({
schema,
document,
variableValues: variables,
contextValue: context,
});
};
}

async function makeGatewaySchema() {
return stitchSchemas({
subschemaConfigTransforms: [stitchingDirectivesTransformer],
Expand All @@ -32,7 +22,7 @@ function fetchFederationSubschema({ schema, typeDefs }) {
const sdl = federationToStitchingSDL(print(typeDefs));
return {
schema: buildSchema(sdl),
executor: createExecutor(schema),
executor: createDefaultExecutor(schema),
batch: true
};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/batch-execute/package.json
Expand Up @@ -10,8 +10,8 @@
"license": "MIT",
"sideEffects": false,
"main": "dist/index.js",
"module": "dist/index.mjs",
"exports": {
"module": "dist/index.mjs",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs"
Expand Down
24 changes: 9 additions & 15 deletions packages/batch-execute/src/createBatchingExecutor.ts
@@ -1,7 +1,5 @@
import DataLoader from 'dataloader';

import { ValueOrPromise } from 'value-or-promise';

import { ExecutionRequest, Executor, ExecutionResult } from '@graphql-tools/utils';

import { mergeRequests } from './mergeRequests';
Expand All @@ -25,7 +23,7 @@ function createLoadFn(
executor: Executor,
extensionsReducer: (mergedExtensions: Record<string, any>, request: ExecutionRequest) => Record<string, any>
) {
return async (requests: ReadonlyArray<ExecutionRequest>): Promise<Array<ExecutionResult>> => {
return async function batchExecuteLoadFn(requests: ReadonlyArray<ExecutionRequest>): Promise<Array<ExecutionResult>> {
const execBatches: Array<Array<ExecutionRequest>> = [];
let index = 0;
const request = requests[index];
Expand All @@ -48,19 +46,15 @@ function createLoadFn(
}
}

const executionResults: Array<ValueOrPromise<ExecutionResult>> = execBatches.map(execBatch => {
const mergedRequests = mergeRequests(execBatch, extensionsReducer);
return new ValueOrPromise(() => executor(mergedRequests) as ExecutionResult);
});
const results = await Promise.all(
execBatches.map(async execBatch => {
const mergedRequests = mergeRequests(execBatch, extensionsReducer);
const resultBatches = (await executor(mergedRequests)) as ExecutionResult;
return splitResult(resultBatches, execBatch.length);
})
);

return ValueOrPromise.all(executionResults)
.then(resultBatches =>
resultBatches.reduce(
(results, resultBatch, index) => results.concat(splitResult(resultBatch, execBatches[index].length)),
new Array<ExecutionResult<Record<string, any>>>()
)
)
.resolve();
return results.flat();
};
}

Expand Down
33 changes: 19 additions & 14 deletions packages/batch-execute/src/mergeRequests.ts
Expand Up @@ -108,27 +108,32 @@ export function mergeRequests(
}

function prefixRequest(prefix: string, request: ExecutionRequest): ExecutionRequest {
let document = aliasTopLevelFields(prefix, request.document);
const executionVariables = request.variables ?? {};
const variableNames = Object.keys(executionVariables);

if (variableNames.length === 0) {
return { ...request, document };
function prefixNode(node: VariableNode | FragmentDefinitionNode | FragmentSpreadNode) {
return prefixNodeName(node, prefix);
}

document = visit(document, {
[Kind.VARIABLE]: (node: VariableNode) => prefixNodeName(node, prefix),
[Kind.FRAGMENT_DEFINITION]: (node: FragmentDefinitionNode) => prefixNodeName(node, prefix),
[Kind.FRAGMENT_SPREAD]: (node: FragmentSpreadNode) => prefixNodeName(node, prefix),
});
let prefixedDocument = aliasTopLevelFields(prefix, request.document);

const executionVariableNames = Object.keys(executionVariables);

if (executionVariableNames.length > 0) {
prefixedDocument = visit(prefixedDocument, {
[Kind.VARIABLE]: prefixNode,
[Kind.FRAGMENT_DEFINITION]: prefixNode,
[Kind.FRAGMENT_SPREAD]: prefixNode,
}) as DocumentNode;
}

const prefixedVariables = {};

const prefixedVariables = variableNames.reduce((acc, name) => {
acc[prefix + name] = executionVariables[name];
return acc;
}, Object.create(null));
for (const variableName of executionVariableNames) {
prefixedVariables[prefix + variableName] = executionVariables[variableName];
}

return {
document,
document: prefixedDocument,
variables: prefixedVariables,
operationType: request.operationType,
};
Expand Down
43 changes: 24 additions & 19 deletions packages/delegate/src/Transformer.ts
Expand Up @@ -29,31 +29,36 @@ export class Transformer<TContext = Record<string, any>> {
}

public transformRequest(originalRequest: ExecutionRequest): ExecutionRequest {
const preparedRequest = {
let request = {
...originalRequest,
document: prepareGatewayDocument(originalRequest.document, this.delegationContext),
document: prepareGatewayDocument(
originalRequest.document,
this.delegationContext.transformedSchema,
this.delegationContext.returnType,
this.delegationContext.info?.schema
),
};

const transformedRequest = this.transformations.reduce(
(request: ExecutionRequest, transformation: Transformation) =>
transformation.transform.transformRequest != null
? transformation.transform.transformRequest(request, this.delegationContext, transformation.context)
: request,
preparedRequest
);
for (const transformation of this.transformations) {
if (transformation.transform.transformRequest) {
request = transformation.transform.transformRequest(request, this.delegationContext, transformation.context);
}
}

return finalizeGatewayRequest(transformedRequest, this.delegationContext);
return finalizeGatewayRequest(request, this.delegationContext);
}

public transformResult(originalResult: ExecutionResult): any {
const transformedResult = this.transformations.reduceRight(
(result: ExecutionResult, transformation: Transformation) =>
transformation.transform.transformResult != null
? transformation.transform.transformResult(result, this.delegationContext, transformation.context)
: result,
originalResult
);
public transformResult(originalResult: ExecutionResult) {
let result = originalResult;

// from rigth to left
for (let i = this.transformations.length - 1; i >= 0; i--) {
const transformation = this.transformations[i];
if (transformation.transform.transformResult) {
result = transformation.transform.transformResult(result, this.delegationContext, transformation.context);
}
}

return checkResultAndHandleErrors(transformedResult, this.delegationContext);
return checkResultAndHandleErrors(result, this.delegationContext);
}
}
4 changes: 1 addition & 3 deletions packages/delegate/src/applySchemaTransforms.ts
@@ -1,7 +1,5 @@
import { GraphQLSchema } from 'graphql';

import { cloneSchema } from '@graphql-tools/utils';

import { SubschemaConfig } from './types';

export function applySchemaTransforms(
Expand All @@ -18,7 +16,7 @@ export function applySchemaTransforms(
return schemaTransforms.reduce(
(schema: GraphQLSchema, transform) =>
transform.transformSchema != null
? transform.transformSchema(cloneSchema(schema), subschemaConfig, transformedSchema)
? transform.transformSchema(schema, subschemaConfig, transformedSchema)
: schema,
originalWrappingSchema
);
Expand Down
32 changes: 16 additions & 16 deletions packages/delegate/src/createRequest.ts
Expand Up @@ -79,16 +79,19 @@ export function createRequest({
info,
}: ICreateRequest): ExecutionRequest {
let newSelectionSet: SelectionSetNode | undefined;
let argumentNodeMap: Record<string, ArgumentNode>;
const argumentNodeMap: Record<string, ArgumentNode> = Object.create(null);

if (selectionSet != null) {
newSelectionSet = selectionSet;
argumentNodeMap = Object.create(null);
} else {
const selections: Array<SelectionNode> = (fieldNodes ?? []).reduce(
(acc, fieldNode) => (fieldNode.selectionSet != null ? acc.concat(fieldNode.selectionSet.selections) : acc),
[] as Array<SelectionNode>
);
const selections: Array<SelectionNode> = [];
for (const fieldNode of fieldNodes || []) {
if (fieldNode.selectionSet) {
for (const selection of fieldNode.selectionSet.selections) {
selections.push(selection);
}
}
}

newSelectionSet = selections.length
? {
Expand All @@ -97,17 +100,11 @@ export function createRequest({
}
: undefined;

argumentNodeMap = {};

const args = fieldNodes?.[0]?.arguments;
if (args) {
argumentNodeMap = args.reduce(
(prev, curr) => ({
...prev,
[curr.name.value]: curr,
}),
argumentNodeMap
);
for (const argNode of args) {
argumentNodeMap[argNode.name.value] = argNode;
}
}
}

Expand Down Expand Up @@ -173,7 +170,10 @@ export function createRequest({
const definitions: Array<DefinitionNode> = [operationDefinition];

if (fragments != null) {
definitions.push(...Object.values(fragments));
for (const fragmentName in fragments) {
const fragment = fragments[fragmentName];
definitions.push(fragment);
}
}

const document: DocumentNode = {
Expand Down
2 changes: 1 addition & 1 deletion packages/delegate/src/defaultMergedResolver.ts
Expand Up @@ -3,7 +3,7 @@ import { defaultFieldResolver, GraphQLResolveInfo } from 'graphql';
import { getResponseKeyFromInfo } from '@graphql-tools/utils';

import { resolveExternalValue } from './resolveExternalValue';
import { getSubschema, getUnpathedErrors, isExternalObject } from './externalObjects';
import { getSubschema, getUnpathedErrors, isExternalObject } from './mergeFields';
import { ExternalObject } from './types';

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/delegate/src/delegateToSchema.ts
Expand Up @@ -208,7 +208,7 @@ function getExecutor<TContext>(delegationContext: DelegationContext<TContext>):

const defaultExecutorCache = new WeakMap<GraphQLSchema, Executor>();

function createDefaultExecutor(schema: GraphQLSchema): Executor {
export function createDefaultExecutor(schema: GraphQLSchema): Executor {
let defaultExecutor = defaultExecutorCache.get(schema);
if (!defaultExecutor) {
defaultExecutor = function defaultExecutor({
Expand Down

0 comments on commit 8c8d4fc

Please sign in to comment.