Browse Source

perf(core): Implement caching of GraphqlValueTransformer type trees

Relates to #226. The `getOutputTypeTree()` and `getOutputTypeTree()` methods have to walk potentially deeply-nested objects to construct the type trees. This change uses a WeakMap to cache the results against a given input. Benchmarks show modest speed improvements.
Michael Bromley 6 years ago
parent
commit
ffe47b1b59

+ 12 - 0
packages/core/src/api/common/graphql-value-transformer.ts

@@ -37,6 +37,8 @@ export type TypeTreeNode = {
  * This class is used to transform the values of input variables or an output object.
  * This class is used to transform the values of input variables or an output object.
  */
  */
 export class GraphqlValueTransformer {
 export class GraphqlValueTransformer {
+    private outputCache = new WeakMap<DocumentNode, TypeTree>();
+    private inputCache = new WeakMap<OperationDefinitionNode, TypeTree>();
     constructor(private schema: GraphQLSchema) {}
     constructor(private schema: GraphQLSchema) {}
 
 
     /**
     /**
@@ -58,6 +60,10 @@ export class GraphqlValueTransformer {
      * Constructs a tree of TypeTreeNodes for the output of a GraphQL operation.
      * Constructs a tree of TypeTreeNodes for the output of a GraphQL operation.
      */
      */
     getOutputTypeTree(document: DocumentNode): TypeTree {
     getOutputTypeTree(document: DocumentNode): TypeTree {
+        const cached = this.outputCache.get(document);
+        if (cached) {
+            return cached;
+        }
         const typeInfo = new TypeInfo(this.schema);
         const typeInfo = new TypeInfo(this.schema);
         const typeTree: TypeTree = {
         const typeTree: TypeTree = {
             operation: {} as any,
             operation: {} as any,
@@ -113,6 +119,7 @@ export class GraphqlValueTransformer {
         for (const operation of document.definitions) {
         for (const operation of document.definitions) {
             visit(operation, visitWithTypeInfo(typeInfo, visitor));
             visit(operation, visitWithTypeInfo(typeInfo, visitor));
         }
         }
+        this.outputCache.set(document, typeTree);
         return typeTree;
         return typeTree;
     }
     }
 
 
@@ -120,6 +127,10 @@ export class GraphqlValueTransformer {
      * Constructs a tree of TypeTreeNodes for the input variables of a GraphQL operation.
      * Constructs a tree of TypeTreeNodes for the input variables of a GraphQL operation.
      */
      */
     getInputTypeTree(definition: OperationDefinitionNode): TypeTree {
     getInputTypeTree(definition: OperationDefinitionNode): TypeTree {
+        const cached = this.inputCache.get(definition);
+        if (cached) {
+            return cached;
+        }
         const typeInfo = new TypeInfo(this.schema);
         const typeInfo = new TypeInfo(this.schema);
         const typeTree: TypeTree = {
         const typeTree: TypeTree = {
             operation: {} as any,
             operation: {} as any,
@@ -167,6 +178,7 @@ export class GraphqlValueTransformer {
             },
             },
         };
         };
         visit(definition, visitWithTypeInfo(typeInfo, visitor));
         visit(definition, visitWithTypeInfo(typeInfo, visitor));
+        this.inputCache.set(definition, typeTree);
         return typeTree;
         return typeTree;
     }
     }
 
 

+ 14 - 3
packages/core/src/api/middleware/id-interceptor.ts

@@ -1,7 +1,7 @@
 import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
 import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
 import { GqlExecutionContext } from '@nestjs/graphql';
 import { GqlExecutionContext } from '@nestjs/graphql';
 import { VariableValues } from 'apollo-server-core';
 import { VariableValues } from 'apollo-server-core';
-import { GraphQLNamedType, OperationDefinitionNode } from 'graphql';
+import { GraphQLNamedType, GraphQLSchema, OperationDefinitionNode } from 'graphql';
 import { Observable } from 'rxjs';
 import { Observable } from 'rxjs';
 
 
 import { GraphqlValueTransformer } from '../common/graphql-value-transformer';
 import { GraphqlValueTransformer } from '../common/graphql-value-transformer';
@@ -25,6 +25,7 @@ type TypeTreeNode = {
  */
  */
 @Injectable()
 @Injectable()
 export class IdInterceptor implements NestInterceptor {
 export class IdInterceptor implements NestInterceptor {
+    private graphQlValueTransformers = new WeakMap<GraphQLSchema, GraphqlValueTransformer>();
     constructor(private idCodecService: IdCodecService) {}
     constructor(private idCodecService: IdCodecService) {}
 
 
     intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
     intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
@@ -32,12 +33,22 @@ export class IdInterceptor implements NestInterceptor {
         if (isGraphQL) {
         if (isGraphQL) {
             const args = GqlExecutionContext.create(context).getArgs();
             const args = GqlExecutionContext.create(context).getArgs();
             const info = GqlExecutionContext.create(context).getInfo();
             const info = GqlExecutionContext.create(context).getInfo();
-            const graphqlValueTransformer = new GraphqlValueTransformer(info.schema);
-            this.decodeIdArguments(graphqlValueTransformer, info.operation, args);
+            const transformer = this.getTransformerForSchema(info.schema);
+            this.decodeIdArguments(transformer, info.operation, args);
         }
         }
         return next.handle();
         return next.handle();
     }
     }
 
 
+    private getTransformerForSchema(schema: GraphQLSchema): GraphqlValueTransformer {
+        const existing = this.graphQlValueTransformers.get(schema);
+        if (existing) {
+            return existing;
+        }
+        const transformer = new GraphqlValueTransformer(schema);
+        this.graphQlValueTransformers.set(schema, transformer);
+        return transformer;
+    }
+
     private decodeIdArguments(
     private decodeIdArguments(
         graphqlValueTransformer: GraphqlValueTransformer,
         graphqlValueTransformer: GraphqlValueTransformer,
         definition: OperationDefinitionNode,
         definition: OperationDefinitionNode,