From 27e6e987a9b1ed32be81a9ae0884ad7a743fa6e3 Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Tue, 10 Dec 2019 21:38:04 +0900 Subject: [PATCH 01/11] optimize applyMiddlewares for simple case --- src/resolvers/helpers.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/resolvers/helpers.ts b/src/resolvers/helpers.ts index cf0f32b52..660b36728 100644 --- a/src/resolvers/helpers.ts +++ b/src/resolvers/helpers.ts @@ -71,12 +71,15 @@ export function applyAuthChecker( } } -export async function applyMiddlewares( +export function applyMiddlewares( container: IOCContainer, resolverData: ResolverData, middlewares: Array>, resolverHandlerFunction: () => any, ): Promise { + if (middlewares.length === 0) { + return resolverHandlerFunction(); + } let middlewaresIndex = -1; async function dispatchHandler(currentIndex: number): Promise { if (currentIndex <= middlewaresIndex) { From 9218785f40d74070f9b442a517ff88a289751a16 Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Tue, 10 Dec 2019 22:05:33 +0900 Subject: [PATCH 02/11] optimize createBasicFieldResolver --- src/resolvers/create.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/resolvers/create.ts b/src/resolvers/create.ts index e161d103b..97fa988f6 100644 --- a/src/resolvers/create.ts +++ b/src/resolvers/create.ts @@ -86,13 +86,8 @@ export function createBasicFieldResolver( const middlewares = globalMiddlewares.concat(fieldMetadata.middlewares!); applyAuthChecker(middlewares, authMode, authChecker, fieldMetadata.roles); - return async (root, args, context, info) => { + return (root, args, context, info) => { const resolverData: ResolverData = { root, args, context, info }; - return await applyMiddlewares( - container, - resolverData, - middlewares, - () => root[fieldMetadata.name], - ); + return applyMiddlewares(container, resolverData, middlewares, () => root[fieldMetadata.name]); }; } From 255e643fbe80d272d6cf09bedb987ccfff8eebad Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Tue, 10 Dec 2019 22:13:12 +0900 Subject: [PATCH 03/11] add isPromise --- src/utils/index.ts | 1 + src/utils/isPromise.ts | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 src/utils/isPromise.ts diff --git a/src/utils/index.ts b/src/utils/index.ts index ae0474d9b..5b89e41fc 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export { defaultPrintSchemaOptions, } from "./emitSchemaDefinitionFile"; export { ContainerType, ContainerGetter } from "./container"; +export { isPromise } from "./isPromise"; diff --git a/src/utils/isPromise.ts b/src/utils/isPromise.ts new file mode 100644 index 000000000..a97644669 --- /dev/null +++ b/src/utils/isPromise.ts @@ -0,0 +1,3 @@ +export function isPromise(value: PromiseLike | T): value is PromiseLike { + return Boolean(value && typeof (value as any).then === "function"); +} From 6a08dc13e9c781e001901eee986dd1c573c14cec Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Tue, 10 Dec 2019 22:19:27 +0900 Subject: [PATCH 04/11] optimize getParams --- src/resolvers/helpers.ts | 92 +++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/src/resolvers/helpers.ts b/src/resolvers/helpers.ts index 660b36728..7bdf5e767 100644 --- a/src/resolvers/helpers.ts +++ b/src/resolvers/helpers.ts @@ -9,55 +9,59 @@ import { Middleware, MiddlewareFn, MiddlewareClass } from "../interfaces/Middlew import { IOCContainer } from "../utils/container"; import { AuthMiddleware } from "../helpers/auth-middleware"; import { convertArgsToInstance, convertArgToInstance } from "./convert-args"; +import { isPromise } from "../utils/isPromise"; -export async function getParams( +export function getParams( params: ParamMetadata[], resolverData: ResolverData, globalValidate: boolean | ValidatorOptions, pubSub: PubSubEngine, -): Promise { - return Promise.all( - params - .sort((a, b) => a.index - b.index) - .map(async paramInfo => { - switch (paramInfo.kind) { - case "args": - return await validateArg( - convertArgsToInstance(paramInfo, resolverData.args), - globalValidate, - paramInfo.validate, - ); - case "arg": - return await validateArg( - convertArgToInstance(paramInfo, resolverData.args), - globalValidate, - paramInfo.validate, - ); - case "context": - if (paramInfo.propertyName) { - return resolverData.context[paramInfo.propertyName]; - } - return resolverData.context; - case "root": - const rootValue = paramInfo.propertyName - ? resolverData.root[paramInfo.propertyName] - : resolverData.root; - if (!paramInfo.getType) { - return rootValue; - } - return convertToType(paramInfo.getType(), rootValue); - case "info": - return resolverData.info; - case "pubSub": - if (paramInfo.triggerKey) { - return (payload: any) => pubSub.publish(paramInfo.triggerKey!, payload); - } - return pubSub; - case "custom": - return await paramInfo.resolver(resolverData); - } - }), - ); +): Promise | any[] { + const paramValues = params + .sort((a, b) => a.index - b.index) + .map(paramInfo => { + switch (paramInfo.kind) { + case "args": + return validateArg( + convertArgsToInstance(paramInfo, resolverData.args), + globalValidate, + paramInfo.validate, + ); + case "arg": + return validateArg( + convertArgToInstance(paramInfo, resolverData.args), + globalValidate, + paramInfo.validate, + ); + case "context": + if (paramInfo.propertyName) { + return resolverData.context[paramInfo.propertyName]; + } + return resolverData.context; + case "root": + const rootValue = paramInfo.propertyName + ? resolverData.root[paramInfo.propertyName] + : resolverData.root; + if (!paramInfo.getType) { + return rootValue; + } + return convertToType(paramInfo.getType(), rootValue); + case "info": + return resolverData.info; + case "pubSub": + if (paramInfo.triggerKey) { + return (payload: any) => pubSub.publish(paramInfo.triggerKey!, payload); + } + return pubSub; + case "custom": + return paramInfo.resolver(resolverData); + } + }); + if (paramValues.some(isPromise)) { + return Promise.all(paramValues); + } else { + return paramValues; + } } export function applyAuthChecker( From 9485dc97628a0b60d092255bd0d5859b356a183f Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Tue, 10 Dec 2019 22:31:10 +0900 Subject: [PATCH 05/11] optimize createHandlerResolver --- src/resolvers/create.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/resolvers/create.ts b/src/resolvers/create.ts index 97fa988f6..fc771012a 100644 --- a/src/resolvers/create.ts +++ b/src/resolvers/create.ts @@ -9,6 +9,7 @@ import { getParams, applyMiddlewares, applyAuthChecker } from "./helpers"; import { convertToType } from "../helpers/types"; import { BuildContext } from "../schema/build-context"; import { ResolverData } from "../interfaces"; +import { isPromise } from "../utils"; export function createHandlerResolver( resolverMetadata: BaseResolverMetadata, @@ -24,17 +25,23 @@ export function createHandlerResolver( const middlewares = globalMiddlewares.concat(resolverMetadata.middlewares!); applyAuthChecker(middlewares, authMode, authChecker, resolverMetadata.roles); - return async (root, args, context, info) => { + return (root, args, context, info) => { const resolverData: ResolverData = { root, args, context, info }; const targetInstance = container.getInstance(resolverMetadata.target, resolverData); - return applyMiddlewares(container, resolverData, middlewares, async () => { - const params: any[] = await getParams( + return applyMiddlewares(container, resolverData, middlewares, () => { + const params: Promise | any[] = getParams( resolverMetadata.params!, resolverData, globalValidate, pubSub, ); - return targetInstance[resolverMetadata.methodName].apply(targetInstance, params); + if (isPromise(params)) { + return params.then(resolvedParams => + targetInstance[resolverMetadata.methodName].apply(targetInstance, resolvedParams), + ); + } else { + return targetInstance[resolverMetadata.methodName].apply(targetInstance, params); + } }); }; } From b3a46fa83f4a554f1fbe000a6ba5f265a0b4a6bf Mon Sep 17 00:00:00 2001 From: Sangmin Yoon Date: Thu, 12 Dec 2019 00:03:47 +0900 Subject: [PATCH 06/11] add benchmark for large array with field resolver --- benchmarks/large/raw-async.ts | 65 +++++++++++++++++++++++++++++++++ benchmarks/large/raw-basic.ts | 53 +++++++++++++++++++++++++++ benchmarks/large/raw-sync.ts | 65 +++++++++++++++++++++++++++++++++ benchmarks/large/run.ts | 36 ++++++++++++++++++ benchmarks/large/tg-async.ts | 69 +++++++++++++++++++++++++++++++++++ benchmarks/large/tg-basic.ts | 49 +++++++++++++++++++++++++ benchmarks/large/tg-sync.ts | 69 +++++++++++++++++++++++++++++++++++ 7 files changed, 406 insertions(+) create mode 100644 benchmarks/large/raw-async.ts create mode 100644 benchmarks/large/raw-basic.ts create mode 100644 benchmarks/large/raw-sync.ts create mode 100644 benchmarks/large/run.ts create mode 100644 benchmarks/large/tg-async.ts create mode 100644 benchmarks/large/tg-basic.ts create mode 100644 benchmarks/large/tg-sync.ts diff --git a/benchmarks/large/raw-async.ts b/benchmarks/large/raw-async.ts new file mode 100644 index 000000000..9adb5ea08 --- /dev/null +++ b/benchmarks/large/raw-async.ts @@ -0,0 +1,65 @@ +import { + GraphQLSchema, + GraphQLObjectType, + GraphQLString, + GraphQLNonNull, + GraphQLBoolean, + GraphQLInt, + GraphQLList, +} from "graphql"; + +import { runBenchmark, ARRAY_ITEMS } from "./run"; + +const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({ + name: "SampleObject", + fields: () => ({ + sampleField: { + type: new GraphQLNonNull(GraphQLString), + resolve: async (source) => { + return source.sampleField; + } + }, + numberField: { + type: new GraphQLNonNull(GraphQLInt), + resolve: async (source) => { + return source.numberField; + } + }, + booleanField: { + type: new GraphQLNonNull(GraphQLBoolean), + resolve: async (source) => { + return source.booleanField; + } + }, + nestedField: { + type: SampleObjectType, + resolve: async (source) => { + return source.nestedField; + } + }, + }), +}); + +const schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: "Query", + fields: { + multipleNestedObjects: { + type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(SampleObjectType))), + resolve: () => + Array.from({ length: ARRAY_ITEMS }, (_, index) => ({ + stringField: "stringField", + booleanField: true, + numberField: index, + nestedField: { + stringField: "stringField", + booleanField: true, + numberField: index, + }, + })), + }, + }, + }), +}); + +runBenchmark(schema).catch(console.error); diff --git a/benchmarks/large/raw-basic.ts b/benchmarks/large/raw-basic.ts new file mode 100644 index 000000000..cbb121e63 --- /dev/null +++ b/benchmarks/large/raw-basic.ts @@ -0,0 +1,53 @@ +import { + GraphQLSchema, + GraphQLObjectType, + GraphQLString, + GraphQLNonNull, + GraphQLBoolean, + GraphQLInt, + GraphQLList, +} from "graphql"; + +import { runBenchmark, ARRAY_ITEMS } from "./run"; + +const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({ + name: "SampleObject", + fields: () => ({ + sampleField: { + type: new GraphQLNonNull(GraphQLString), + }, + numberField: { + type: new GraphQLNonNull(GraphQLInt), + }, + booleanField: { + type: new GraphQLNonNull(GraphQLBoolean), + }, + nestedField: { + type: SampleObjectType, + }, + }), +}); + +const schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: "Query", + fields: { + multipleNestedObjects: { + type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(SampleObjectType))), + resolve: () => + Array.from({ length: ARRAY_ITEMS }, (_, index) => ({ + stringField: "stringField", + booleanField: true, + numberField: index, + nestedField: { + stringField: "stringField", + booleanField: true, + numberField: index, + }, + })), + }, + }, + }), +}); + +runBenchmark(schema).catch(console.error); diff --git a/benchmarks/large/raw-sync.ts b/benchmarks/large/raw-sync.ts new file mode 100644 index 000000000..79f376c76 --- /dev/null +++ b/benchmarks/large/raw-sync.ts @@ -0,0 +1,65 @@ +import { + GraphQLSchema, + GraphQLObjectType, + GraphQLString, + GraphQLNonNull, + GraphQLBoolean, + GraphQLInt, + GraphQLList, +} from "graphql"; + +import { runBenchmark, ARRAY_ITEMS } from "./run"; + +const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({ + name: "SampleObject", + fields: () => ({ + sampleField: { + type: new GraphQLNonNull(GraphQLString), + resolve: (source) => { + return source.sampleField; + } + }, + numberField: { + type: new GraphQLNonNull(GraphQLInt), + resolve: (source) => { + return source.numberField; + } + }, + booleanField: { + type: new GraphQLNonNull(GraphQLBoolean), + resolve: (source) => { + return source.booleanField; + } + }, + nestedField: { + type: SampleObjectType, + resolve: (source) => { + return source.nestedField; + } + }, + }), +}); + +const schema = new GraphQLSchema({ + query: new GraphQLObjectType({ + name: "Query", + fields: { + multipleNestedObjects: { + type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(SampleObjectType))), + resolve: () => + Array.from({ length: ARRAY_ITEMS }, (_, index) => ({ + stringField: "stringField", + booleanField: true, + numberField: index, + nestedField: { + stringField: "stringField", + booleanField: true, + numberField: index, + }, + })), + }, + }, + }), +}); + +runBenchmark(schema).catch(console.error); diff --git a/benchmarks/large/run.ts b/benchmarks/large/run.ts new file mode 100644 index 000000000..4a907b099 --- /dev/null +++ b/benchmarks/large/run.ts @@ -0,0 +1,36 @@ +import { GraphQLSchema, execute } from "graphql"; +import { gql } from "apollo-server"; + +const BENCHMARK_ITERATIONS = 100; +export const ARRAY_ITEMS = 5000; + +export async function runBenchmark(schema: GraphQLSchema) { + const multipleNestedObjectsQuery = gql` + query { + multipleNestedObjects { + stringField + booleanField + numberField + nestedField { + stringField + booleanField + numberField + } + } + } + `; + console.time("multipleNestedObjects"); + for (let i = 0; i < BENCHMARK_ITERATIONS; i++) { + const result = await execute({ schema, document: multipleNestedObjectsQuery }); + console.assert(result.data !== undefined, "result data is undefined"); + console.assert( + result.data!.multipleNestedObjects.length === ARRAY_ITEMS, + "result data is not a proper array", + ); + console.assert( + result.data!.multipleNestedObjects[0].nestedField.booleanField === true, + "data nestedField are incorrect", + ); + } + console.timeEnd("multipleNestedObjects"); +} diff --git a/benchmarks/large/tg-async.ts b/benchmarks/large/tg-async.ts new file mode 100644 index 000000000..a7ef96098 --- /dev/null +++ b/benchmarks/large/tg-async.ts @@ -0,0 +1,69 @@ +import "reflect-metadata"; +import { buildSchema, Field, ObjectType, Resolver, Query, Int, FieldResolver, Root } from "../../build/package"; + +import { runBenchmark, ARRAY_ITEMS } from "./run"; + +@ObjectType() +class SampleObject { + @Field() + stringField!: string; + + @Field(type => Int) + numberField!: number; + + @Field() + booleanField!: boolean; + + @Field({ nullable: true }) + nestedField?: SampleObject; +} + +@Resolver(SampleObject) +class SampleResolver { + @Query(returns => [SampleObject]) + multipleNestedObjects(): SampleObject[] { + return Array.from( + { length: ARRAY_ITEMS }, + (_, index): SampleObject => ({ + stringField: "stringField", + booleanField: true, + numberField: index, + nestedField: { + stringField: "stringField", + booleanField: true, + numberField: index, + }, + }), + ); + } + + @FieldResolver() + async stringField(@Root() source: SampleObject) { + return source.stringField; + } + + @FieldResolver() + async numberField(@Root() source: SampleObject) { + return source.numberField; + } + + @FieldResolver() + async booleanField(@Root() source: SampleObject) { + return source.booleanField; + } + + @FieldResolver() + async nestedField(@Root() source: SampleObject) { + return source.nestedField; + } +} + +async function main() { + const schema = await buildSchema({ + resolvers: [SampleResolver], + }); + + await runBenchmark(schema); +} + +main().catch(console.error); diff --git a/benchmarks/large/tg-basic.ts b/benchmarks/large/tg-basic.ts new file mode 100644 index 000000000..ac3b1b999 --- /dev/null +++ b/benchmarks/large/tg-basic.ts @@ -0,0 +1,49 @@ +import "reflect-metadata"; +import { buildSchema, Field, ObjectType, Resolver, Query, Int } from "../../build/package"; + +import { runBenchmark, ARRAY_ITEMS } from "./run"; + +@ObjectType() +class SampleObject { + @Field() + stringField!: string; + + @Field(type => Int) + numberField!: number; + + @Field() + booleanField!: boolean; + + @Field({ nullable: true }) + nestedField?: SampleObject; +} + +@Resolver() +class SampleResolver { + @Query(returns => [SampleObject]) + multipleNestedObjects(): SampleObject[] { + return Array.from( + { length: ARRAY_ITEMS }, + (_, index): SampleObject => ({ + stringField: "stringField", + booleanField: true, + numberField: index, + nestedField: { + stringField: "stringField", + booleanField: true, + numberField: index, + }, + }), + ); + } +} + +async function main() { + const schema = await buildSchema({ + resolvers: [SampleResolver], + }); + + await runBenchmark(schema); +} + +main().catch(console.error); diff --git a/benchmarks/large/tg-sync.ts b/benchmarks/large/tg-sync.ts new file mode 100644 index 000000000..007e5216b --- /dev/null +++ b/benchmarks/large/tg-sync.ts @@ -0,0 +1,69 @@ +import "reflect-metadata"; +import { buildSchema, Field, ObjectType, Resolver, Query, Int, FieldResolver, Root } from "../../build/package"; + +import { runBenchmark, ARRAY_ITEMS } from "./run"; + +@ObjectType() +class SampleObject { + @Field() + stringField!: string; + + @Field(type => Int) + numberField!: number; + + @Field() + booleanField!: boolean; + + @Field({ nullable: true }) + nestedField?: SampleObject; +} + +@Resolver(SampleObject) +class SampleResolver { + @Query(returns => [SampleObject]) + multipleNestedObjects(): SampleObject[] { + return Array.from( + { length: ARRAY_ITEMS }, + (_, index): SampleObject => ({ + stringField: "stringField", + booleanField: true, + numberField: index, + nestedField: { + stringField: "stringField", + booleanField: true, + numberField: index, + }, + }), + ); + } + + @FieldResolver() + stringField(@Root() source: SampleObject) { + return source.stringField; + } + + @FieldResolver() + numberField(@Root() source: SampleObject) { + return source.numberField; + } + + @FieldResolver() + booleanField(@Root() source: SampleObject) { + return source.booleanField; + } + + @FieldResolver() + nestedField(@Root() source: SampleObject) { + return source.nestedField; + } +} + +async function main() { + const schema = await buildSchema({ + resolvers: [SampleResolver], + }); + + await runBenchmark(schema); +} + +main().catch(console.error); From f20a3390516ec9b63862c25be37357065fb51534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lytek?= Date: Wed, 11 Dec 2019 19:45:10 +0100 Subject: [PATCH 07/11] Add benchmark results --- benchmarks/large/results.txt | 24 ++++++++++++++++++++++++ benchmarks/large/run.ts | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 benchmarks/large/results.txt diff --git a/benchmarks/large/results.txt b/benchmarks/large/results.txt new file mode 100644 index 000000000..8b3f710b1 --- /dev/null +++ b/benchmarks/large/results.txt @@ -0,0 +1,24 @@ +Core i7 2700K @ 3.5GHz +Windows 10 x64 +10 000 array items | 100 iterations + +----- +Node.js v13.3 + +GraphQL-js async: +- 19.765s + +GraphQL-js sync: +- 10.031s + +TypeGraphQL async field resolvers: +- 30.749s + +TypeGraphQL sync field resolvers: +- 16.441s + +TypeGraphQL implicit fields: +- 13.784s + +TypeGraphQL simple resolvers: +- 11.814s diff --git a/benchmarks/large/run.ts b/benchmarks/large/run.ts index 4a907b099..608631c39 100644 --- a/benchmarks/large/run.ts +++ b/benchmarks/large/run.ts @@ -2,7 +2,7 @@ import { GraphQLSchema, execute } from "graphql"; import { gql } from "apollo-server"; const BENCHMARK_ITERATIONS = 100; -export const ARRAY_ITEMS = 5000; +export const ARRAY_ITEMS = 10000; export async function runBenchmark(schema: GraphQLSchema) { const multipleNestedObjectsQuery = gql` From 2b044b39242d4540ae0125c02eee053ce27ddaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lytek?= Date: Sat, 14 Dec 2019 10:13:33 +0100 Subject: [PATCH 08/11] Rename util to isPromiseLike --- src/resolvers/create.ts | 4 ++-- src/resolvers/helpers.ts | 4 ++-- src/utils/index.ts | 1 - src/utils/isPromise.ts | 3 --- src/utils/isPromiseLike.ts | 5 +++++ 5 files changed, 9 insertions(+), 8 deletions(-) delete mode 100644 src/utils/isPromise.ts create mode 100644 src/utils/isPromiseLike.ts diff --git a/src/resolvers/create.ts b/src/resolvers/create.ts index fc771012a..c74871891 100644 --- a/src/resolvers/create.ts +++ b/src/resolvers/create.ts @@ -9,7 +9,7 @@ import { getParams, applyMiddlewares, applyAuthChecker } from "./helpers"; import { convertToType } from "../helpers/types"; import { BuildContext } from "../schema/build-context"; import { ResolverData } from "../interfaces"; -import { isPromise } from "../utils"; +import isPromiseLike from "../utils/isPromiseLike"; export function createHandlerResolver( resolverMetadata: BaseResolverMetadata, @@ -35,7 +35,7 @@ export function createHandlerResolver( globalValidate, pubSub, ); - if (isPromise(params)) { + if (isPromiseLike(params)) { return params.then(resolvedParams => targetInstance[resolverMetadata.methodName].apply(targetInstance, resolvedParams), ); diff --git a/src/resolvers/helpers.ts b/src/resolvers/helpers.ts index 7bdf5e767..b3893e0ba 100644 --- a/src/resolvers/helpers.ts +++ b/src/resolvers/helpers.ts @@ -9,7 +9,7 @@ import { Middleware, MiddlewareFn, MiddlewareClass } from "../interfaces/Middlew import { IOCContainer } from "../utils/container"; import { AuthMiddleware } from "../helpers/auth-middleware"; import { convertArgsToInstance, convertArgToInstance } from "./convert-args"; -import { isPromise } from "../utils/isPromise"; +import isPromiseLike from "../utils/isPromiseLike"; export function getParams( params: ParamMetadata[], @@ -57,7 +57,7 @@ export function getParams( return paramInfo.resolver(resolverData); } }); - if (paramValues.some(isPromise)) { + if (paramValues.some(isPromiseLike)) { return Promise.all(paramValues); } else { return paramValues; diff --git a/src/utils/index.ts b/src/utils/index.ts index 5b89e41fc..ae0474d9b 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,4 +7,3 @@ export { defaultPrintSchemaOptions, } from "./emitSchemaDefinitionFile"; export { ContainerType, ContainerGetter } from "./container"; -export { isPromise } from "./isPromise"; diff --git a/src/utils/isPromise.ts b/src/utils/isPromise.ts deleted file mode 100644 index a97644669..000000000 --- a/src/utils/isPromise.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isPromise(value: PromiseLike | T): value is PromiseLike { - return Boolean(value && typeof (value as any).then === "function"); -} diff --git a/src/utils/isPromiseLike.ts b/src/utils/isPromiseLike.ts new file mode 100644 index 000000000..1a9743f3b --- /dev/null +++ b/src/utils/isPromiseLike.ts @@ -0,0 +1,5 @@ +export default function isPromiseLike( + value: PromiseLike | TValue, +): value is PromiseLike { + return value != null && typeof (value as PromiseLike).then === "function"; +} From 1a9dd5e8a7d402df520e83f5da167687eb607659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lytek?= Date: Sat, 28 Dec 2019 13:55:36 +0100 Subject: [PATCH 09/11] Merge "large" benchmarks with "array" and update results --- .../raw-sync.ts => array/graphql-js/async.ts} | 22 +++---- .../{graphql-js.ts => graphql-js/standard.ts} | 4 +- benchmarks/array/results.txt | 31 +++++++-- benchmarks/array/run.ts | 4 +- .../type-graphql/async-field-resolvers.ts} | 13 +++- .../simple-resolvers.ts} | 20 +++++- .../type-graphql/standard.ts} | 4 +- .../type-graphql/sync-field-resolvers.ts} | 13 +++- benchmarks/array/type-graphql/sync-getters.ts | 62 ++++++++++++++++++ .../type-graphql/with-global-middleware.ts | 65 +++++++++++++++++++ benchmarks/large/raw-async.ts | 65 ------------------- benchmarks/large/raw-basic.ts | 53 --------------- benchmarks/large/results.txt | 24 ------- benchmarks/large/run.ts | 36 ---------- 14 files changed, 209 insertions(+), 207 deletions(-) rename benchmarks/{large/raw-sync.ts => array/graphql-js/async.ts} (82%) rename benchmarks/array/{graphql-js.ts => graphql-js/standard.ts} (94%) rename benchmarks/{large/tg-async.ts => array/type-graphql/async-field-resolvers.ts} (87%) rename benchmarks/array/{type-graphql.ts => type-graphql/simple-resolvers.ts} (68%) rename benchmarks/{large/tg-basic.ts => array/type-graphql/standard.ts} (91%) rename benchmarks/{large/tg-sync.ts => array/type-graphql/sync-field-resolvers.ts} (87%) create mode 100644 benchmarks/array/type-graphql/sync-getters.ts create mode 100644 benchmarks/array/type-graphql/with-global-middleware.ts delete mode 100644 benchmarks/large/raw-async.ts delete mode 100644 benchmarks/large/raw-basic.ts delete mode 100644 benchmarks/large/results.txt delete mode 100644 benchmarks/large/run.ts diff --git a/benchmarks/large/raw-sync.ts b/benchmarks/array/graphql-js/async.ts similarity index 82% rename from benchmarks/large/raw-sync.ts rename to benchmarks/array/graphql-js/async.ts index 79f376c76..1898f6584 100644 --- a/benchmarks/large/raw-sync.ts +++ b/benchmarks/array/graphql-js/async.ts @@ -8,34 +8,34 @@ import { GraphQLList, } from "graphql"; -import { runBenchmark, ARRAY_ITEMS } from "./run"; +import { runBenchmark, ARRAY_ITEMS } from "../run"; const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({ name: "SampleObject", fields: () => ({ - sampleField: { + stringField: { type: new GraphQLNonNull(GraphQLString), - resolve: (source) => { - return source.sampleField; - } + resolve: async source => { + return source.stringField; + }, }, numberField: { type: new GraphQLNonNull(GraphQLInt), - resolve: (source) => { + resolve: async source => { return source.numberField; - } + }, }, booleanField: { type: new GraphQLNonNull(GraphQLBoolean), - resolve: (source) => { + resolve: async source => { return source.booleanField; - } + }, }, nestedField: { type: SampleObjectType, - resolve: (source) => { + resolve: async source => { return source.nestedField; - } + }, }, }), }); diff --git a/benchmarks/array/graphql-js.ts b/benchmarks/array/graphql-js/standard.ts similarity index 94% rename from benchmarks/array/graphql-js.ts rename to benchmarks/array/graphql-js/standard.ts index cbb121e63..c6e2b1259 100644 --- a/benchmarks/array/graphql-js.ts +++ b/benchmarks/array/graphql-js/standard.ts @@ -8,12 +8,12 @@ import { GraphQLList, } from "graphql"; -import { runBenchmark, ARRAY_ITEMS } from "./run"; +import { runBenchmark, ARRAY_ITEMS } from "../run"; const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({ name: "SampleObject", fields: () => ({ - sampleField: { + stringField: { type: new GraphQLNonNull(GraphQLString), }, numberField: { diff --git a/benchmarks/array/results.txt b/benchmarks/array/results.txt index 8c1a3f8ac..6453b1a22 100644 --- a/benchmarks/array/results.txt +++ b/benchmarks/array/results.txt @@ -1,15 +1,34 @@ Core i7 2700K @ 3.5GHz Windows 10 x64 -10 000 array items | 100 iterations +25 000 array items | 50 iterations +Node.js v13.5 ----- -Node.js v13.2 +TypeGraphQL standard -- 42.551s +- 15.518s + +using sync field resolvers +- 18.180s + +using async field resolvers +- 39.934s + +using getters +- 31.207s + +standard with global middleware +- 62.664s with `simpleResolvers: true` -- 11.841s +- 14.980s + +----- +`graphql-js` + +standard +- 13.276s -graphql-js -- 9.963s +async field resolvers +- 25.630s diff --git a/benchmarks/array/run.ts b/benchmarks/array/run.ts index 608631c39..d9e70a115 100644 --- a/benchmarks/array/run.ts +++ b/benchmarks/array/run.ts @@ -1,8 +1,8 @@ import { GraphQLSchema, execute } from "graphql"; import { gql } from "apollo-server"; -const BENCHMARK_ITERATIONS = 100; -export const ARRAY_ITEMS = 10000; +const BENCHMARK_ITERATIONS = 50; +export const ARRAY_ITEMS = 25000; export async function runBenchmark(schema: GraphQLSchema) { const multipleNestedObjectsQuery = gql` diff --git a/benchmarks/large/tg-async.ts b/benchmarks/array/type-graphql/async-field-resolvers.ts similarity index 87% rename from benchmarks/large/tg-async.ts rename to benchmarks/array/type-graphql/async-field-resolvers.ts index a7ef96098..e69011b7a 100644 --- a/benchmarks/large/tg-async.ts +++ b/benchmarks/array/type-graphql/async-field-resolvers.ts @@ -1,7 +1,16 @@ import "reflect-metadata"; -import { buildSchema, Field, ObjectType, Resolver, Query, Int, FieldResolver, Root } from "../../build/package"; +import { + buildSchema, + Field, + ObjectType, + Resolver, + Query, + Int, + FieldResolver, + Root, +} from "../../../build/package/dist"; -import { runBenchmark, ARRAY_ITEMS } from "./run"; +import { runBenchmark, ARRAY_ITEMS } from "../run"; @ObjectType() class SampleObject { diff --git a/benchmarks/array/type-graphql.ts b/benchmarks/array/type-graphql/simple-resolvers.ts similarity index 68% rename from benchmarks/array/type-graphql.ts rename to benchmarks/array/type-graphql/simple-resolvers.ts index ea5ab82fa..f2a97750d 100644 --- a/benchmarks/array/type-graphql.ts +++ b/benchmarks/array/type-graphql/simple-resolvers.ts @@ -1,7 +1,15 @@ import "reflect-metadata"; -import { buildSchema, Field, ObjectType, Resolver, Query, Int } from "../../build/package"; +import { + buildSchema, + Field, + ObjectType, + Resolver, + Query, + Int, + MiddlewareFn, +} from "../../../build/package/dist"; -import { runBenchmark, ARRAY_ITEMS } from "./run"; +import { runBenchmark, ARRAY_ITEMS } from "../run"; @ObjectType({ simpleResolvers: true }) class SampleObject { @@ -38,9 +46,17 @@ class SampleResolver { } } +const log = (...args: any[]) => void 0; // noop + +const loggingMiddleware: MiddlewareFn = ({ info }, next) => { + log(`${info.parentType.name}.${info.fieldName} accessed`); + return next(); +}; + async function main() { const schema = await buildSchema({ resolvers: [SampleResolver], + globalMiddlewares: [loggingMiddleware], }); await runBenchmark(schema); diff --git a/benchmarks/large/tg-basic.ts b/benchmarks/array/type-graphql/standard.ts similarity index 91% rename from benchmarks/large/tg-basic.ts rename to benchmarks/array/type-graphql/standard.ts index ac3b1b999..36d5fbb9c 100644 --- a/benchmarks/large/tg-basic.ts +++ b/benchmarks/array/type-graphql/standard.ts @@ -1,7 +1,7 @@ import "reflect-metadata"; -import { buildSchema, Field, ObjectType, Resolver, Query, Int } from "../../build/package"; +import { buildSchema, Field, ObjectType, Resolver, Query, Int } from "../../../build/package/dist"; -import { runBenchmark, ARRAY_ITEMS } from "./run"; +import { runBenchmark, ARRAY_ITEMS } from "../run"; @ObjectType() class SampleObject { diff --git a/benchmarks/large/tg-sync.ts b/benchmarks/array/type-graphql/sync-field-resolvers.ts similarity index 87% rename from benchmarks/large/tg-sync.ts rename to benchmarks/array/type-graphql/sync-field-resolvers.ts index 007e5216b..1c9cc6e16 100644 --- a/benchmarks/large/tg-sync.ts +++ b/benchmarks/array/type-graphql/sync-field-resolvers.ts @@ -1,7 +1,16 @@ import "reflect-metadata"; -import { buildSchema, Field, ObjectType, Resolver, Query, Int, FieldResolver, Root } from "../../build/package"; +import { + buildSchema, + Field, + ObjectType, + Resolver, + Query, + Int, + FieldResolver, + Root, +} from "../../../build/package/dist"; -import { runBenchmark, ARRAY_ITEMS } from "./run"; +import { runBenchmark, ARRAY_ITEMS } from "../run"; @ObjectType() class SampleObject { diff --git a/benchmarks/array/type-graphql/sync-getters.ts b/benchmarks/array/type-graphql/sync-getters.ts new file mode 100644 index 000000000..3f9d6e19f --- /dev/null +++ b/benchmarks/array/type-graphql/sync-getters.ts @@ -0,0 +1,62 @@ +import "reflect-metadata"; +import { buildSchema, Field, ObjectType, Resolver, Query, Int } from "../../../build/package/dist"; + +import { runBenchmark, ARRAY_ITEMS } from "../run"; + +@ObjectType() +class SampleObject { + stringField!: string; + @Field({ name: "stringField" }) + get getStringField(): string { + return this.stringField; + } + + numberField!: number; + @Field(type => Int, { name: "numberField" }) + get getNumberField(): number { + return this.numberField; + } + + booleanField!: boolean; + @Field({ name: "booleanField" }) + get getBooleanField(): boolean { + return this.booleanField; + } + + nestedField?: SampleObject; + @Field(type => SampleObject, { name: "nestedField", nullable: true }) + get getNestedField(): SampleObject | undefined { + return this.nestedField; + } +} + +@Resolver(SampleObject) +class SampleResolver { + @Query(returns => [SampleObject]) + multipleNestedObjects(): SampleObject[] { + return Array.from( + { length: ARRAY_ITEMS }, + (_, index): SampleObject => + ({ + stringField: "stringField", + booleanField: true, + numberField: index, + nestedField: { + stringField: "stringField", + booleanField: true, + numberField: index, + } as SampleObject, + } as SampleObject), + ); + } +} + +async function main() { + const schema = await buildSchema({ + resolvers: [SampleResolver], + }); + + await runBenchmark(schema); +} + +main().catch(console.error); diff --git a/benchmarks/array/type-graphql/with-global-middleware.ts b/benchmarks/array/type-graphql/with-global-middleware.ts new file mode 100644 index 000000000..2b63a22dc --- /dev/null +++ b/benchmarks/array/type-graphql/with-global-middleware.ts @@ -0,0 +1,65 @@ +import "reflect-metadata"; +import { + buildSchema, + Field, + ObjectType, + Resolver, + Query, + Int, + MiddlewareFn, +} from "../../../build/package/dist"; + +import { runBenchmark, ARRAY_ITEMS } from "../run"; + +@ObjectType() +class SampleObject { + @Field() + stringField!: string; + + @Field(type => Int) + numberField!: number; + + @Field() + booleanField!: boolean; + + @Field({ nullable: true }) + nestedField?: SampleObject; +} + +@Resolver() +class SampleResolver { + @Query(returns => [SampleObject]) + multipleNestedObjects(): SampleObject[] { + return Array.from( + { length: ARRAY_ITEMS }, + (_, index): SampleObject => ({ + stringField: "stringField", + booleanField: true, + numberField: index, + nestedField: { + stringField: "stringField", + booleanField: true, + numberField: index, + }, + }), + ); + } +} + +const log = (...args: any[]) => void 0; // noop + +const loggingMiddleware: MiddlewareFn = ({ info }, next) => { + log(`${info.parentType.name}.${info.fieldName} accessed`); + return next(); +}; + +async function main() { + const schema = await buildSchema({ + resolvers: [SampleResolver], + globalMiddlewares: [loggingMiddleware], + }); + + await runBenchmark(schema); +} + +main().catch(console.error); diff --git a/benchmarks/large/raw-async.ts b/benchmarks/large/raw-async.ts deleted file mode 100644 index 9adb5ea08..000000000 --- a/benchmarks/large/raw-async.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - GraphQLSchema, - GraphQLObjectType, - GraphQLString, - GraphQLNonNull, - GraphQLBoolean, - GraphQLInt, - GraphQLList, -} from "graphql"; - -import { runBenchmark, ARRAY_ITEMS } from "./run"; - -const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({ - name: "SampleObject", - fields: () => ({ - sampleField: { - type: new GraphQLNonNull(GraphQLString), - resolve: async (source) => { - return source.sampleField; - } - }, - numberField: { - type: new GraphQLNonNull(GraphQLInt), - resolve: async (source) => { - return source.numberField; - } - }, - booleanField: { - type: new GraphQLNonNull(GraphQLBoolean), - resolve: async (source) => { - return source.booleanField; - } - }, - nestedField: { - type: SampleObjectType, - resolve: async (source) => { - return source.nestedField; - } - }, - }), -}); - -const schema = new GraphQLSchema({ - query: new GraphQLObjectType({ - name: "Query", - fields: { - multipleNestedObjects: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(SampleObjectType))), - resolve: () => - Array.from({ length: ARRAY_ITEMS }, (_, index) => ({ - stringField: "stringField", - booleanField: true, - numberField: index, - nestedField: { - stringField: "stringField", - booleanField: true, - numberField: index, - }, - })), - }, - }, - }), -}); - -runBenchmark(schema).catch(console.error); diff --git a/benchmarks/large/raw-basic.ts b/benchmarks/large/raw-basic.ts deleted file mode 100644 index cbb121e63..000000000 --- a/benchmarks/large/raw-basic.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { - GraphQLSchema, - GraphQLObjectType, - GraphQLString, - GraphQLNonNull, - GraphQLBoolean, - GraphQLInt, - GraphQLList, -} from "graphql"; - -import { runBenchmark, ARRAY_ITEMS } from "./run"; - -const SampleObjectType: GraphQLObjectType = new GraphQLObjectType({ - name: "SampleObject", - fields: () => ({ - sampleField: { - type: new GraphQLNonNull(GraphQLString), - }, - numberField: { - type: new GraphQLNonNull(GraphQLInt), - }, - booleanField: { - type: new GraphQLNonNull(GraphQLBoolean), - }, - nestedField: { - type: SampleObjectType, - }, - }), -}); - -const schema = new GraphQLSchema({ - query: new GraphQLObjectType({ - name: "Query", - fields: { - multipleNestedObjects: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(SampleObjectType))), - resolve: () => - Array.from({ length: ARRAY_ITEMS }, (_, index) => ({ - stringField: "stringField", - booleanField: true, - numberField: index, - nestedField: { - stringField: "stringField", - booleanField: true, - numberField: index, - }, - })), - }, - }, - }), -}); - -runBenchmark(schema).catch(console.error); diff --git a/benchmarks/large/results.txt b/benchmarks/large/results.txt deleted file mode 100644 index 8b3f710b1..000000000 --- a/benchmarks/large/results.txt +++ /dev/null @@ -1,24 +0,0 @@ -Core i7 2700K @ 3.5GHz -Windows 10 x64 -10 000 array items | 100 iterations - ------ -Node.js v13.3 - -GraphQL-js async: -- 19.765s - -GraphQL-js sync: -- 10.031s - -TypeGraphQL async field resolvers: -- 30.749s - -TypeGraphQL sync field resolvers: -- 16.441s - -TypeGraphQL implicit fields: -- 13.784s - -TypeGraphQL simple resolvers: -- 11.814s diff --git a/benchmarks/large/run.ts b/benchmarks/large/run.ts deleted file mode 100644 index 608631c39..000000000 --- a/benchmarks/large/run.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { GraphQLSchema, execute } from "graphql"; -import { gql } from "apollo-server"; - -const BENCHMARK_ITERATIONS = 100; -export const ARRAY_ITEMS = 10000; - -export async function runBenchmark(schema: GraphQLSchema) { - const multipleNestedObjectsQuery = gql` - query { - multipleNestedObjects { - stringField - booleanField - numberField - nestedField { - stringField - booleanField - numberField - } - } - } - `; - console.time("multipleNestedObjects"); - for (let i = 0; i < BENCHMARK_ITERATIONS; i++) { - const result = await execute({ schema, document: multipleNestedObjectsQuery }); - console.assert(result.data !== undefined, "result data is undefined"); - console.assert( - result.data!.multipleNestedObjects.length === ARRAY_ITEMS, - "result data is not a proper array", - ); - console.assert( - result.data!.multipleNestedObjects[0].nestedField.booleanField === true, - "data nestedField are incorrect", - ); - } - console.timeEnd("multipleNestedObjects"); -} From 967e5374171bc97a6cd2dd79381ff2be391a6ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lytek?= Date: Thu, 2 Jan 2020 22:08:23 +0100 Subject: [PATCH 10/11] Update performance docs --- docs/performance.md | 55 ++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/docs/performance.md b/docs/performance.md index 9cba9a1fe..ece807b53 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -10,11 +10,33 @@ While this enable easy and convenient development, it's sometimes a tradeoff in To measure the overhead of the abstraction, a few demo examples were made to compare the usage of TypeGraphQL against the implementations using "bare metal" - raw `graphql-js` library. The benchmarks are located in a [folder on the GitHub repo](https://github.com/MichalLytek/type-graphql/tree/master/benchmarks). -The most demanding cases like returning an array of 10 000 nested objects shows that it's about 4 times slower. In real apps (e.g. with complex database queries) it's usually a much lower factor but still not negligible. That's why TypeGraphQL has some built-in performance optimization options. +The most demanding cases like returning an array of 25 000 nested objects showed that in some cases it might be about 5 times slower. + +| | 25 000 array items | Deeply nested object | +| -------------------- | :----------------: | :------------------: | +| Standard TypeGraphQL | 1253.28 ms | 45.57 μs | +| `graphql-js` | 265.52 ms | 24.22 μs | + +In real apps (e.g. with complex database queries) it's usually a much lower factor but still not negligible. That's why TypeGraphQL has some built-in performance optimization options. ## Optimizations -When we have a query that returns a huge amount of JSON-like data and we don't need any field-level access control, we can turn off the whole authorization and middlewares stack for selected field resolvers using a `{ simple: true }` decorator option, e.g.: +Promises in JS have a quite big performance overhead. In the same example of returning an array with 25 000 items, if we change the Object Type field resolvers to an asynchronous one that return a promise, the execution slows down by a half even in "raw" `graphql-js`. + +| `graphql-js` | 25 000 array items | +| --------------- | :----------------: | +| sync resolvers | 265.52 ms | +| async resolvers | 512.61 ms | + +TypeGraphQL tries to avoid the async execution path when it's possible, e.g. if the query/mutation/field resolver doesn't use the auth feature, doesn't use args (or has args validation disabled) and if doesn't return a promise. So if you find a bottleneck in your app, try to investigate your resolvers, disable not used features and maybe remove some unnecessary async/await usage. + +Also, using middlewares implicitly turns on the async execution path (for global middlewares the middlewares stack is created even for every implicit field resolver!), so be careful when using this feature if you care about the performance very much (and maybe then use the "simple resolvers" tweak described below). + +The whole middleware stack will be soon redesigned with a performance in mind and with a new API that will also allow fine-grained scoping of global middlewares. Stay tuned! + +## Further performance tweaks + +When we have a query that returns a huge amount of JSON-like data and we don't need any field-level access control or other custom middlewares, we can turn off the whole authorization and middlewares stack for selected field resolver using a `{ simple: true }` decorator option, e.g.: ```typescript @ObjectType() @@ -22,20 +44,12 @@ class SampleObject { @Field() sampleField: string; - @Field(type => [SuperComplexObject], { simple: true }) - superComplexData: SuperComplexObject[]; + @Field({ simple: true }) + publicFrequentlyQueriedField: SomeType; } ``` -This simple trick can speed up the execution up to 72%! The benchmarks show that using simple resolvers allows for as fast execution as with bare `graphql-js` - the measured overhead is only about ~20%, which is a much more reasonable value than 400%. Below you can see [the benchmarks results](https://github.com/MichalLytek/type-graphql/tree/master/benchmarks): - -| | 10 000 array items | Deeply nested object | -| ---------------------------------- | :----------------: | :------------------: | -| Standard TypeGraphQL | 42.551s | 4.557s | -| `graphql-js` | 9.963s | 2.422s | -| TypeGraphQL with "simpleResolvers" | 11.841s | 3.086s | - -Moreover, you can also apply this behavior for all the fields of the object type by using a `{ simpleResolvers: true }` decorator option, e.g.: +Moreover, we can also apply this behavior for all the fields of the object type by using a `{ simpleResolvers: true }` decorator option, e.g.: ```typescript @ObjectType({ simpleResolvers: true }) @@ -51,8 +65,17 @@ class Post { } ``` -### Caution +This simple trick can speed up the execution up to 76%! The benchmarks show that using simple resolvers allows for as fast execution as with bare `graphql-js` - the measured overhead is only about ~13%, which is a much more reasonable value than 500%. Below you can see [the benchmarks results](https://github.com/MichalLytek/type-graphql/tree/master/benchmarks): + +| | 25 000 array items | +| ----------------------------------------------------------------------------- | :----------------: | +| `graphql-js` | 265.52 ms | +| Standard TypeGraphQL | 310.36 ms | +| TypeGraphQL with a global middleware | 1253.28 ms | +| **TypeGraphQL with "simpleResolvers" applied
(and a global middleware)** | **299.61 ms** | + +> This optimization **is not turned on by default** mostly because of the global middlewares and authorization feature. -This optimization **is not turned on by default** mostly because of the global middlewares and authorization feature. By using "simple resolvers" we are turning them off, so we have to be aware of the consequences - `@Authorized` guard on fields won't work for that fields so they will be publicly available, as well as global middlewares won't be executed for that fields, so we might lost, for example, performance metrics or access logs. +By using "simple resolvers" we are turning them off, so we have to be aware of the consequences - `@Authorized` guard on fields won't work for that fields so they will be publicly available, as well as global middlewares won't be executed for that fields, so we might lost, for example, performance metrics or access logs. -That's why we should **be really careful with using this feature**. The rule of thumb is to use "simple resolvers" only when it's really needed, like returning huge array of nested objects. +That's why we should **be really careful with using this tweak**. The rule of thumb is to use "simple resolvers" only when it's really needed, like returning huge array of nested objects. From 1cbfccf1cc3c4926c84038e244ef4a1e9c34ab36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Lytek?= Date: Fri, 3 Jan 2020 18:38:32 +0100 Subject: [PATCH 11/11] Add entry to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0992a8e31..72ae37fa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - update `TypeResolver` interface to match with `GraphQLTypeResolver` from `graphql-js` - add basic support for directives with `@Directive()` decorator (#369) - add possibility to tune up the performance and disable auth & middlewares stack for simple field resolvers (#479) +- optimize resolvers execution paths to speed up a lot basic scenarios (#488) ### Fixes - refactor union types function syntax handling to prevent possible errors with circular refs - fix transforming and validating nested inputs and arrays (#462)