From 877c56d29ebf0ad688d87df70022a8b97db52610 Mon Sep 17 00:00:00 2001 From: Boopathi Nedunchezhiyan Date: Thu, 10 Oct 2024 14:12:23 +0200 Subject: [PATCH 1/3] perf: add a few performance optimizations --- src/ast.ts | 60 +++++++++++++++++-------------------- src/execution.ts | 73 ++++++++++++++++++++++++++++++++------------- src/generate.ts | 12 ++++++++ src/resolve-info.ts | 2 +- src/variables.ts | 2 +- 5 files changed, 94 insertions(+), 55 deletions(-) create mode 100644 src/generate.ts diff --git a/src/ast.ts b/src/ast.ts index a7460b03..9e63cd4e 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,4 +1,4 @@ -import genFn from "generate-function"; +import { genFn } from "./generate"; import { type ArgumentNode, type ASTNode, @@ -41,7 +41,7 @@ export interface JitFieldNode extends FieldNode { * @deprecated Use __internalShouldIncludePath instead * @see __internalShouldIncludePath */ - __internalShouldInclude?: string; + __internalShouldInclude?: string[]; // The shouldInclude logic is specific to the current path // This is because the same fieldNode can be reached from different paths @@ -49,7 +49,7 @@ export interface JitFieldNode extends FieldNode { __internalShouldIncludePath?: { // Key is the stringified ObjectPath, // Value is the shouldInclude logic for that path - [path: string]: string; + [path: string]: string[]; }; } @@ -96,7 +96,7 @@ function collectFieldsImpl( selectionSet: SelectionSetNode, fields: FieldsAndNodes, visitedFragmentNames: { [key: string]: boolean }, - previousShouldInclude = "", + previousShouldInclude: string[] = [], parentResponsePath = "" ): FieldsAndNodes { for (const selection of selectionSet.selections) { @@ -146,16 +146,16 @@ function collectFieldsImpl( fieldNode.__internalShouldIncludePath[currentPath] = joinShouldIncludeCompilations( - fieldNode.__internalShouldIncludePath?.[currentPath] ?? "", + fieldNode.__internalShouldIncludePath?.[currentPath] ?? [], previousShouldInclude, - compiledSkipInclude + [compiledSkipInclude] ); } else { // @deprecated fieldNode.__internalShouldInclude = joinShouldIncludeCompilations( - fieldNode.__internalShouldInclude ?? "", + fieldNode.__internalShouldInclude ?? [], previousShouldInclude, - compiledSkipInclude + [compiledSkipInclude] ); } /** @@ -199,7 +199,7 @@ function collectFieldsImpl( // `should include`s from previous fragments previousShouldInclude, // current fragment's shouldInclude - compiledSkipInclude + [compiledSkipInclude] ), parentResponsePath ); @@ -237,7 +237,7 @@ function collectFieldsImpl( // `should include`s from previous fragments previousShouldInclude, // current fragment's shouldInclude - compiledSkipInclude + [compiledSkipInclude] ), parentResponsePath ); @@ -335,15 +335,15 @@ function augmentFieldNodeTree( joinShouldIncludeCompilations( parentFieldNode.__internalShouldIncludePath?.[ parentResponsePath - ] ?? "", - jitFieldNode.__internalShouldIncludePath?.[currentPath] ?? "" + ] ?? [], + jitFieldNode.__internalShouldIncludePath?.[currentPath] ?? [] ); } else { // @deprecated jitFieldNode.__internalShouldInclude = joinShouldIncludeCompilations( - parentFieldNode.__internalShouldInclude ?? "", - jitFieldNode.__internalShouldInclude ?? "" + parentFieldNode.__internalShouldInclude ?? [], + jitFieldNode.__internalShouldInclude ?? [] ); } } @@ -392,7 +392,7 @@ function augmentFieldNodeTree( * * @param compilations */ -function joinShouldIncludeCompilations(...compilations: string[]) { +function joinShouldIncludeCompilations(...compilations: string[][]): string[] { // remove "true" since we are joining with '&&' as `true && X` = `X` // This prevents an explosion of `&& true` which could break // V8's internal size limit for string. @@ -404,16 +404,16 @@ function joinShouldIncludeCompilations(...compilations: string[]) { // Failing to do this results in [RangeError: invalid array length] // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length - // remove empty strings - let filteredCompilations = compilations.filter((it) => it); + const conditionsSet = new Set(); + for (const conditions of compilations) { + for (const condition of conditions) { + if (condition !== "true") { + conditionsSet.add(condition); + } + } + } - // Split conditions by && and flatten it - filteredCompilations = ([] as string[]).concat( - ...filteredCompilations.map((e) => e.split(" && ").map((it) => it.trim())) - ); - // Deduplicate items - filteredCompilations = Array.from(new Set(filteredCompilations)); - return filteredCompilations.join(" && "); + return Array.from(conditionsSet); } /** @@ -427,8 +427,6 @@ function compileSkipInclude( compilationContext: CompilationContext, node: SelectionNode ): string { - const gen = genFn(); - const { skipValue, includeValue } = compileSkipIncludeDirectiveValues( compilationContext, node @@ -446,16 +444,14 @@ function compileSkipInclude( * condition is true or the @include condition is false. */ if (skipValue != null && includeValue != null) { - gen(`${skipValue} === false && ${includeValue} === true`); + return `${skipValue} === false && ${includeValue} === true`; } else if (skipValue != null) { - gen(`(${skipValue} === false)`); + return `(${skipValue} === false)`; } else if (includeValue != null) { - gen(`(${includeValue} === true)`); + return `(${includeValue} === true)`; } else { - gen(`true`); + return `true`; } - - return gen.toString(); } /** diff --git a/src/execution.ts b/src/execution.ts index 4d65df5b..a0a68829 100644 --- a/src/execution.ts +++ b/src/execution.ts @@ -1,6 +1,6 @@ import { type TypedDocumentNode } from "@graphql-typed-document-node/core"; import fastJson from "fast-json-stringify"; -import genFn from "generate-function"; +import { genFn } from "./generate"; import { type ASTNode, type DocumentNode, @@ -65,8 +65,10 @@ import { failToParseVariables } from "./variables.js"; import { getGraphQLErrorOptions, getOperationRootType } from "./compat.js"; +import memoize from "lodash.memoize"; const inspect = createInspect(); +const joinOriginPaths = memoize(joinOriginPathsImpl); export interface CompilerOptions { customJSONSerializer: boolean; @@ -129,6 +131,7 @@ interface DeferredField { name: string; responsePath: ObjectPath; originPaths: string[]; + originPathsFormatted: string; destinationPaths: string[]; parentType: GraphQLObjectType; fieldName: string; @@ -517,7 +520,7 @@ function compileDeferredField( ): string { const { name, - originPaths, + originPathsFormatted, destinationPaths, fieldNodes, fieldType, @@ -562,7 +565,7 @@ function compileDeferredField( responsePath ); const emptyError = createErrorObject(context, fieldNodes, responsePath, '""'); - const resolverParentPath = originPaths.join("."); + const resolverParentPath = originPathsFormatted; const resolverCall = `${GLOBAL_EXECUTION_CONTEXT}.resolvers.${resolverName}( ${resolverParentPath},${topLevelArgs},${GLOBAL_CONTEXT_NAME}, ${executionInfo})`; const resultParentPath = destinationPaths.join("."); @@ -661,7 +664,7 @@ function compileType( destinationPaths: string[], previousPath: ObjectPath ): string { - const sourcePath = originPaths.join("."); + const sourcePath = joinOriginPaths(originPaths); let body = `${sourcePath} == null ? `; let errorDestination; if (isNonNullType(type)) { @@ -754,7 +757,7 @@ function compileLeafType( context.options.disableLeafSerialization && (type instanceof GraphQLEnumType || isSpecifiedScalarType(type)) ) { - body += `${originPaths.join(".")}`; + body += `${joinOriginPaths(originPaths)}`; } else { const serializerName = getSerializerName(type.name); context.serializers[serializerName] = getSerializer( @@ -775,8 +778,8 @@ function compileLeafType( "message" )});} `); - body += `${GLOBAL_EXECUTION_CONTEXT}.serializers.${serializerName}(${GLOBAL_EXECUTION_CONTEXT}, ${originPaths.join( - "." + body += `${GLOBAL_EXECUTION_CONTEXT}.serializers.${serializerName}(${GLOBAL_EXECUTION_CONTEXT}, ${joinOriginPaths( + originPaths )}, ${serializerErrorHandler}, ${parentIndexes})`; } return body; @@ -815,15 +818,17 @@ function compileObjectType( body( `!${GLOBAL_EXECUTION_CONTEXT}.isTypeOfs["${ type.name - }IsTypeOf"](${originPaths.join( - "." + }IsTypeOf"](${joinOriginPaths( + originPaths )}) ? (${errorDestination}.push(${createErrorObject( context, fieldNodes, responsePath as any, `\`Expected value of type "${ type.name - }" but got: $\{${GLOBAL_INSPECT_NAME}(${originPaths.join(".")})}.\`` + }" but got: $\{${GLOBAL_INSPECT_NAME}(${joinOriginPaths( + originPaths + )})}.\`` )}), null) :` ); } @@ -872,15 +877,32 @@ function compileObjectType( name ); - const fieldCondition = context.options.useExperimentalPathBasedSkipInclude - ? fieldNodes - .map((it) => it.__internalShouldIncludePath?.[serializedResponsePath]) - .filter((it) => it) - .join(" || ") || /* if(true) - default */ "true" - : fieldNodes - .map((it) => it.__internalShouldInclude) - .filter((it) => it) - .join(" || ") || /* if(true) - default */ "true"; + const fieldConditionsList = ( + context.options.useExperimentalPathBasedSkipInclude + ? fieldNodes.map( + (it) => it.__internalShouldIncludePath?.[serializedResponsePath] + ) + : fieldNodes.map((it) => it.__internalShouldInclude) + ).filter(isNotNull); + + let fieldCondition = fieldConditionsList + .map((it) => { + if (it.length > 0) { + return `(${it.join(" && ")})`; + } + // default: if there are no conditions, it means that the field + // is always included in the path + return "true"; + }) + .filter(isNotNull) + .join(" || "); + + // if it's an empty string, it means that the field is always included + if (!fieldCondition) { + // if there are no conditions, it means that the field + // is always included in the path + fieldCondition = "true"; + } body(` ( @@ -907,6 +929,7 @@ function compileObjectType( name, responsePath: addPath(responsePath, name), originPaths, + originPathsFormatted: joinOriginPaths(originPaths), destinationPaths, parentType: type, fieldName: field.name, @@ -1046,8 +1069,8 @@ function compileAbstractType( return null; } })( - ${GLOBAL_EXECUTION_CONTEXT}.typeResolvers.${typeResolverName}(${originPaths.join( - "." + ${GLOBAL_EXECUTION_CONTEXT}.typeResolvers.${typeResolverName}(${joinOriginPaths( + originPaths )}, ${GLOBAL_CONTEXT_NAME}, ${getExecutionInfo( @@ -1916,3 +1939,11 @@ function mapAsyncIterator( } }; } + +function joinOriginPathsImpl(originPaths: string[]) { + return originPaths.join("."); +} + +function isNotNull(it: T): it is Exclude { + return it != null; +} diff --git a/src/generate.ts b/src/generate.ts new file mode 100644 index 00000000..f2d370a0 --- /dev/null +++ b/src/generate.ts @@ -0,0 +1,12 @@ +export function genFn() { + let body = ""; + + function add(str: string) { + body += str + "\n"; + return add; + } + + add.toString = () => body; + + return add; +} diff --git a/src/resolve-info.ts b/src/resolve-info.ts index b8bcd5b8..f73c3b22 100644 --- a/src/resolve-info.ts +++ b/src/resolve-info.ts @@ -1,4 +1,4 @@ -import genFn from "generate-function"; +import { genFn } from "./generate"; import { doTypesOverlap, type FieldNode, diff --git a/src/variables.ts b/src/variables.ts index dbbca5cf..fcb827e0 100644 --- a/src/variables.ts +++ b/src/variables.ts @@ -1,4 +1,4 @@ -import genFn from "generate-function"; +import { genFn } from "./generate"; import { GraphQLBoolean, GraphQLError, From 8b004462700a95b213d1cc2e9d5f831295fb6a95 Mon Sep 17 00:00:00 2001 From: Boopathi Nedunchezhiyan Date: Mon, 14 Oct 2024 14:05:55 +0200 Subject: [PATCH 2/3] more perf optimizations --- src/ast.ts | 444 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 258 insertions(+), 186 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index 9e63cd4e..24143205 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -99,154 +99,188 @@ function collectFieldsImpl( previousShouldInclude: string[] = [], parentResponsePath = "" ): FieldsAndNodes { - for (const selection of selectionSet.selections) { - switch (selection.kind) { - case Kind.FIELD: { - const name = getFieldEntryKey(selection); - if (!fields[name]) { - fields[name] = []; - } - const fieldNode: JitFieldNode = selection; + interface StackItem { + selectionSet: SelectionSetNode; + parentResponsePath: string; + previousShouldInclude: string[]; + } - // the current path of the field - // This is used to generate per path skip/include code - // because the same field can be reached from different paths (e.g. fragment reuse) - const currentPath = joinSkipIncludePath( - parentResponsePath, + const stack: StackItem[] = []; - // use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias - name - ); + stack.push({ + selectionSet, + parentResponsePath, + previousShouldInclude + }); + + while (stack.length > 0) { + const { selectionSet, parentResponsePath, previousShouldInclude } = + stack.pop()!; + + for (const selection of selectionSet.selections) { + switch (selection.kind) { + case Kind.FIELD: { + collectFieldsForField({ + compilationContext, + fields, + parentResponsePath, + previousShouldInclude, + selection + }); + break; + } - // `should include`s generated for the current fieldNode - const compiledSkipInclude = compileSkipInclude( - compilationContext, - selection - ); + case Kind.INLINE_FRAGMENT: { + if ( + !doesFragmentConditionMatch( + compilationContext, + selection, + runtimeType + ) + ) { + continue; + } - /** - * Carry over fragment's skip and include code - * - * fieldNode.__internalShouldInclude - * --------------------------------- - * When the parent field has a skip or include, the current one - * should be skipped if the parent is skipped in the path. - * - * previousShouldInclude - * --------------------- - * `should include`s from fragment spread and inline fragments - * - * compileSkipInclude(selection) - * ----------------------------- - * `should include`s generated for the current fieldNode - */ - if (compilationContext.options.useExperimentalPathBasedSkipInclude) { - if (!fieldNode.__internalShouldIncludePath) - fieldNode.__internalShouldIncludePath = {}; - - fieldNode.__internalShouldIncludePath[currentPath] = - joinShouldIncludeCompilations( - fieldNode.__internalShouldIncludePath?.[currentPath] ?? [], + // current fragment's shouldInclude + const compiledSkipInclude = compileSkipInclude( + compilationContext, + selection + ); + + // push to stack + stack.push({ + selectionSet: selection.selectionSet, + parentResponsePath: parentResponsePath, + previousShouldInclude: joinShouldIncludeCompilations( + // `should include`s from previous fragments previousShouldInclude, + // current fragment's shouldInclude [compiledSkipInclude] - ); - } else { - // @deprecated - fieldNode.__internalShouldInclude = joinShouldIncludeCompilations( - fieldNode.__internalShouldInclude ?? [], - previousShouldInclude, - [compiledSkipInclude] - ); + ) + }); + break; } - /** - * We augment the entire subtree as the parent object's skip/include - * directives influence the child even if the child doesn't have - * skip/include on it's own. - * - * Refer the function definition for example. - */ - augmentFieldNodeTree(compilationContext, fieldNode, currentPath); - - fields[name].push(fieldNode); - break; - } - case Kind.INLINE_FRAGMENT: { - if ( - !doesFragmentConditionMatch( + case Kind.FRAGMENT_SPREAD: { + const fragName = selection.name.value; + if (visitedFragmentNames[fragName]) { + continue; + } + visitedFragmentNames[fragName] = true; + const fragment = compilationContext.fragments[fragName]; + if ( + !fragment || + !doesFragmentConditionMatch( + compilationContext, + fragment, + runtimeType + ) + ) { + continue; + } + + // current fragment's shouldInclude + const compiledSkipInclude = compileSkipInclude( compilationContext, - selection, - runtimeType - ) - ) { - continue; + selection + ); + + // push to stack + stack.push({ + selectionSet: fragment.selectionSet, + parentResponsePath, + previousShouldInclude: joinShouldIncludeCompilations( + // `should include`s from previous fragments + previousShouldInclude, + // current fragment's shouldInclude + [compiledSkipInclude] + ) + }); + break; } + } + } + } - // current fragment's shouldInclude - const compiledSkipInclude = compileSkipInclude( - compilationContext, - selection - ); + return fields; +} - // recurse - collectFieldsImpl( - compilationContext, - runtimeType, - selection.selectionSet, - fields, - visitedFragmentNames, - joinShouldIncludeCompilations( - // `should include`s from previous fragments - previousShouldInclude, - // current fragment's shouldInclude - [compiledSkipInclude] - ), - parentResponsePath - ); - break; - } +function collectFieldsForField({ + compilationContext, + fields, + parentResponsePath, + previousShouldInclude, + selection +}: { + compilationContext: CompilationContext; + fields: FieldsAndNodes; + parentResponsePath: string; + previousShouldInclude: string[]; + selection: FieldNode; +}) { + const name = getFieldEntryKey(selection); + if (!fields[name]) { + fields[name] = []; + } + const fieldNode: JitFieldNode = selection; - case Kind.FRAGMENT_SPREAD: { - const fragName = selection.name.value; - if (visitedFragmentNames[fragName]) { - continue; - } - visitedFragmentNames[fragName] = true; - const fragment = compilationContext.fragments[fragName]; - if ( - !fragment || - !doesFragmentConditionMatch(compilationContext, fragment, runtimeType) - ) { - continue; - } + // the current path of the field + // This is used to generate per path skip/include code + // because the same field can be reached from different paths (e.g. fragment reuse) + const currentPath = joinSkipIncludePath( + parentResponsePath, - // current fragment's shouldInclude - const compiledSkipInclude = compileSkipInclude( - compilationContext, - selection - ); + // use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias + name + ); - // recurse - collectFieldsImpl( - compilationContext, - runtimeType, - fragment.selectionSet, - fields, - visitedFragmentNames, - joinShouldIncludeCompilations( - // `should include`s from previous fragments - previousShouldInclude, - // current fragment's shouldInclude - [compiledSkipInclude] - ), - parentResponsePath - ); + // `should include`s generated for the current fieldNode + const compiledSkipInclude = compileSkipInclude(compilationContext, selection); - break; - } - } + /** + * Carry over fragment's skip and include code + * + * fieldNode.__internalShouldInclude + * --------------------------------- + * When the parent field has a skip or include, the current one + * should be skipped if the parent is skipped in the path. + * + * previousShouldInclude + * --------------------- + * `should include`s from fragment spread and inline fragments + * + * compileSkipInclude(selection) + * ----------------------------- + * `should include`s generated for the current fieldNode + */ + if (compilationContext.options.useExperimentalPathBasedSkipInclude) { + if (!fieldNode.__internalShouldIncludePath) + fieldNode.__internalShouldIncludePath = {}; + + fieldNode.__internalShouldIncludePath[currentPath] = + joinShouldIncludeCompilations( + fieldNode.__internalShouldIncludePath?.[currentPath] ?? [], + previousShouldInclude, + [compiledSkipInclude] + ); + } else { + // @deprecated + fieldNode.__internalShouldInclude = joinShouldIncludeCompilations( + fieldNode.__internalShouldInclude ?? [], + previousShouldInclude, + [compiledSkipInclude] + ); } - return fields; + /** + * We augment the entire subtree as the parent object's skip/include + * directives influence the child even if the child doesn't have + * skip/include on it's own. + * + * Refer the function definition for example. + */ + augmentFieldNodeTree(compilationContext, fieldNode, currentPath); + + fields[name].push(fieldNode); } /** @@ -303,66 +337,99 @@ function augmentFieldNodeTree( parentResponsePath: string ) { for (const selection of rootFieldNode.selectionSet?.selections ?? []) { - handle(rootFieldNode, selection, false, parentResponsePath); - } + /** + * Traverse through sub-selection and combine `shouldInclude`s + * from parent and current ones. + */ + interface StackItem { + parentFieldNode: JitFieldNode; + selection: SelectionNode; + comesFromFragmentSpread: boolean; + parentResponsePath: string; + } - /** - * Recursively traverse through sub-selection and combine `shouldInclude`s - * from parent and current ones. - */ - function handle( - parentFieldNode: JitFieldNode, - selection: SelectionNode, - comesFromFragmentSpread = false, - parentResponsePath: string - ) { - switch (selection.kind) { - case Kind.FIELD: { - const jitFieldNode: JitFieldNode = selection; - const currentPath = joinSkipIncludePath( - parentResponsePath, - - // use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias - getFieldEntryKey(jitFieldNode) - ); + const stack: StackItem[] = []; - if (!comesFromFragmentSpread) { - if (compilationContext.options.useExperimentalPathBasedSkipInclude) { - if (!jitFieldNode.__internalShouldIncludePath) - jitFieldNode.__internalShouldIncludePath = {}; - - jitFieldNode.__internalShouldIncludePath[currentPath] = - joinShouldIncludeCompilations( - parentFieldNode.__internalShouldIncludePath?.[ - parentResponsePath - ] ?? [], - jitFieldNode.__internalShouldIncludePath?.[currentPath] ?? [] - ); - } else { - // @deprecated - jitFieldNode.__internalShouldInclude = - joinShouldIncludeCompilations( - parentFieldNode.__internalShouldInclude ?? [], - jitFieldNode.__internalShouldInclude ?? [] - ); + stack.push({ + parentFieldNode: rootFieldNode, + selection, + comesFromFragmentSpread: false, + parentResponsePath + }); + + while (stack.length > 0) { + const { + parentFieldNode, + selection, + comesFromFragmentSpread, + parentResponsePath + } = stack.pop()!; + + switch (selection.kind) { + case Kind.FIELD: { + const jitFieldNode: JitFieldNode = selection; + const currentPath = joinSkipIncludePath( + parentResponsePath, + + // use alias(instead of selection.name.value) if available as the responsePath used for lookup uses alias + getFieldEntryKey(jitFieldNode) + ); + + if (!comesFromFragmentSpread) { + if ( + compilationContext.options.useExperimentalPathBasedSkipInclude + ) { + if (!jitFieldNode.__internalShouldIncludePath) + jitFieldNode.__internalShouldIncludePath = {}; + + jitFieldNode.__internalShouldIncludePath[currentPath] = + joinShouldIncludeCompilations( + parentFieldNode.__internalShouldIncludePath?.[ + parentResponsePath + ] ?? [], + jitFieldNode.__internalShouldIncludePath?.[currentPath] ?? [] + ); + } else { + // @deprecated + jitFieldNode.__internalShouldInclude = + joinShouldIncludeCompilations( + parentFieldNode.__internalShouldInclude ?? [], + jitFieldNode.__internalShouldInclude ?? [] + ); + } } + // go further down the query tree + for (const selection of jitFieldNode.selectionSet?.selections ?? []) { + stack.push({ + parentFieldNode: jitFieldNode, + selection, + comesFromFragmentSpread: false, + parentResponsePath: currentPath + }); + } + break; } - // go further down the query tree - for (const selection of jitFieldNode.selectionSet?.selections ?? []) { - handle(jitFieldNode, selection, false, currentPath); - } - break; - } - case Kind.INLINE_FRAGMENT: { - for (const subSelection of selection.selectionSet.selections) { - handle(parentFieldNode, subSelection, true, parentResponsePath); + case Kind.INLINE_FRAGMENT: { + for (const subSelection of selection.selectionSet.selections) { + stack.push({ + parentFieldNode, + selection: subSelection, + comesFromFragmentSpread: true, + parentResponsePath + }); + } + break; } - break; - } - case Kind.FRAGMENT_SPREAD: { - const fragment = compilationContext.fragments[selection.name.value]; - for (const subSelection of fragment.selectionSet.selections) { - handle(parentFieldNode, subSelection, true, parentResponsePath); + case Kind.FRAGMENT_SPREAD: { + const fragment = compilationContext.fragments[selection.name.value]; + for (const subSelection of fragment.selectionSet.selections) { + stack.push({ + parentFieldNode, + selection: subSelection, + comesFromFragmentSpread: true, + parentResponsePath + }); + } } } } @@ -427,6 +494,11 @@ function compileSkipInclude( compilationContext: CompilationContext, node: SelectionNode ): string { + // minor optimization to avoid compilation if there are no directives + if (node.directives == null || node.directives.length < 1) { + return "true"; + } + const { skipValue, includeValue } = compileSkipIncludeDirectiveValues( compilationContext, node From 8cbae3e33f81eebab6011ba5f8640b11f4250ec5 Mon Sep 17 00:00:00 2001 From: Boopathi Nedunchezhiyan Date: Mon, 14 Oct 2024 14:27:14 +0200 Subject: [PATCH 3/3] make non-null assertion a warning as it is required --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 04d2c4a1..53e1702d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -26,7 +26,7 @@ export default tseslint.config( "no-sequences": "error", "no-template-curly-in-string": "error", "@typescript-eslint/no-empty-function": "error", - "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/no-non-null-assertion": "warn", "@typescript-eslint/no-unused-vars": "warn",